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.

1324 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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...)
3 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
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
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
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(unsigned char id_, PayloadStatus status_) :
  39. id(id_),
  40. status(status_),
  41. _found(true)
  42. {}
  43. bool ok() {
  44. return _found;
  45. }
  46. void reset(unsigned char id_, PayloadStatus status_) {
  47. id = id_;
  48. status = status_;
  49. _found = true;
  50. }
  51. unsigned char id { 0u };
  52. PayloadStatus status { PayloadStatus::Unknown };
  53. private:
  54. bool _found { false };
  55. };
  56. struct RfbLearn {
  57. unsigned long ts;
  58. unsigned char id;
  59. bool status;
  60. };
  61. // Usage depends on the implementation. Will either:
  62. // - efm8bb1: wait until learn OK / TIMEOUT code
  63. // - rc-switch: receiver loop will check `ts` vs RFB_LEARN_TIMEOUT
  64. static std::unique_ptr<RfbLearn> _rfb_learn;
  65. // Individual lock for the relay, prevent rfbStatus from re-sending the code we just received
  66. static std::bitset<RelaysMax> _rfb_relay_status_lock;
  67. #endif // RELAY_SUPPORT
  68. // -----------------------------------------------------------------------------
  69. // EFM8BB1 PROTOCOL PARSING
  70. // -----------------------------------------------------------------------------
  71. constexpr uint8_t RfbDefaultProtocol { 0u };
  72. constexpr uint8_t CodeStart { 0xAAu };
  73. constexpr uint8_t CodeEnd { 0x55u };
  74. constexpr uint8_t CodeAck { 0xA0u };
  75. // both stock and https://github.com/Portisch/RF-Bridge-EFM8BB1/
  76. // sending:
  77. constexpr uint8_t CodeLearn { 0xA1u };
  78. // receiving:
  79. constexpr uint8_t CodeLearnTimeout { 0xA2u };
  80. constexpr uint8_t CodeLearnOk { 0xA3u };
  81. constexpr uint8_t CodeRecvBasic = { 0xA4u };
  82. constexpr uint8_t CodeSendBasic = { 0xA5u };
  83. // only https://github.com/Portisch/RF-Bridge-EFM8BB1/
  84. constexpr uint8_t CodeRecvProto { 0xA6u };
  85. constexpr uint8_t CodeRecvBucket { 0xB1u };
  86. struct RfbParser {
  87. using callback_type = void(uint8_t, const std::vector<uint8_t>&);
  88. using state_type = void(RfbParser::*)(uint8_t);
  89. // AA XX ... 55
  90. // ^~~~~ ~~ - protocol head + tail
  91. // ^~ - message code
  92. // ^~~ - actual payload is always 9 bytes
  93. static constexpr size_t PayloadSizeBasic { 9ul };
  94. static constexpr size_t MessageSizeBasic { PayloadSizeBasic + 3ul };
  95. static constexpr size_t MessageSizeMax { 112ul };
  96. RfbParser() = delete;
  97. RfbParser(const RfbParser&) = delete;
  98. explicit RfbParser(callback_type* callback) :
  99. _callback(callback)
  100. {}
  101. RfbParser(RfbParser&&) = default;
  102. void stop(uint8_t c) {
  103. }
  104. void start(uint8_t c) {
  105. switch (c) {
  106. case CodeStart:
  107. _state = &RfbParser::read_code;
  108. break;
  109. default:
  110. _state = &RfbParser::stop;
  111. break;
  112. }
  113. }
  114. void read_code(uint8_t c) {
  115. _payload_code = c;
  116. switch (c) {
  117. // Generic ACK signal. We *expect* this after our requests
  118. case CodeAck:
  119. // *Expect* any code within a certain window.
  120. // Only matters to us, does not really do anything but help us to signal that the next code needs to be recorded
  121. case CodeLearnTimeout:
  122. _state = &RfbParser::read_end;
  123. break;
  124. // both stock and https://github.com/Portisch/RF-Bridge-EFM8BB1/
  125. // receive 9 bytes, where first 3 2-byte tuples are timings
  126. // and the last 3 bytes are the actual payload
  127. case CodeLearnOk:
  128. case CodeRecvBasic:
  129. _payload_length = PayloadSizeBasic;
  130. _state = &RfbParser::read_until_length;
  131. break;
  132. // specific to the https://github.com/Portisch/RF-Bridge-EFM8BB1/
  133. // receive N bytes, where the 1st byte is the protocol ID and the next N-1 bytes are the payload
  134. case CodeRecvProto:
  135. _state = &RfbParser::read_length;
  136. break;
  137. // unlike CodeRecvProto, we don't have any length byte here :/ for some reason, it is there only when sending
  138. // just bail out when we find CodeEnd
  139. // (TODO: is number of buckets somehow convertible to the 'expected' size?)
  140. case CodeRecvBucket:
  141. _state = &RfbParser::read_length;
  142. break;
  143. default:
  144. _state = &RfbParser::stop;
  145. break;
  146. }
  147. }
  148. void read_end(uint8_t c) {
  149. if (CodeEnd == c) {
  150. _callback(_payload_code, _payload);
  151. }
  152. _state = &RfbParser::stop;
  153. }
  154. void read_until_end(uint8_t c) {
  155. if (CodeEnd == c) {
  156. read_end(c);
  157. return;
  158. }
  159. _payload.push_back(c);
  160. }
  161. void read_until_length(uint8_t c) {
  162. _payload.push_back(c);
  163. if ((_payload_offset + _payload_length) == _payload.size()) {
  164. switch (_payload_code) {
  165. case CodeLearnOk:
  166. case CodeRecvBasic:
  167. case CodeRecvProto:
  168. _state = &RfbParser::read_end;
  169. break;
  170. case CodeRecvBucket:
  171. _state = &RfbParser::read_until_end;
  172. break;
  173. default:
  174. _state = &RfbParser::stop;
  175. break;
  176. }
  177. _payload_length = 0u;
  178. }
  179. }
  180. void read_length(uint8_t c) {
  181. switch (_payload_code) {
  182. case CodeRecvProto:
  183. _payload_length = c;
  184. break;
  185. case CodeRecvBucket:
  186. _payload_length = c * 2;
  187. break;
  188. default:
  189. _state = &RfbParser::stop;
  190. return;
  191. }
  192. _payload.push_back(c);
  193. _payload_offset = _payload.size();
  194. _state = &RfbParser::read_until_length;
  195. }
  196. bool loop(uint8_t c) {
  197. (this->*_state)(c);
  198. return (_state != &RfbParser::stop);
  199. }
  200. void reset() {
  201. _payload.clear();
  202. _payload_length = 0u;
  203. _payload_offset = 0u;
  204. _payload_code = 0u;
  205. _state = &RfbParser::start;
  206. }
  207. void reserve(size_t size) {
  208. _payload.reserve(size);
  209. }
  210. private:
  211. callback_type* _callback { nullptr };
  212. state_type _state { &RfbParser::start };
  213. std::vector<uint8_t> _payload;
  214. size_t _payload_length { 0ul };
  215. size_t _payload_offset { 0ul };
  216. uint8_t _payload_code { 0ul };
  217. };
  218. // -----------------------------------------------------------------------------
  219. // MESSAGE SENDER
  220. //
  221. // Depends on the selected provider. While we do serialize RCSwitch results,
  222. // we don't want to pass around such byte-array everywhere since we already
  223. // know all of the required data members and can prepare a basic POD struct
  224. // -----------------------------------------------------------------------------
  225. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  226. struct RfbMessage {
  227. RfbMessage(const RfbMessage&) = default;
  228. RfbMessage(RfbMessage&&) = default;
  229. explicit RfbMessage(uint8_t (&data)[RfbParser::PayloadSizeBasic], unsigned char repeats_) :
  230. repeats(repeats_)
  231. {
  232. std::copy(data, data + sizeof(data), code);
  233. }
  234. uint8_t code[RfbParser::PayloadSizeBasic] { 0u };
  235. uint8_t repeats { 1u };
  236. };
  237. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  238. struct RfbMessage {
  239. using code_type = decltype(std::declval<RCSwitch>().getReceivedValue());
  240. static constexpr size_t BufferSize = sizeof(code_type) + 5;
  241. uint8_t protocol;
  242. uint16_t timing;
  243. uint8_t bits;
  244. code_type code;
  245. uint8_t repeats;
  246. };
  247. #endif // RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  248. static std::list<RfbMessage> _rfb_message_queue;
  249. void _rfbLearnImpl();
  250. void _rfbReceiveImpl();
  251. void _rfbSendImpl(const RfbMessage& message);
  252. // -----------------------------------------------------------------------------
  253. // WEBUI INTEGRATION
  254. // -----------------------------------------------------------------------------
  255. #if WEB_SUPPORT
  256. void _rfbWebSocketOnVisible(JsonObject& root) {
  257. root["rfbVisible"] = 1;
  258. }
  259. #if RELAY_SUPPORT
  260. void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned char size) {
  261. JsonObject& rfb = root.createNestedObject("rfb");
  262. rfb["size"] = size;
  263. rfb["start"] = start;
  264. JsonArray& on = rfb.createNestedArray("on");
  265. JsonArray& off = rfb.createNestedArray("off");
  266. for (uint8_t id=start; id<start+size; id++) {
  267. on.add(rfbRetrieve(id, true));
  268. off.add(rfbRetrieve(id, false));
  269. }
  270. }
  271. void _rfbWebSocketOnData(JsonObject& root) {
  272. _rfbWebSocketSendCodeArray(root, 0, relayCount());
  273. }
  274. #endif // RELAY_SUPPORT
  275. void _rfbWebSocketOnConnected(JsonObject& root) {
  276. root["rfbRepeat"] = getSetting("rfbRepeat", RFB_SEND_REPEATS);
  277. #if RELAY_SUPPORT
  278. root["rfbCount"] = relayCount();
  279. #endif
  280. #if RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  281. root["rfbdirectVisible"] = 1;
  282. root["rfbRX"] = getSetting("rfbRX", RFB_RX_PIN);
  283. root["rfbTX"] = getSetting("rfbTX", RFB_TX_PIN);
  284. #endif
  285. }
  286. void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  287. #if RELAY_SUPPORT
  288. if (strcmp(action, "rfblearn") == 0) rfbLearn(data["id"], data["status"]);
  289. if (strcmp(action, "rfbforget") == 0) rfbForget(data["id"], data["status"]);
  290. if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
  291. #endif
  292. }
  293. bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  294. return (strncmp(key, "rfb", 3) == 0);
  295. }
  296. #endif // WEB_SUPPORT
  297. // -----------------------------------------------------------------------------
  298. // RELAY <-> CODE MATCHING
  299. // -----------------------------------------------------------------------------
  300. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  301. // we only care about last 6 chars (3 bytes in hex),
  302. // since in 'default' mode rfbridge only handles a single protocol
  303. bool _rfbCompare(const char* lhs, const char* rhs, size_t length) {
  304. return (0 == std::memcmp((lhs + length - 6), (rhs + length - 6), 6));
  305. }
  306. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  307. // protocol is [2:3), actual payload is [10:), as bit length may vary
  308. // although, we don't care if it does, since we expect length of both args to be the same
  309. bool _rfbCompare(const char* lhs, const char* rhs, size_t length) {
  310. return (0 == std::memcmp((lhs + 2), (rhs + 2), 2))
  311. && (0 == std::memcmp((lhs + 10), (rhs + 10), length - 10));
  312. }
  313. #endif // RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  314. #if RELAY_SUPPORT
  315. // try to find the 'code' saves as either rfbON# or rfbOFF#
  316. //
  317. // **always** expect full length code as input to simplify comparison
  318. // previous implementation tried to help MQTT / API requests to match based on the saved code,
  319. // thus requiring us to 'return' value from settings as the real code, replacing input
  320. RfbRelayMatch _rfbMatch(const char* code) {
  321. if (!relayCount()) {
  322. return {};
  323. }
  324. const auto len = strlen(code);
  325. // we gather all available options, as the kv store might be defined in any order
  326. // scan kvs only once, since we want both ON and OFF options and don't want to depend on the relayCount()
  327. RfbRelayMatch matched;
  328. using namespace settings;
  329. kv_store.foreach([code, len, &matched](kvs_type::KeyValueResult&& kv) {
  330. const auto key = kv.key.read();
  331. PayloadStatus status = key.startsWith(F("rfbON"))
  332. ? PayloadStatus::On : key.startsWith(F("rfbOFF"))
  333. ? PayloadStatus::Off : PayloadStatus::Unknown;
  334. if (PayloadStatus::Unknown == status) {
  335. return;
  336. }
  337. const auto value = kv.value.read();
  338. if (len != value.length()) {
  339. return;
  340. }
  341. if (!_rfbCompare(code, value.c_str(), len)) {
  342. return;
  343. }
  344. // note: strlen is constexpr here
  345. const char* id_ptr = key.c_str() + (
  346. (PayloadStatus::On == status) ? strlen("rfbON") : strlen("rfbOFF"));
  347. if (*id_ptr == '\0') {
  348. return;
  349. }
  350. char *endptr = nullptr;
  351. const auto id = strtoul(id_ptr, &endptr, 10);
  352. if (endptr == id_ptr || endptr[0] != '\0' || id > std::numeric_limits<uint8_t>::max() || id >= relayCount()) {
  353. return;
  354. }
  355. // when we see the same id twice, we match the opposite statuses
  356. if (matched.ok() && (id == matched.id)) {
  357. matched.status = PayloadStatus::Toggle;
  358. return;
  359. }
  360. matched.reset(matched.ok()
  361. ? std::min(static_cast<uint8_t>(id), matched.id)
  362. : static_cast<uint8_t>(id),
  363. status
  364. );
  365. });
  366. return matched;
  367. }
  368. void _rfbLearnFromString(std::unique_ptr<RfbLearn>& learn, const char* buffer) {
  369. if (!learn) return;
  370. DEBUG_MSG_P(PSTR("[RF] Learned relay ID %u after %u ms\n"), learn->id, millis() - learn->ts);
  371. rfbStore(learn->id, learn->status, buffer);
  372. // Websocket update needs to happen right here, since the only time
  373. // we send these in bulk is at the very start of the connection
  374. #if WEB_SUPPORT
  375. auto id = learn->id;
  376. wsPost([id](JsonObject& root) {
  377. _rfbWebSocketSendCodeArray(root, id, 1);
  378. });
  379. #endif
  380. learn.reset(nullptr);
  381. }
  382. bool _rfbRelayHandler(const char* buffer, bool locked = false) {
  383. bool result { false };
  384. auto match = _rfbMatch(buffer);
  385. if (match.ok()) {
  386. DEBUG_MSG_P(PSTR("[RF] Matched with the relay ID %u\n"), match.id);
  387. _rfb_relay_status_lock.set(match.id, locked);
  388. switch (match.status) {
  389. case PayloadStatus::On:
  390. case PayloadStatus::Off:
  391. relayStatus(match.id, (PayloadStatus::On == match.status));
  392. result = true;
  393. break;
  394. case PayloadStatus::Toggle:
  395. relayToggle(match.id);
  396. result = true;
  397. case PayloadStatus::Unknown:
  398. break;
  399. }
  400. }
  401. return result;
  402. }
  403. void _rfbLearnStartFromPayload(const char* payload) {
  404. // The payload must be the `relayID,mode` (where mode is either 0 or 1)
  405. const char* sep = strchr(payload, ',');
  406. if (nullptr == sep) {
  407. return;
  408. }
  409. // ref. RelaysMax, we only have up to 2 digits
  410. char relay[3] {0, 0, 0};
  411. if ((sep - payload) > 2) {
  412. return;
  413. }
  414. std::copy(payload, sep, relay);
  415. char *endptr = nullptr;
  416. const auto id = strtoul(relay, &endptr, 10);
  417. if (endptr == &relay[0] || endptr[0] != '\0') {
  418. return;
  419. }
  420. if (id >= relayCount()) {
  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, unsigned char 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. #if BROKER_SUPPORT
  512. RfbridgeBroker::Publish(RfbDefaultProtocol, buffer + 12);
  513. #endif
  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. #if BROKER_SUPPORT
  529. // ref. https://github.com/Portisch/RF-Bridge-EFM8BB1/wiki/0xA6#example-of-a-received-decoded-protocol
  530. RfbridgeBroker::Publish(payload[0], buffer + 2);
  531. #endif
  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. #if BROKER_SUPPORT
  694. RfbridgeBroker::Publish(message[1], buffer + 10);
  695. #endif
  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 _rfbInitCommands() {
  838. terminalRegisterCommand(F("RFB.SEND"), [](const terminal::CommandContext& ctx) {
  839. if (ctx.argc == 2) {
  840. rfbSend(ctx.argv[1]);
  841. return;
  842. }
  843. terminalError(ctx, F("RFB.SEND <CODE>"));
  844. });
  845. #if RELAY_SUPPORT
  846. terminalRegisterCommand(F("RFB.LEARN"), [](const terminal::CommandContext& ctx) {
  847. if (ctx.argc != 3) {
  848. terminalError(ctx, F("RFB.LEARN <ID> <STATUS>"));
  849. return;
  850. }
  851. int id = ctx.argv[1].toInt();
  852. if (id >= relayCount()) {
  853. terminalError(ctx, F("Invalid relay ID"));
  854. return;
  855. }
  856. rfbLearn(id, (ctx.argv[2].toInt()) == 1);
  857. terminalOK(ctx);
  858. });
  859. terminalRegisterCommand(F("RFB.FORGET"), [](const terminal::CommandContext& ctx) {
  860. if (ctx.argc < 2) {
  861. terminalError(ctx, F("RFB.FORGET <ID> [<STATUS>]"));
  862. return;
  863. }
  864. int id = ctx.argv[1].toInt();
  865. if (id >= relayCount()) {
  866. terminalError(ctx, F("Invalid relay ID"));
  867. return;
  868. }
  869. if (ctx.argc == 3) {
  870. rfbForget(id, (ctx.argv[2].toInt()) == 1);
  871. } else {
  872. rfbForget(id, true);
  873. rfbForget(id, false);
  874. }
  875. terminalOK(ctx);
  876. });
  877. #endif // if RELAY_SUPPORT
  878. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  879. terminalRegisterCommand(F("RFB.WRITE"), [](const terminal::CommandContext& ctx) {
  880. if (ctx.argc != 2) {
  881. terminalError(ctx, F("RFB.WRITE <PAYLOAD>"));
  882. return;
  883. }
  884. _rfbSendRawFromPayload(ctx.argv[1].c_str());
  885. terminalOK(ctx);
  886. });
  887. #endif
  888. }
  889. #endif // TERMINAL_SUPPORT
  890. // -----------------------------------------------------------------------------
  891. // PUBLIC
  892. // -----------------------------------------------------------------------------
  893. void rfbStore(unsigned char id, bool status, const char * code) {
  894. SettingsKey key { status ? F("rfbON") : F("rfbOFF"), id };
  895. setSetting(key, code);
  896. DEBUG_MSG_P(PSTR("[RF] Saved %s => \"%s\"\n"), key.toString().c_str(), code);
  897. }
  898. String rfbRetrieve(unsigned char id, bool status) {
  899. return getSetting({ status ? F("rfbON") : F("rfbOFF"), id });
  900. }
  901. #if RELAY_SUPPORT
  902. void rfbStatus(unsigned char id, bool status) {
  903. // TODO: This is a left-over from the old implementation. Right now we set this lock when relay handler
  904. // is called within the receiver, while this is called from either relayStatus or relay loop calling
  905. // this via provider callback. This prevents us from re-sending the code we just received.
  906. // TODO: Consider having 'origin' of the relay change. Either supply relayStatus with an additional arg,
  907. // or track these statuses directly.
  908. if (!_rfb_relay_status_lock[id]) {
  909. rfbSend(rfbRetrieve(id, status));
  910. }
  911. _rfb_relay_status_lock[id] = false;
  912. }
  913. void rfbLearn(unsigned char id, bool status) {
  914. _rfb_learn.reset(new RfbLearn { millis(), id, status });
  915. _rfbLearnImpl();
  916. }
  917. void rfbForget(unsigned char id, bool status) {
  918. delSetting({status ? F("rfbON") : F("rfbOFF"), id});
  919. // Websocket update needs to happen right here, since the only time
  920. // we send these in bulk is at the very start of the connection
  921. #if WEB_SUPPORT
  922. wsPost([id](JsonObject& root) {
  923. _rfbWebSocketSendCodeArray(root, id, 1);
  924. });
  925. #endif
  926. }
  927. #endif // RELAY_SUPPORT
  928. // -----------------------------------------------------------------------------
  929. // SETUP & LOOP
  930. // -----------------------------------------------------------------------------
  931. #if RELAY_SUPPORT && (RFB_PROVIDER == RFB_PROVIDER_RCSWITCH)
  932. // TODO: remove this in 1.16.0
  933. void _rfbSettingsMigrate(int version) {
  934. if (!version || (version > 4)) {
  935. return;
  936. }
  937. auto migrate_code = [](String& out, const String& in) -> bool {
  938. out = "";
  939. if (18 == in.length()) {
  940. uint8_t bits { 0u };
  941. if (!hexDecode(in.c_str() + 8, 2, &bits, 1)) {
  942. return false;
  943. }
  944. auto bytes = _rfb_bytes_for_bits(bits);
  945. out = in.substring(0, 10);
  946. out += (in.c_str() + in.length() - (2 * bytes));
  947. return in != out;
  948. }
  949. return false;
  950. };
  951. String buffer;
  952. for (unsigned char index = 0; index < relayCount(); ++index) {
  953. SettingsKey on_key {F("rfbON"), index};
  954. if (migrate_code(buffer, getSetting(on_key))) {
  955. setSetting(on_key, buffer);
  956. }
  957. SettingsKey off_key {F("rfbOFF"), index};
  958. if (migrate_code(buffer, getSetting(off_key))) {
  959. setSetting(off_key, buffer);
  960. }
  961. }
  962. }
  963. #endif
  964. void rfbSetup() {
  965. #if RFB_PROVIDER == RFB_PROVIDER_EFM8BB1
  966. _rfb_parser.reserve(RfbParser::MessageSizeBasic);
  967. #elif RFB_PROVIDER == RFB_PROVIDER_RCSWITCH
  968. #if RELAY_SUPPORT
  969. _rfbSettingsMigrate(migrateVersion());
  970. #endif
  971. {
  972. auto rx = getSetting("rfbRX", RFB_RX_PIN);
  973. auto tx = getSetting("rfbTX", RFB_TX_PIN);
  974. if ((GPIO_NONE == rx) && (GPIO_NONE == tx)) {
  975. DEBUG_MSG_P(PSTR("[RF] Neither RX or TX are set\n"));
  976. return;
  977. }
  978. _rfb_modem = new RCSwitch();
  979. if (gpioLock(rx)) {
  980. _rfb_receive = true;
  981. _rfb_modem->enableReceive(rx);
  982. DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), rx);
  983. }
  984. if (gpioLock(tx)) {
  985. auto transmit = getSetting("rfbTransmit", RFB_TRANSMIT_REPEATS);
  986. _rfb_transmit = true;
  987. _rfb_modem->enableTransmit(tx);
  988. _rfb_modem->setRepeatTransmit(transmit);
  989. DEBUG_MSG_P(PSTR("[RF] RF transmitter on GPIO %u\n"), tx);
  990. }
  991. }
  992. #endif
  993. #if RELAY_SUPPORT
  994. relaySetStatusNotify(rfbStatus);
  995. relaySetStatusChange(rfbStatus);
  996. #endif
  997. #if MQTT_SUPPORT
  998. mqttRegister(_rfbMqttCallback);
  999. #endif
  1000. #if API_SUPPORT
  1001. _rfbApiSetup();
  1002. #endif
  1003. #if WEB_SUPPORT
  1004. wsRegister()
  1005. #if RELAY_SUPPORT
  1006. .onData(_rfbWebSocketOnData)
  1007. .onAction(_rfbWebSocketOnAction)
  1008. #endif
  1009. .onConnected(_rfbWebSocketOnConnected)
  1010. .onVisible(_rfbWebSocketOnVisible)
  1011. .onKeyCheck(_rfbWebSocketOnKeyCheck);
  1012. #endif
  1013. #if TERMINAL_SUPPORT
  1014. _rfbInitCommands();
  1015. #endif
  1016. _rfb_repeats = getSetting("rfbRepeat", RFB_SEND_REPEATS);
  1017. // Note: as rfbridge protocol is simplistic enough, we rely on Serial queue to deliver timely updates
  1018. // learn / command acks / etc. are not queued, only RF messages are
  1019. espurnaRegisterLoop([]() {
  1020. _rfbReceiveImpl();
  1021. _rfbSendQueued();
  1022. });
  1023. }
  1024. #endif // RFB_SUPPORT