Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1335 lines
36 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
api: rework plain and JSON implementations (#2405) - match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler - allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`) Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type. - restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type - adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it. - breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally. - allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON. - add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer - remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`. - add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output) - `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback - `/api/rpc` custom handler replaced with an `apiRegister` callback WIP further down: - no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers. - migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :) - actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays - fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...)
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
  1. /*
  2. RF BRIDGE MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "rfbridge.h"
  6. #if RFB_SUPPORT
  7. #include "api.h"
  8. #include "relay.h"
  9. #include "terminal.h"
  10. #include "mqtt.h"
  11. #include "ws.h"
  12. #include "utils.h"
  13. #include <algorithm>
  14. #include <bitset>
  15. #include <cstring>
  16. #include <list>
  17. #include <memory>
  18. // -----------------------------------------------------------------------------
  19. // GLOBALS TO THE MODULE
  20. // -----------------------------------------------------------------------------
  21. unsigned char _rfb_repeats = RFB_SEND_REPEATS;
  22. #if RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  23. #include <RCSwitch.h>
  24. RCSwitch * _rfb_modem;
  25. bool _rfb_receive { false };
  26. bool _rfb_transmit { false };
  27. #else
  28. constexpr bool _rfb_receive { true };
  29. constexpr bool _rfb_transmit { true };
  30. #endif
  31. std::forward_list<RfbCodeHandler> _rfb_code_handlers;
  32. void rfbSetCodeHandler(RfbCodeHandler handler) {
  33. _rfb_code_handlers.push_front(handler);
  34. }
  35. // -----------------------------------------------------------------------------
  36. // MATCH RECEIVED CODE WITH THE SPECIFIC RELAY ID
  37. // -----------------------------------------------------------------------------
  38. #if RELAY_SUPPORT
  39. struct RfbRelayMatch {
  40. RfbRelayMatch() = default;
  41. RfbRelayMatch(size_t id, PayloadStatus status) :
  42. _id(id),
  43. _status(status),
  44. _found(true)
  45. {}
  46. void reset(size_t id, PayloadStatus status) {
  47. _id = id;
  48. _status = status;
  49. _found = true;
  50. }
  51. size_t id() const {
  52. return _id;
  53. }
  54. PayloadStatus status() const {
  55. return _status;
  56. }
  57. explicit operator bool() const {
  58. return _found;
  59. }
  60. private:
  61. size_t _id { RelaysMax };
  62. PayloadStatus _status { PayloadStatus::Unknown };
  63. bool _found { false };
  64. };
  65. struct RfbLearn {
  66. unsigned long ts;
  67. size_t id;
  68. bool status;
  69. };
  70. // Usage depends on the implementation. Will either:
  71. // - efm8bb1: wait until learn OK / TIMEOUT code
  72. // - rc-switch: receiver loop will check `ts` vs RFB_LEARN_TIMEOUT
  73. static std::unique_ptr<RfbLearn> _rfb_learn;
  74. // Individual lock for the relay, prevent rfbStatus from re-sending the code we just received
  75. static std::bitset<RelaysMax> _rfb_relay_status_lock;
  76. #endif // RELAY_SUPPORT
  77. // -----------------------------------------------------------------------------
  78. // EFM8BB1 PROTOCOL PARSING
  79. // -----------------------------------------------------------------------------
  80. constexpr uint8_t RfbDefaultProtocol { 0u };
  81. constexpr uint8_t CodeStart { 0xAAu };
  82. constexpr uint8_t CodeEnd { 0x55u };
  83. constexpr uint8_t CodeAck { 0xA0u };
  84. // both stock and https://github.com/Portisch/RF-Bridge-EFM8BB1/
  85. // sending:
  86. constexpr uint8_t CodeLearn { 0xA1u };
  87. // receiving:
  88. constexpr uint8_t CodeLearnTimeout { 0xA2u };
  89. constexpr uint8_t CodeLearnOk { 0xA3u };
  90. constexpr uint8_t CodeRecvBasic = { 0xA4u };
  91. constexpr uint8_t CodeSendBasic = { 0xA5u };
  92. // only https://github.com/Portisch/RF-Bridge-EFM8BB1/
  93. constexpr uint8_t CodeRecvProto { 0xA6u };
  94. constexpr uint8_t CodeRecvBucket { 0xB1u };
  95. struct RfbParser {
  96. using callback_type = void(uint8_t, const std::vector<uint8_t>&);
  97. using state_type = void(RfbParser::*)(uint8_t);
  98. // AA XX ... 55
  99. // ^~~~~ ~~ - protocol head + tail
  100. // ^~ - message code
  101. // ^~~ - actual payload is always 9 bytes
  102. static constexpr size_t PayloadSizeBasic { 9ul };
  103. static constexpr size_t MessageSizeBasic { PayloadSizeBasic + 3ul };
  104. static constexpr size_t MessageSizeMax { 112ul };
  105. RfbParser() = delete;
  106. RfbParser(const RfbParser&) = delete;
  107. explicit RfbParser(callback_type* callback) :
  108. _callback(callback)
  109. {}
  110. RfbParser(RfbParser&&) = default;
  111. void stop(uint8_t c) {
  112. }
  113. void start(uint8_t c) {
  114. switch (c) {
  115. case CodeStart:
  116. _state = &RfbParser::read_code;
  117. break;
  118. default:
  119. _state = &RfbParser::stop;
  120. break;
  121. }
  122. }
  123. void read_code(uint8_t c) {
  124. _payload_code = c;
  125. switch (c) {
  126. // Generic ACK signal. We *expect* this after our requests
  127. case CodeAck:
  128. // *Expect* any code within a certain window.
  129. // Only matters to us, does not really do anything but help us to signal that the next code needs to be recorded
  130. case CodeLearnTimeout:
  131. _state = &RfbParser::read_end;
  132. break;
  133. // both stock and https://github.com/Portisch/RF-Bridge-EFM8BB1/
  134. // receive 9 bytes, where first 3 2-byte tuples are timings
  135. // and the last 3 bytes are the actual payload
  136. case CodeLearnOk:
  137. case CodeRecvBasic:
  138. _payload_length = PayloadSizeBasic;
  139. _state = &RfbParser::read_until_length;
  140. break;
  141. // specific to the https://github.com/Portisch/RF-Bridge-EFM8BB1/
  142. // receive N bytes, where the 1st byte is the protocol ID and the next N-1 bytes are the payload
  143. case CodeRecvProto:
  144. _state = &RfbParser::read_length;
  145. break;
  146. // unlike CodeRecvProto, we don't have any length byte here :/ for some reason, it is there only when sending
  147. // just bail out when we find CodeEnd
  148. // (TODO: is number of buckets somehow convertible to the 'expected' size?)
  149. case CodeRecvBucket:
  150. _state = &RfbParser::read_length;
  151. break;
  152. default:
  153. _state = &RfbParser::stop;
  154. break;
  155. }
  156. }
  157. void read_end(uint8_t c) {
  158. if (CodeEnd == c) {
  159. _callback(_payload_code, _payload);
  160. }
  161. _state = &RfbParser::stop;
  162. }
  163. void read_until_end(uint8_t c) {
  164. if (CodeEnd == c) {
  165. read_end(c);
  166. return;
  167. }
  168. _payload.push_back(c);
  169. }
  170. void read_until_length(uint8_t c) {
  171. _payload.push_back(c);
  172. if ((_payload_offset + _payload_length) == _payload.size()) {
  173. switch (_payload_code) {
  174. case CodeLearnOk:
  175. case CodeRecvBasic:
  176. case CodeRecvProto:
  177. _state = &RfbParser::read_end;
  178. break;
  179. case CodeRecvBucket:
  180. _state = &RfbParser::read_until_end;
  181. break;
  182. default:
  183. _state = &RfbParser::stop;
  184. break;
  185. }
  186. _payload_length = 0u;
  187. }
  188. }
  189. void read_length(uint8_t c) {
  190. switch (_payload_code) {
  191. case CodeRecvProto:
  192. _payload_length = c;
  193. break;
  194. case CodeRecvBucket:
  195. _payload_length = c * 2;
  196. break;
  197. default:
  198. _state = &RfbParser::stop;
  199. return;
  200. }
  201. _payload.push_back(c);
  202. _payload_offset = _payload.size();
  203. _state = &RfbParser::read_until_length;
  204. }
  205. bool loop(uint8_t c) {
  206. (this->*_state)(c);
  207. return (_state != &RfbParser::stop);
  208. }
  209. void reset() {
  210. _payload.clear();
  211. _payload_length = 0u;
  212. _payload_offset = 0u;
  213. _payload_code = 0u;
  214. _state = &RfbParser::start;
  215. }
  216. void reserve(size_t size) {
  217. _payload.reserve(size);
  218. }
  219. private:
  220. callback_type* _callback { nullptr };
  221. state_type _state { &RfbParser::start };
  222. std::vector<uint8_t> _payload;
  223. size_t _payload_length { 0ul };
  224. size_t _payload_offset { 0ul };
  225. uint8_t _payload_code { 0ul };
  226. };
  227. // -----------------------------------------------------------------------------
  228. // MESSAGE SENDER
  229. //
  230. // Depends on the selected provider. While we do serialize RCSwitch results,
  231. // we don't want to pass around such byte-array everywhere since we already
  232. // know all of the required data members and can prepare a basic POD struct
  233. // -----------------------------------------------------------------------------
  234. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  235. struct RfbMessage {
  236. RfbMessage(const RfbMessage&) = default;
  237. RfbMessage(RfbMessage&&) = default;
  238. explicit RfbMessage(uint8_t (&data)[RfbParser::PayloadSizeBasic], unsigned char repeats_) :
  239. repeats(repeats_)
  240. {
  241. std::copy(data, data + sizeof(data), code);
  242. }
  243. uint8_t code[RfbParser::PayloadSizeBasic] { 0u };
  244. uint8_t repeats { 1u };
  245. };
  246. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  247. struct RfbMessage {
  248. using code_type = decltype(std::declval<RCSwitch>().getReceivedValue());
  249. static constexpr size_t BufferSize = sizeof(code_type) + 5;
  250. uint8_t protocol;
  251. uint16_t timing;
  252. uint8_t bits;
  253. code_type code;
  254. uint8_t repeats;
  255. };
  256. #endif // RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  257. static std::list<RfbMessage> _rfb_message_queue;
  258. void _rfbLearnImpl();
  259. void _rfbReceiveImpl();
  260. void _rfbSendImpl(const RfbMessage& message);
  261. // -----------------------------------------------------------------------------
  262. // WEBUI INTEGRATION
  263. // -----------------------------------------------------------------------------
  264. #if WEB_SUPPORT
  265. void _rfbWebSocketOnVisible(JsonObject& root) {
  266. root["rfbVisible"] = 1;
  267. }
  268. #if RELAY_SUPPORT
  269. void _rfbWebSocketSendCodeArray(JsonObject& root, size_t start, size_t size) {
  270. JsonObject& rfb = root.createNestedObject("rfb");
  271. rfb["size"] = size;
  272. rfb["start"] = start;
  273. JsonArray& on = rfb.createNestedArray("on");
  274. JsonArray& off = rfb.createNestedArray("off");
  275. for (auto id = start; id < (start + size); ++id) {
  276. on.add(rfbRetrieve(id, true));
  277. off.add(rfbRetrieve(id, false));
  278. }
  279. }
  280. void _rfbWebSocketOnData(JsonObject& root) {
  281. _rfbWebSocketSendCodeArray(root, 0ul, relayCount());
  282. }
  283. #endif // RELAY_SUPPORT
  284. void _rfbWebSocketOnConnected(JsonObject& root) {
  285. root["rfbRepeat"] = getSetting("rfbRepeat", RFB_SEND_REPEATS);
  286. #if RELAY_SUPPORT
  287. root["rfbCount"] = relayCount();
  288. #endif
  289. #if RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  290. root["rfbdirectVisible"] = 1;
  291. root["rfbRX"] = getSetting("rfbRX", RFB_RX_PIN);
  292. root["rfbTX"] = getSetting("rfbTX", RFB_TX_PIN);
  293. #endif
  294. }
  295. void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  296. #if RELAY_SUPPORT
  297. if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
  298. if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
  299. if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
  300. #endif
  301. }
  302. bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  303. return (strncmp(key, "rfb", 3) == 0);
  304. }
  305. #endif // WEB_SUPPORT
  306. // -----------------------------------------------------------------------------
  307. // RELAY <-> CODE MATCHING
  308. // -----------------------------------------------------------------------------
  309. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  310. // we only care about last 6 chars (3 bytes in hex),
  311. // since in 'default' mode rfbridge only handles a single protocol
  312. bool _rfbCompare(const char* lhs, const char* rhs, size_t length) {
  313. return (0 == std::memcmp((lhs + length - 6), (rhs + length - 6), 6));
  314. }
  315. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  316. // protocol is [2:3), actual payload is [10:), as bit length may vary
  317. // although, we don't care if it does, since we expect length of both args to be the same
  318. bool _rfbCompare(const char* lhs, const char* rhs, size_t length) {
  319. return (0 == std::memcmp((lhs + 2), (rhs + 2), 2))
  320. && (0 == std::memcmp((lhs + 10), (rhs + 10), length - 10));
  321. }
  322. #endif // RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  323. #if RELAY_SUPPORT
  324. // try to find the 'code' saves as either rfbON# or rfbOFF#
  325. //
  326. // **always** expect full length code as input to simplify comparison
  327. // previous implementation tried to help MQTT / API requests to match based on the saved code,
  328. // thus requiring us to 'return' value from settings as the real code, replacing input
  329. RfbRelayMatch _rfbMatch(const char* code) {
  330. if (!relayCount()) {
  331. return {};
  332. }
  333. const auto len = strlen(code);
  334. // we gather all available options, as the kv store might be defined in any order
  335. // scan kvs only once, since we want both ON and OFF options and don't want to depend on the relayCount()
  336. RfbRelayMatch matched;
  337. using namespace settings;
  338. kv_store.foreach([code, len, &matched](kvs_type::KeyValueResult&& kv) {
  339. const auto key = kv.key.read();
  340. PayloadStatus status = key.startsWith(F("rfbON"))
  341. ? PayloadStatus::On : key.startsWith(F("rfbOFF"))
  342. ? PayloadStatus::Off : PayloadStatus::Unknown;
  343. if (PayloadStatus::Unknown == status) {
  344. return;
  345. }
  346. const auto value = kv.value.read();
  347. if (len != value.length()) {
  348. return;
  349. }
  350. if (!_rfbCompare(code, value.c_str(), len)) {
  351. return;
  352. }
  353. // note: strlen is constexpr here
  354. const char* id_ptr = key.c_str() + (
  355. (PayloadStatus::On == status) ? strlen("rfbON") : strlen("rfbOFF"));
  356. if (*id_ptr == '\0') {
  357. return;
  358. }
  359. size_t id;
  360. if (!tryParseId(id_ptr, relayCount, id)) {
  361. return;
  362. }
  363. // when we see the same id twice, we match the opposite statuses
  364. if (matched && (id == matched.id())) {
  365. matched.reset(matched.id(), PayloadStatus::Toggle);
  366. return;
  367. }
  368. matched.reset(matched ? std::min(id, matched.id()) : id, status);
  369. });
  370. return matched;
  371. }
  372. void _rfbLearnFromString(std::unique_ptr<RfbLearn>& learn, const char* buffer) {
  373. if (!learn) return;
  374. DEBUG_MSG_P(PSTR("[RF] Learned relay ID %u after %u ms\n"), learn->id, millis() - learn->ts);
  375. rfbStore(learn->id, learn->status, buffer);
  376. // Websocket update needs to happen right here, since the only time
  377. // we send these in bulk is at the very start of the connection
  378. #if WEB_SUPPORT
  379. auto id = learn->id;
  380. wsPost([id](JsonObject& root) {
  381. _rfbWebSocketSendCodeArray(root, id, 1ul);
  382. });
  383. #endif
  384. learn.reset(nullptr);
  385. }
  386. bool _rfbRelayHandler(const char* buffer, bool locked = false) {
  387. bool result { false };
  388. auto match = _rfbMatch(buffer);
  389. if (match) {
  390. DEBUG_MSG_P(PSTR("[RF] Matched with the relay ID %u\n"), match.id());
  391. _rfb_relay_status_lock.set(match.id(), locked);
  392. switch (match.status()) {
  393. case PayloadStatus::On:
  394. case PayloadStatus::Off:
  395. relayStatus(match.id(), (PayloadStatus::On == match.status()));
  396. result = true;
  397. break;
  398. case PayloadStatus::Toggle:
  399. relayToggle(match.id());
  400. result = true;
  401. case PayloadStatus::Unknown:
  402. break;
  403. }
  404. }
  405. return result;
  406. }
  407. void _rfbLearnStartFromPayload(const char* payload) {
  408. // The payload must be the `relayID,mode` (where mode is either 0 or 1)
  409. const char* sep = strchr(payload, ',');
  410. if (nullptr == sep) {
  411. return;
  412. }
  413. // ref. RelaysMax, we only have up to 2 digits
  414. char relay[3] {0, 0, 0};
  415. if ((sep - payload) > 2) {
  416. return;
  417. }
  418. std::copy(payload, sep, relay);
  419. size_t id;
  420. if (!tryParseId(relay, relayCount, id)) {
  421. DEBUG_MSG_P(PSTR("[RF] Invalid relay ID (%u)\n"), id);
  422. return;
  423. }
  424. ++sep;
  425. if ((*sep == '0') || (*sep == '1')) {
  426. rfbLearn(id, (*sep != '0'));
  427. }
  428. }
  429. void _rfbLearnFromReceived(std::unique_ptr<RfbLearn>& learn, const char* buffer) {
  430. if (millis() - learn->ts > RFB_LEARN_TIMEOUT) {
  431. DEBUG_MSG_P(PSTR("[RF] Learn timeout after %u ms\n"), millis() - learn->ts);
  432. learn.reset(nullptr);
  433. return;
  434. }
  435. _rfbLearnFromString(learn, buffer);
  436. }
  437. #endif // RELAY_SUPPORT
  438. // -----------------------------------------------------------------------------
  439. // RF handler implementations
  440. // -----------------------------------------------------------------------------
  441. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  442. void _rfbEnqueue(uint8_t (&code)[RfbParser::PayloadSizeBasic], unsigned char repeats = 1u) {
  443. if (!_rfb_transmit) return;
  444. _rfb_message_queue.push_back(RfbMessage(code, repeats));
  445. }
  446. bool _rfbEnqueue(const char* code, size_t length, unsigned char repeats = 1u) {
  447. uint8_t buffer[RfbParser::PayloadSizeBasic] { 0u };
  448. if (hexDecode(code, length, buffer, sizeof(buffer))) {
  449. _rfbEnqueue(buffer, repeats);
  450. return true;
  451. }
  452. DEBUG_MSG_P(PSTR("[RF] Cannot decode the message\n"));
  453. return false;
  454. }
  455. void _rfbSendRaw(const uint8_t* message, size_t size) {
  456. Serial.write(message, size);
  457. }
  458. void _rfbAckImpl() {
  459. static uint8_t message[3] {
  460. CodeStart, CodeAck, CodeEnd
  461. };
  462. DEBUG_MSG_P(PSTR("[RF] Sending ACK\n"));
  463. Serial.write(message, sizeof(message));
  464. Serial.flush();
  465. }
  466. void _rfbLearnImpl() {
  467. static uint8_t message[3] {
  468. CodeStart, CodeLearn, CodeEnd
  469. };
  470. DEBUG_MSG_P(PSTR("[RF] Sending LEARN\n"));
  471. Serial.write(message, sizeof(message));
  472. Serial.flush();
  473. }
  474. void _rfbSendImpl(const RfbMessage& message) {
  475. Serial.write(CodeStart);
  476. Serial.write(CodeSendBasic);
  477. _rfbSendRaw(message.code, sizeof(message.code));
  478. Serial.write(CodeEnd);
  479. Serial.flush();
  480. }
  481. void _rfbParse(uint8_t code, const std::vector<uint8_t>& payload) {
  482. switch (code) {
  483. case CodeAck:
  484. DEBUG_MSG_P(PSTR("[RF] Received ACK\n"));
  485. break;
  486. case CodeLearnTimeout:
  487. _rfbAckImpl();
  488. #if RELAY_SUPPORT
  489. if (_rfb_learn) {
  490. DEBUG_MSG_P(PSTR("[RF] Learn timeout after %u ms\n"), millis() - _rfb_learn->ts);
  491. _rfb_learn.reset(nullptr);
  492. }
  493. #endif
  494. break;
  495. case CodeLearnOk:
  496. case CodeRecvBasic: {
  497. _rfbAckImpl();
  498. char buffer[(RfbParser::PayloadSizeBasic * 2) + 1] = {0};
  499. if (hexEncode(payload.data(), payload.size(), buffer, sizeof(buffer))) {
  500. DEBUG_MSG_P(PSTR("[RF] Received code: %s\n"), buffer);
  501. #if RELAY_SUPPORT
  502. if (CodeLearnOk == code) {
  503. _rfbLearnFromString(_rfb_learn, buffer);
  504. } else {
  505. _rfbRelayHandler(buffer, true);
  506. }
  507. #endif
  508. #if MQTT_SUPPORT
  509. mqttSend(MQTT_TOPIC_RFIN, buffer, false, false);
  510. #endif
  511. for (auto& handler : _rfb_code_handlers) {
  512. handler(RfbDefaultProtocol, buffer + 12);
  513. }
  514. }
  515. break;
  516. }
  517. case CodeRecvProto:
  518. case CodeRecvBucket: {
  519. _rfbAckImpl();
  520. char buffer[(RfbParser::MessageSizeMax * 2) + 1] = {0};
  521. if (hexEncode(payload.data(), payload.size(), buffer, sizeof(buffer))) {
  522. DEBUG_MSG_P(PSTR("[RF] Received %s code: %s\n"),
  523. (CodeRecvProto == code) ? "advanced" : "bucket", buffer
  524. );
  525. #if MQTT_SUPPORT
  526. mqttSend(MQTT_TOPIC_RFIN, buffer, false, false);
  527. #endif
  528. // ref. https://github.com/Portisch/RF-Bridge-EFM8BB1/wiki/0xA6#example-of-a-received-decoded-protocol
  529. for (auto& handler : _rfb_code_handlers) {
  530. handler(payload[0], buffer + 2);
  531. }
  532. } else {
  533. DEBUG_MSG_P(PSTR("[RF] Received 0x%02X (%u bytes)\n"), code, payload.size());
  534. }
  535. break;
  536. }
  537. }
  538. }
  539. static RfbParser _rfb_parser(_rfbParse);
  540. void _rfbReceiveImpl() {
  541. while (Serial.available()) {
  542. auto c = Serial.read();
  543. if (c < 0) {
  544. continue;
  545. }
  546. // narrowing is justified, as `c` can only contain byte-sized value
  547. if (!_rfb_parser.loop(static_cast<uint8_t>(c))) {
  548. _rfb_parser.reset();
  549. }
  550. }
  551. }
  552. // note that we don't care about queue here, just dump raw message as-is
  553. void _rfbSendRawFromPayload(const char * raw) {
  554. auto rawlen = strlen(raw);
  555. if (rawlen > (RfbParser::MessageSizeMax * 2)) return;
  556. if ((rawlen < 6) || (rawlen & 1)) return;
  557. DEBUG_MSG_P(PSTR("[RF] Sending RAW MESSAGE \"%s\"\n"), raw);
  558. size_t bytes = 0;
  559. uint8_t message[RfbParser::MessageSizeMax] { 0u };
  560. if ((bytes = hexDecode(raw, rawlen, message, sizeof(message)))) {
  561. if (message[0] != CodeStart) return;
  562. if (message[bytes - 1] != CodeEnd) return;
  563. _rfbSendRaw(message, bytes);
  564. }
  565. }
  566. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  567. namespace {
  568. size_t _rfb_bytes_for_bits(size_t bits) {
  569. decltype(bits) bytes = 0;
  570. decltype(bits) need = 0;
  571. while (need < bits) {
  572. need += 8u;
  573. bytes += 1u;
  574. }
  575. return bytes;
  576. }
  577. // TODO: RCSwitch code type: long unsigned int != uint32_t, thus the specialization
  578. static_assert(sizeof(uint32_t) == sizeof(long unsigned int), "");
  579. template <typename T>
  580. T _rfb_bswap(T value);
  581. template <>
  582. [[gnu::unused]] uint32_t _rfb_bswap(uint32_t value) {
  583. return __builtin_bswap32(value);
  584. }
  585. template <>
  586. [[gnu::unused]] long unsigned int _rfb_bswap(long unsigned int value) {
  587. return __builtin_bswap32(value);
  588. }
  589. template <>
  590. [[gnu::unused]] uint64_t _rfb_bswap(uint64_t value) {
  591. return __builtin_bswap64(value);
  592. }
  593. }
  594. void _rfbEnqueue(uint8_t protocol, uint16_t timing, uint8_t bits, RfbMessage::code_type code, unsigned char repeats = 1u) {
  595. if (!_rfb_transmit) return;
  596. _rfb_message_queue.push_back(RfbMessage{protocol, timing, bits, code, repeats});
  597. }
  598. void _rfbEnqueue(const char* message, size_t length, unsigned char repeats = 1u) {
  599. uint8_t buffer[RfbMessage::BufferSize] { 0u };
  600. if (hexDecode(message, length, buffer, sizeof(buffer))) {
  601. const auto bytes = _rfb_bytes_for_bits(buffer[4]);
  602. uint8_t raw_code[sizeof(RfbMessage::code_type)] { 0u };
  603. std::memcpy(&raw_code[sizeof(raw_code) - bytes], &buffer[5], bytes);
  604. RfbMessage::code_type code;
  605. std::memcpy(&code, raw_code, sizeof(code));
  606. _rfbEnqueue(buffer[1], (buffer[2] << 8) | buffer[3], buffer[4], _rfb_bswap(code), repeats);
  607. return;
  608. }
  609. DEBUG_MSG_P(PSTR("[RF] Cannot decode the message\n"));
  610. }
  611. void _rfbLearnImpl() {
  612. DEBUG_MSG_P(PSTR("[RF] Entering LEARN mode\n"));
  613. }
  614. void _rfbSendImpl(const RfbMessage& message) {
  615. if (!_rfb_transmit) return;
  616. // TODO: note that this seems to be setting global setting
  617. // if code for some reason forgets this, we end up with the previous value
  618. _rfb_modem->setProtocol(message.protocol);
  619. if (message.timing) {
  620. _rfb_modem->setPulseLength(message.timing);
  621. }
  622. yield();
  623. _rfb_modem->send(message.code, message.bits);
  624. _rfb_modem->resetAvailable();
  625. }
  626. // Try to mimic the basic RF message format. although, we might have different size of the code itself
  627. // Skip leading zeroes and only keep the useful data
  628. //
  629. // TODO: 'timing' value shooould be relatively small,
  630. // since it's original intent was to be used with 16bit ints
  631. // TODO: both 'protocol' and 'bitlength' fit in a byte, despite being declared as 'unsigned int'
  632. size_t _rfbModemPack(uint8_t (&out)[RfbMessage::BufferSize], RfbMessage::code_type code, unsigned int protocol, unsigned int timing, unsigned int bits) {
  633. static_assert((sizeof(decltype(code)) == 4) || (sizeof(decltype(code)) == 8), "");
  634. size_t index = 0;
  635. out[index++] = 0xC0;
  636. out[index++] = static_cast<uint8_t>(protocol);
  637. out[index++] = static_cast<uint8_t>(timing >> 8);
  638. out[index++] = static_cast<uint8_t>(timing);
  639. out[index++] = static_cast<uint8_t>(bits);
  640. auto bytes = _rfb_bytes_for_bits(bits);
  641. if (bytes > (sizeof(out) - index)) {
  642. return 0;
  643. }
  644. // manually overload each bswap, since we can't use ternary here
  645. // (and `if constexpr (...)` is only available starting from Arduino Core 3.x.x)
  646. decltype(code) swapped = _rfb_bswap(code);
  647. uint8_t raw[sizeof(swapped)];
  648. std::memcpy(raw, &swapped, sizeof(raw));
  649. while (bytes) {
  650. out[index++] = raw[sizeof(raw) - (bytes--)];
  651. }
  652. return index;
  653. }
  654. void _rfbReceiveImpl() {
  655. if (!_rfb_receive) return;
  656. // TODO: rc-switch isr handler sets 4 variables at the same time and never checks their existence before overwriting them
  657. // thus, we can't *really* trust that all 4 are from the same reading :/
  658. // TODO: in theory, we may also expirience memory tearing while doing 2 separate 32bit reads on the 64bit code value,
  659. // while isr handler *may* write into it at the same time
  660. auto rf_code = _rfb_modem->getReceivedValue();
  661. if (!rf_code) {
  662. return;
  663. }
  664. #if RFB_RECEIVE_DELAY
  665. static unsigned long last = 0;
  666. if (millis() - last < RFB_RECEIVE_DELAY) {
  667. _rfb_modem->resetAvailable();
  668. return;
  669. }
  670. last = millis();
  671. #endif
  672. uint8_t message[RfbMessage::BufferSize];
  673. auto real_msgsize = _rfbModemPack(
  674. message,
  675. rf_code,
  676. _rfb_modem->getReceivedProtocol(),
  677. _rfb_modem->getReceivedDelay(),
  678. _rfb_modem->getReceivedBitlength()
  679. );
  680. char buffer[(sizeof(message) * 2) + 1] = {0};
  681. if (hexEncode(message, real_msgsize, buffer, sizeof(buffer))) {
  682. DEBUG_MSG_P(PSTR("[RF] Received code: %s\n"), buffer);
  683. #if RELAY_SUPPORT
  684. if (_rfb_learn) {
  685. _rfbLearnFromReceived(_rfb_learn, buffer);
  686. } else {
  687. _rfbRelayHandler(buffer, true);
  688. }
  689. #endif
  690. #if MQTT_SUPPORT
  691. mqttSend(MQTT_TOPIC_RFIN, buffer, false, false);
  692. #endif
  693. for (auto& handler : _rfb_code_handlers) {
  694. handler(message[1], buffer + 10);
  695. }
  696. }
  697. _rfb_modem->resetAvailable();
  698. }
  699. #endif // RFB_PROVIDER == ...
  700. void _rfbSendQueued() {
  701. if (!_rfb_transmit) return;
  702. if (_rfb_message_queue.empty()) return;
  703. static unsigned long last = 0;
  704. if (millis() - last < RFB_SEND_DELAY) return;
  705. last = millis();
  706. auto message = _rfb_message_queue.front();
  707. _rfb_message_queue.pop_front();
  708. _rfbSendImpl(message);
  709. // Sometimes we really want to repeat the message, not only to rely on built-in transfer repeat
  710. if (message.repeats > 1) {
  711. message.repeats -= 1;
  712. _rfb_message_queue.push_back(std::move(message));
  713. }
  714. yield();
  715. }
  716. // Check if the payload looks like a HEX code (plus comma, specifying the 'repeats' arg for the queue)
  717. void _rfbSendFromPayload(const char * payload) {
  718. size_t len { strlen(payload) };
  719. if (!len) {
  720. return;
  721. }
  722. decltype(_rfb_repeats) repeats { _rfb_repeats };
  723. const char* sep { strchr(payload, ',') };
  724. if (sep) {
  725. len -= strlen(sep);
  726. sep += 1;
  727. if ('\0' == *sep) return;
  728. if ('-' == *sep) return;
  729. char *endptr = nullptr;
  730. repeats = strtoul(sep, &endptr, 10);
  731. if (endptr == payload || endptr[0] != '\0') {
  732. return;
  733. }
  734. }
  735. if (!len || (len & 1)) {
  736. return;
  737. }
  738. DEBUG_MSG_P(PSTR("[RF] Enqueuing MESSAGE '%s' %u time(s)\n"), payload, repeats);
  739. // We postpone the actual sending until the loop, as we may've been called from MQTT or HTTP API
  740. // RFB_PROVIDER implementation should select the appropriate de-serialization function
  741. _rfbEnqueue(payload, len, repeats);
  742. }
  743. void rfbSend(const char* code) {
  744. _rfbSendFromPayload(code);
  745. }
  746. void rfbSend(const String& code) {
  747. _rfbSendFromPayload(code.c_str());
  748. }
  749. #if MQTT_SUPPORT
  750. void _rfbMqttCallback(unsigned int type, const char * topic, char * payload) {
  751. if (type == MQTT_CONNECT_EVENT) {
  752. #if RELAY_SUPPORT
  753. mqttSubscribe(MQTT_TOPIC_RFLEARN);
  754. #endif
  755. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  756. mqttSubscribe(MQTT_TOPIC_RFRAW);
  757. #endif
  758. if (_rfb_transmit) {
  759. mqttSubscribe(MQTT_TOPIC_RFOUT);
  760. }
  761. return;
  762. }
  763. if (type == MQTT_MESSAGE_EVENT) {
  764. String t = mqttMagnitude((char *) topic);
  765. #if RELAY_SUPPORT
  766. if (t.equals(MQTT_TOPIC_RFLEARN)) {
  767. _rfbLearnStartFromPayload(payload);
  768. return;
  769. }
  770. #endif
  771. if (t.equals(MQTT_TOPIC_RFOUT)) {
  772. #if RELAY_SUPPORT
  773. // we *sometimes* want to check the code against available rfbON / rfbOFF
  774. // e.g. in case we want to control some external device and have an external remote.
  775. // - when remote press happens, relays stay in sync when we receive the code via the processing loop
  776. // - when we send the code here, we never register it as *sent*, thus relays need to be made in sync manually
  777. if (!_rfbRelayHandler(payload)) {
  778. #endif
  779. _rfbSendFromPayload(payload);
  780. #if RELAY_SUPPORT
  781. }
  782. #endif
  783. return;
  784. }
  785. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  786. if (t.equals(MQTT_TOPIC_RFRAW)) {
  787. // in case this is RAW message, we should not match anything and just send it as-is to the serial
  788. _rfbSendRawFromPayload(payload);
  789. return;
  790. }
  791. #endif
  792. return;
  793. }
  794. }
  795. #endif // MQTT_SUPPORT
  796. #if API_SUPPORT
  797. void _rfbApiSetup() {
  798. apiRegister(F(MQTT_TOPIC_RFOUT),
  799. apiOk, // just a stub, nothing to return
  800. [](ApiRequest& request) {
  801. _rfbSendFromPayload(request.param(F("value")).c_str());
  802. return true;
  803. }
  804. );
  805. #if RELAY_SUPPORT
  806. apiRegister(F(MQTT_TOPIC_RFLEARN),
  807. [](ApiRequest& request) {
  808. char buffer[64] { 0 };
  809. if (_rfb_learn) {
  810. snprintf_P(buffer, sizeof(buffer), PSTR("learning id:%u,status:%c"),
  811. _rfb_learn->id, _rfb_learn->status ? 't' : 'f'
  812. );
  813. } else {
  814. snprintf_P(buffer, sizeof(buffer), PSTR("waiting"));
  815. }
  816. request.send(buffer);
  817. return true;
  818. },
  819. [](ApiRequest& request) {
  820. _rfbLearnStartFromPayload(request.param(F("value")).c_str());
  821. return true;
  822. }
  823. );
  824. #endif
  825. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  826. apiRegister(F(MQTT_TOPIC_RFRAW),
  827. apiOk, // just a stub, nothing to return
  828. [](ApiRequest& request) {
  829. _rfbSendRawFromPayload(request.param(F("value")).c_str());
  830. return true;
  831. }
  832. );
  833. #endif
  834. }
  835. #endif // API_SUPPORT
  836. #if TERMINAL_SUPPORT
  837. void _rfbCommandStatusDispatch(const terminal::CommandContext& ctx, size_t id, const String& payload, RelayStatusCallback callback) {
  838. auto parsed = rpcParsePayload(payload.c_str());
  839. switch (parsed) {
  840. case PayloadStatus::On:
  841. case PayloadStatus::Off:
  842. callback(id, (parsed == PayloadStatus::On));
  843. return;
  844. case PayloadStatus::Toggle:
  845. case PayloadStatus::Unknown:
  846. terminalError(ctx, F("Invalid status"));
  847. break;
  848. }
  849. }
  850. void _rfbInitCommands() {
  851. terminalRegisterCommand(F("RFB.SEND"), [](const terminal::CommandContext& ctx) {
  852. if (ctx.argc == 2) {
  853. rfbSend(ctx.argv[1]);
  854. return;
  855. }
  856. terminalError(ctx, F("RFB.SEND <CODE>"));
  857. });
  858. #if RELAY_SUPPORT
  859. terminalRegisterCommand(F("RFB.LEARN"), [](const terminal::CommandContext& ctx) {
  860. if (ctx.argc != 3) {
  861. terminalError(ctx, F("RFB.LEARN <ID> <STATUS>"));
  862. return;
  863. }
  864. size_t id;
  865. if (!tryParseId(ctx.argv[1].c_str(), relayCount, id)) {
  866. terminalError(ctx, F("Invalid relay ID"));
  867. return;
  868. }
  869. _rfbCommandStatusDispatch(ctx, id, ctx.argv[2], rfbLearn);
  870. });
  871. terminalRegisterCommand(F("RFB.FORGET"), [](const terminal::CommandContext& ctx) {
  872. if (ctx.argc < 2) {
  873. terminalError(ctx, F("RFB.FORGET <ID> [<STATUS>]"));
  874. return;
  875. }
  876. size_t id;
  877. if (!tryParseId(ctx.argv[1].c_str(), relayCount, id)) {
  878. terminalError(ctx, F("Invalid relay ID"));
  879. return;
  880. }
  881. if (ctx.argc == 3) {
  882. _rfbCommandStatusDispatch(ctx, id, ctx.argv[2], rfbForget);
  883. return;
  884. }
  885. rfbForget(id, true);
  886. rfbForget(id, false);
  887. terminalOK(ctx);
  888. });
  889. #endif // if RELAY_SUPPORT
  890. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  891. terminalRegisterCommand(F("RFB.WRITE"), [](const terminal::CommandContext& ctx) {
  892. if (ctx.argc != 2) {
  893. terminalError(ctx, F("RFB.WRITE <PAYLOAD>"));
  894. return;
  895. }
  896. _rfbSendRawFromPayload(ctx.argv[1].c_str());
  897. terminalOK(ctx);
  898. });
  899. #endif
  900. }
  901. #endif // TERMINAL_SUPPORT
  902. // -----------------------------------------------------------------------------
  903. // PUBLIC
  904. // -----------------------------------------------------------------------------
  905. void rfbStore(size_t id, bool status, const char * code) {
  906. SettingsKey key { status ? F("rfbON") : F("rfbOFF"), id };
  907. setSetting(key, code);
  908. DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.c_str(), code);
  909. }
  910. String rfbRetrieve(size_t id, bool status) {
  911. return getSetting({ status ? F("rfbON") : F("rfbOFF"), id });
  912. }
  913. #if RELAY_SUPPORT
  914. void rfbStatus(size_t id, bool status) {
  915. // TODO: This is a left-over from the old implementation. Right now we set this lock when relay handler
  916. // is called within the receiver, while this is called from either relayStatus or relay loop calling
  917. // this via provider callback. This prevents us from re-sending the code we just received.
  918. // TODO: Consider having 'origin' of the relay change. Either supply relayStatus with an additional arg,
  919. // or track these statuses directly.
  920. if (!_rfb_relay_status_lock[id]) {
  921. rfbSend(rfbRetrieve(id, status));
  922. }
  923. _rfb_relay_status_lock[id] = false;
  924. }
  925. void rfbLearn(size_t id, bool status) {
  926. _rfb_learn.reset(new RfbLearn { millis(), id, status });
  927. _rfbLearnImpl();
  928. }
  929. void rfbForget(size_t id, bool status) {
  930. delSetting({status ? F("rfbON") : F("rfbOFF"), id});
  931. // Websocket update needs to happen right here, since the only time
  932. // we send these in bulk is at the very start of the connection
  933. #if WEB_SUPPORT
  934. wsPost([id](JsonObject& root) {
  935. _rfbWebSocketSendCodeArray(root, id, 1ul);
  936. });
  937. #endif
  938. }
  939. #endif // RELAY_SUPPORT
  940. // -----------------------------------------------------------------------------
  941. // SETUP & LOOP
  942. // -----------------------------------------------------------------------------
  943. #if RELAY_SUPPORT && (RFB_PROVIDER == RFB_PROVIDER_RCSWITCH)
  944. // TODO: remove this in 1.16.0
  945. void _rfbSettingsMigrate(int version) {
  946. if (!version || (version > 4)) {
  947. return;
  948. }
  949. auto migrate_code = [](String& out, const String& in) -> bool {
  950. out = "";
  951. if (18 == in.length()) {
  952. uint8_t bits { 0u };
  953. if (!hexDecode(in.c_str() + 8, 2, &bits, 1)) {
  954. return false;
  955. }
  956. auto bytes = _rfb_bytes_for_bits(bits);
  957. out = in.substring(0, 10);
  958. out += (in.c_str() + in.length() - (2 * bytes));
  959. return in != out;
  960. }
  961. return false;
  962. };
  963. String buffer;
  964. auto relays = relayCount();
  965. for (decltype(relays) id = 0; id < relays; ++id) {
  966. SettingsKey on_key {F("rfbON"), id};
  967. if (migrate_code(buffer, getSetting(on_key))) {
  968. setSetting(on_key, buffer);
  969. }
  970. SettingsKey off_key {F("rfbOFF"), id};
  971. if (migrate_code(buffer, getSetting(off_key))) {
  972. setSetting(off_key, buffer);
  973. }
  974. }
  975. }
  976. #endif
  977. void rfbSetup() {
  978. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  979. _rfb_parser.reserve(RfbParser::MessageSizeBasic);
  980. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  981. #if RELAY_SUPPORT
  982. _rfbSettingsMigrate(migrateVersion());
  983. #endif
  984. {
  985. auto rx = getSetting("rfbRX", RFB_RX_PIN);
  986. auto tx = getSetting("rfbTX", RFB_TX_PIN);
  987. if ((GPIO_NONE == rx) && (GPIO_NONE == tx)) {
  988. DEBUG_MSG_P(PSTR("[RF] Neither RX or TX are set\n"));
  989. return;
  990. }
  991. _rfb_modem = new RCSwitch();
  992. if (gpioLock(rx)) {
  993. _rfb_receive = true;
  994. _rfb_modem->enableReceive(rx);
  995. DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), rx);
  996. }
  997. if (gpioLock(tx)) {
  998. auto transmit = getSetting("rfbTransmit", RFB_TRANSMIT_REPEATS);
  999. _rfb_transmit = true;
  1000. _rfb_modem->enableTransmit(tx);
  1001. _rfb_modem->setRepeatTransmit(transmit);
  1002. DEBUG_MSG_P(PSTR("[RF] RF transmitter on GPIO %u\n"), tx);
  1003. }
  1004. }
  1005. #endif
  1006. #if RELAY_SUPPORT
  1007. relaySetStatusNotify(rfbStatus);
  1008. relaySetStatusChange(rfbStatus);
  1009. #endif
  1010. #if MQTT_SUPPORT
  1011. mqttRegister(_rfbMqttCallback);
  1012. #endif
  1013. #if API_SUPPORT
  1014. _rfbApiSetup();
  1015. #endif
  1016. #if WEB_SUPPORT
  1017. wsRegister()
  1018. #if RELAY_SUPPORT
  1019. .onData(_rfbWebSocketOnData)
  1020. .onAction(_rfbWebSocketOnAction)
  1021. #endif
  1022. .onConnected(_rfbWebSocketOnConnected)
  1023. .onVisible(_rfbWebSocketOnVisible)
  1024. .onKeyCheck(_rfbWebSocketOnKeyCheck);
  1025. #endif
  1026. #if TERMINAL_SUPPORT
  1027. _rfbInitCommands();
  1028. #endif
  1029. _rfb_repeats = getSetting("rfbRepeat", RFB_SEND_REPEATS);
  1030. // Note: as rfbridge protocol is simplistic enough, we rely on Serial queue to deliver timely updates
  1031. // learn / command acks / etc. are not queued, only RF messages are
  1032. espurnaRegisterLoop([]() {
  1033. _rfbReceiveImpl();
  1034. _rfbSendQueued();
  1035. });
  1036. }
  1037. #endif // RFB_SUPPORT