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.

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