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.

235 lines
5.7 KiB

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
6 years ago
6 years ago
  1. /*
  2. ALEXA MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "alexa.h"
  6. #if ALEXA_SUPPORT
  7. #include <queue>
  8. #include "api.h"
  9. #include "light.h"
  10. #include "mqtt.h"
  11. #include "relay.h"
  12. #include "rpc.h"
  13. #include "web.h"
  14. #include "ws.h"
  15. #include <fauxmoESP.h>
  16. #include <ArduinoJson.h>
  17. namespace {
  18. struct AlexaEvent {
  19. AlexaEvent() = delete;
  20. AlexaEvent(unsigned char id, bool state, unsigned char value) :
  21. _id(id),
  22. _state(state),
  23. _value(value)
  24. {}
  25. unsigned char id() const {
  26. return _id;
  27. }
  28. unsigned char value() const {
  29. return _value;
  30. }
  31. bool state() const {
  32. return _state;
  33. }
  34. private:
  35. unsigned char _id;
  36. bool _state;
  37. unsigned char _value;
  38. };
  39. std::queue<AlexaEvent> _alexa_events;
  40. fauxmoESP _alexa;
  41. } // namespace
  42. // -----------------------------------------------------------------------------
  43. // ALEXA
  44. // -----------------------------------------------------------------------------
  45. bool _alexaWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  46. return (strncmp(key, "alexa", 5) == 0);
  47. }
  48. void _alexaWebSocketOnConnected(JsonObject& root) {
  49. root["alexaEnabled"] = alexaEnabled();
  50. root["alexaName"] = getSetting("alexaName");
  51. }
  52. void _alexaConfigure() {
  53. _alexa.enable(wifiConnected() && alexaEnabled());
  54. }
  55. #if WEB_SUPPORT
  56. bool _alexaBodyCallback(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
  57. return _alexa.process(request->client(), request->method() == HTTP_GET, request->url(), String((char *)data));
  58. }
  59. bool _alexaRequestCallback(AsyncWebServerRequest *request) {
  60. String body = (request->hasParam("body", true)) ? request->getParam("body", true)->value() : String();
  61. return _alexa.process(request->client(), request->method() == HTTP_GET, request->url(), body);
  62. }
  63. #endif
  64. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  65. void _alexaUpdateLights() {
  66. _alexa.setState(static_cast<unsigned char>(0u), lightState(), lightState() ? 255u : 0u);
  67. auto channels = lightChannels();
  68. for (decltype(channels) channel = 0; channel < channels; ++channel) {
  69. auto value = lightChannel(channel);
  70. _alexa.setState(channel + 1, value > 0, value);
  71. }
  72. }
  73. #endif
  74. #if RELAY_SUPPORT
  75. void _alexaUpdateRelay(size_t id, bool status) {
  76. _alexa.setState(id, status, status ? 255 : 0);
  77. }
  78. #endif
  79. // -----------------------------------------------------------------------------
  80. bool alexaEnabled() {
  81. return getSetting("alexaEnabled", 1 == ALEXA_ENABLED);
  82. }
  83. void alexaLoop() {
  84. _alexa.handle();
  85. while (!_alexa_events.empty()) {
  86. auto& event = _alexa_events.front();
  87. DEBUG_MSG_P(PSTR("[ALEXA] Device #%hhu state=#%s value=%hhu\n"),
  88. event.id(), event.state() ? 't' : 'f', event.value());
  89. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  90. if (0 == event.id()) {
  91. lightState(event.state());
  92. } else {
  93. lightState(event.id() - 1, event.state());
  94. lightChannel(event.id() - 1, event.value());
  95. lightUpdate();
  96. }
  97. #else
  98. relayStatus(event.id(), event.state());
  99. #endif
  100. _alexa_events.pop();
  101. }
  102. }
  103. constexpr bool _alexaCreateServer() {
  104. return !WEB_SUPPORT;
  105. }
  106. constexpr const char* _alexaHostname() {
  107. return ALEXA_HOSTNAME;
  108. }
  109. void _alexaSettingsMigrate(int version) {
  110. if (version && (version < 3)) {
  111. moveSetting("fauxmoEnabled", "alexaEnabled");
  112. }
  113. }
  114. void alexaSetup() {
  115. // Backwards compatibility
  116. _alexaSettingsMigrate(migrateVersion());
  117. // Basic fauxmoESP configuration
  118. _alexa.createServer(_alexaCreateServer());
  119. _alexa.setPort(80);
  120. // Use custom alexa hostname if defined, device hostname otherwise
  121. String hostname = getSetting("alexaName", _alexaHostname());
  122. if (!hostname.length()) {
  123. hostname = getSetting("hostname", getIdentifier());
  124. }
  125. auto deviceName = [&](size_t index) {
  126. auto name = hostname;
  127. name += ' ';
  128. name += index;
  129. return name;
  130. };
  131. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  132. // 1st is the global state, the rest are mapped to channel values
  133. _alexa.addDevice(hostname.c_str());
  134. for (size_t channel = 1; channel <= lightChannels(); ++channel) {
  135. _alexa.addDevice(deviceName(channel).c_str());
  136. }
  137. // Relays are mapped 1-to-1
  138. #elif RELAY_SUPPORT
  139. auto relays = relayCount();
  140. if (relays > 1) {
  141. for (decltype(relays) id = 1; id <= relays; ++id) {
  142. _alexa.addDevice(deviceName(id).c_str());
  143. }
  144. } else {
  145. _alexa.addDevice(hostname.c_str());
  146. }
  147. #endif
  148. // Load & cache settings
  149. _alexaConfigure();
  150. // Websockets
  151. #if WEB_SUPPORT
  152. webBodyRegister(_alexaBodyCallback);
  153. webRequestRegister(_alexaRequestCallback);
  154. wsRegister()
  155. .onVisible([](JsonObject& root) { root["alexaVisible"] = 1; })
  156. .onConnected(_alexaWebSocketOnConnected)
  157. .onKeyCheck(_alexaWebSocketOnKeyCheck);
  158. #endif
  159. // Register wifi callback
  160. wifiRegister([](wifi::Event event) {
  161. if ((event == wifi::Event::StationConnected)
  162. || (event == wifi::Event::StationDisconnected)) {
  163. _alexaConfigure();
  164. }
  165. });
  166. // Callback
  167. _alexa.onSetState([&](unsigned char device_id, const char*, bool state, unsigned char value) {
  168. _alexa_events.emplace(device_id, state, value);
  169. });
  170. // Register main callbacks
  171. #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
  172. lightSetReportListener(_alexaUpdateLights);
  173. #else
  174. relaySetStatusChange(_alexaUpdateRelay);
  175. #endif
  176. espurnaRegisterReload(_alexaConfigure);
  177. espurnaRegisterLoop(alexaLoop);
  178. }
  179. #endif