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.

809 lines
22 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /*
  2. WEBSOCKET MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #if WEB_SUPPORT
  6. #include <ESPAsyncTCP.h>
  7. #include <ESPAsyncWebServer.h>
  8. #include <ArduinoJson.h>
  9. #include <Ticker.h>
  10. #include <vector>
  11. #include "system.h"
  12. #include "libs/WebSocketIncommingBuffer.h"
  13. AsyncWebSocket _ws("/ws");
  14. Ticker _ws_defer;
  15. uint32_t _ws_last_update = 0;
  16. // -----------------------------------------------------------------------------
  17. // WS callbacks
  18. // -----------------------------------------------------------------------------
  19. ws_callbacks_t& ws_callbacks_t::onVisible(ws_on_send_callback_f cb) {
  20. on_visible.push_back(cb);
  21. return *this;
  22. }
  23. ws_callbacks_t& ws_callbacks_t::onConnected(ws_on_send_callback_f cb) {
  24. on_connected.push_back(cb);
  25. return *this;
  26. }
  27. ws_callbacks_t& ws_callbacks_t::onData(ws_on_send_callback_f cb) {
  28. on_data.push_back(cb);
  29. return *this;
  30. }
  31. ws_callbacks_t& ws_callbacks_t::onAction(ws_on_action_callback_f cb) {
  32. on_action.push_back(cb);
  33. return *this;
  34. }
  35. ws_callbacks_t& ws_callbacks_t::onKeyCheck(ws_on_keycheck_callback_f cb) {
  36. on_keycheck.push_back(cb);
  37. return *this;
  38. }
  39. ws_callbacks_t _ws_callbacks;
  40. struct ws_counter_t {
  41. ws_counter_t() : current(0), start(0), stop(0) {}
  42. ws_counter_t(uint32_t start, uint32_t stop) :
  43. current(start), start(start), stop(stop) {}
  44. void reset() {
  45. current = start;
  46. }
  47. void next() {
  48. if (current < stop) {
  49. ++current;
  50. }
  51. }
  52. bool done() {
  53. return (current >= stop);
  54. }
  55. uint32_t current;
  56. uint32_t start;
  57. uint32_t stop;
  58. };
  59. struct ws_data_t {
  60. enum mode_t {
  61. SEQUENCE,
  62. ALL
  63. };
  64. ws_data_t(const ws_on_send_callback_f& cb) :
  65. storage(new ws_on_send_callback_list_t {cb}),
  66. client_id(0),
  67. mode(ALL),
  68. callbacks(*storage.get()),
  69. counter(0, 1)
  70. {}
  71. ws_data_t(uint32_t client_id, const ws_on_send_callback_f& cb) :
  72. storage(new ws_on_send_callback_list_t {cb}),
  73. client_id(client_id),
  74. mode(ALL),
  75. callbacks(*storage.get()),
  76. counter(0, 1)
  77. {}
  78. ws_data_t(const uint32_t client_id, ws_on_send_callback_list_t&& callbacks, mode_t mode = SEQUENCE) :
  79. storage(new ws_on_send_callback_list_t(std::move(callbacks))),
  80. client_id(client_id),
  81. mode(mode),
  82. callbacks(*storage.get()),
  83. counter(0, (storage.get())->size())
  84. {}
  85. ws_data_t(const uint32_t client_id, const ws_on_send_callback_list_t& callbacks, mode_t mode = SEQUENCE) :
  86. client_id(client_id),
  87. mode(mode),
  88. callbacks(callbacks),
  89. counter(0, callbacks.size())
  90. {}
  91. bool done() {
  92. return counter.done();
  93. }
  94. void sendAll(JsonObject& root) {
  95. while (!counter.done()) counter.next();
  96. for (auto& callback : callbacks) {
  97. callback(root);
  98. }
  99. }
  100. void sendCurrent(JsonObject& root) {
  101. callbacks[counter.current](root);
  102. counter.next();
  103. }
  104. void send(JsonObject& root) {
  105. switch (mode) {
  106. case SEQUENCE: sendCurrent(root); break;
  107. case ALL: sendAll(root); break;
  108. }
  109. }
  110. std::unique_ptr<ws_on_send_callback_list_t> storage;
  111. const uint32_t client_id;
  112. const mode_t mode;
  113. const ws_on_send_callback_list_t& callbacks;
  114. ws_counter_t counter;
  115. };
  116. std::queue<ws_data_t> _ws_client_data;
  117. // -----------------------------------------------------------------------------
  118. // WS authentication
  119. // -----------------------------------------------------------------------------
  120. struct ws_ticket_t {
  121. IPAddress ip;
  122. unsigned long timestamp = 0;
  123. };
  124. ws_ticket_t _ws_tickets[WS_BUFFER_SIZE];
  125. void _onAuth(AsyncWebServerRequest *request) {
  126. webLog(request);
  127. if (!webAuthenticate(request)) return request->requestAuthentication();
  128. IPAddress ip = request->client()->remoteIP();
  129. unsigned long now = millis();
  130. unsigned short index;
  131. for (index = 0; index < WS_BUFFER_SIZE; index++) {
  132. if (_ws_tickets[index].ip == ip) break;
  133. if (_ws_tickets[index].timestamp == 0) break;
  134. if (now - _ws_tickets[index].timestamp > WS_TIMEOUT) break;
  135. }
  136. if (index == WS_BUFFER_SIZE) {
  137. request->send(429);
  138. } else {
  139. _ws_tickets[index].ip = ip;
  140. _ws_tickets[index].timestamp = now;
  141. request->send(200, "text/plain", "OK");
  142. }
  143. }
  144. bool _wsAuth(AsyncWebSocketClient * client) {
  145. IPAddress ip = client->remoteIP();
  146. unsigned long now = millis();
  147. unsigned short index = 0;
  148. for (index = 0; index < WS_BUFFER_SIZE; index++) {
  149. if ((_ws_tickets[index].ip == ip) && (now - _ws_tickets[index].timestamp < WS_TIMEOUT)) break;
  150. }
  151. if (index == WS_BUFFER_SIZE) {
  152. return false;
  153. }
  154. return true;
  155. }
  156. // -----------------------------------------------------------------------------
  157. // Debug
  158. // -----------------------------------------------------------------------------
  159. #if DEBUG_WEB_SUPPORT
  160. using ws_debug_msg_t = std::pair<String, String>;
  161. struct ws_debug_t {
  162. ws_debug_t(size_t capacity) :
  163. flush(false),
  164. current(0),
  165. capacity(capacity)
  166. {
  167. messages.reserve(capacity);
  168. }
  169. void clear() {
  170. messages.clear();
  171. current = 0;
  172. flush = false;
  173. }
  174. void add(const char* prefix, const char* message) {
  175. if (current >= capacity) {
  176. flush = true;
  177. send(wsConnected());
  178. }
  179. messages.emplace(messages.begin() + current, prefix, message);
  180. flush = true;
  181. ++current;
  182. }
  183. void send(const bool connected) {
  184. if (!connected && flush) {
  185. clear();
  186. return;
  187. }
  188. if (!flush) return;
  189. // ref: http://arduinojson.org/v5/assistant/
  190. // {"weblog": {"msg":[...],"pre":[...]}}
  191. DynamicJsonBuffer jsonBuffer(2*JSON_ARRAY_SIZE(messages.size()) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2));
  192. JsonObject& root = jsonBuffer.createObject();
  193. JsonObject& weblog = root.createNestedObject("weblog");
  194. JsonArray& msg = weblog.createNestedArray("msg");
  195. JsonArray& pre = weblog.createNestedArray("pre");
  196. for (auto& message : messages) {
  197. pre.add(message.first.c_str());
  198. msg.add(message.second.c_str());
  199. }
  200. wsSend(root);
  201. clear();
  202. }
  203. bool flush;
  204. size_t current;
  205. const size_t capacity;
  206. std::vector<ws_debug_msg_t> messages;
  207. };
  208. // TODO: move to the headers?
  209. constexpr const size_t WS_DEBUG_MSG_BUFFER = 8;
  210. ws_debug_t _ws_debug(WS_DEBUG_MSG_BUFFER);
  211. bool wsDebugSend(const char* prefix, const char* message) {
  212. if (!wsConnected()) return false;
  213. _ws_debug.add(prefix, message);
  214. return true;
  215. }
  216. #endif
  217. // Check the existing setting before saving it
  218. // TODO: this should know of the default values, somehow?
  219. // TODO: move webPort handling somewhere else?
  220. bool _wsStore(const String& key, const String& value) {
  221. if (key == "webPort") {
  222. if ((value.toInt() == 0) || (value.toInt() == 80)) {
  223. return delSetting(key);
  224. }
  225. }
  226. if (value != getSetting(key)) {
  227. return setSetting(key, value);
  228. }
  229. return false;
  230. }
  231. // -----------------------------------------------------------------------------
  232. // Store indexed key (key0, key1, etc.) from array
  233. // -----------------------------------------------------------------------------
  234. bool _wsStore(const String& key, JsonArray& value) {
  235. bool changed = false;
  236. unsigned char index = 0;
  237. for (auto element : value) {
  238. if (_wsStore(key + index, element.as<String>())) changed = true;
  239. index++;
  240. }
  241. // Delete further values
  242. for (unsigned char i=index; i<SETTINGS_MAX_LIST_COUNT; i++) {
  243. if (!delSetting(key, index)) break;
  244. changed = true;
  245. }
  246. return changed;
  247. }
  248. bool _wsCheckKey(const String& key, JsonVariant& value) {
  249. for (auto& callback : _ws_callbacks.on_keycheck) {
  250. if (callback(key.c_str(), value)) return true;
  251. // TODO: remove this to call all OnKeyCheckCallbacks with the
  252. // current key/value
  253. }
  254. return false;
  255. }
  256. void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
  257. //DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : "");
  258. // Get client ID
  259. uint32_t client_id = client->id();
  260. // Check early for empty object / nothing
  261. if ((length == 0) || (length == 1)) {
  262. return;
  263. }
  264. if ((length == 3) && (strcmp((char*) payload, "{}") == 0)) {
  265. return;
  266. }
  267. // Parse JSON input
  268. // TODO: json buffer should be pretty efficient with the non-const payload,
  269. // most of the space is taken by the object key references
  270. DynamicJsonBuffer jsonBuffer(512);
  271. JsonObject& root = jsonBuffer.parseObject((char *) payload);
  272. if (!root.success()) {
  273. DEBUG_MSG_P(PSTR("[WEBSOCKET] JSON parsing error\n"));
  274. wsSend_P(client_id, PSTR("{\"message\": 3}"));
  275. return;
  276. }
  277. // Check actions -----------------------------------------------------------
  278. const char* action = root["action"];
  279. if (action) {
  280. DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action);
  281. if (strcmp(action, "reboot") == 0) {
  282. deferredReset(100, CUSTOM_RESET_WEB);
  283. return;
  284. }
  285. if (strcmp(action, "reconnect") == 0) {
  286. _ws_defer.once_ms(100, wifiDisconnect);
  287. return;
  288. }
  289. if (strcmp(action, "factory_reset") == 0) {
  290. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  291. resetSettings();
  292. deferredReset(100, CUSTOM_RESET_FACTORY);
  293. return;
  294. }
  295. JsonObject& data = root["data"];
  296. if (data.success()) {
  297. // Callbacks
  298. for (auto& callback : _ws_callbacks.on_action) {
  299. callback(client_id, action, data);
  300. }
  301. // Restore configuration via websockets
  302. if (strcmp(action, "restore") == 0) {
  303. if (settingsRestoreJson(data)) {
  304. wsSend_P(client_id, PSTR("{\"message\": 5}"));
  305. } else {
  306. wsSend_P(client_id, PSTR("{\"message\": 4}"));
  307. }
  308. }
  309. return;
  310. }
  311. };
  312. // Check configuration -----------------------------------------------------
  313. JsonObject& config = root["config"];
  314. if (config.success()) {
  315. DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n"));
  316. String adminPass;
  317. bool save = false;
  318. for (auto kv: config) {
  319. bool changed = false;
  320. String key = kv.key;
  321. JsonVariant& value = kv.value;
  322. // Check password
  323. if (key == "adminPass") {
  324. if (!value.is<JsonArray&>()) continue;
  325. JsonArray& values = value.as<JsonArray&>();
  326. if (values.size() != 2) continue;
  327. if (values[0].as<String>().equals(values[1].as<String>())) {
  328. String password = values[0].as<String>();
  329. if (password.length() > 0) {
  330. setSetting(key, password);
  331. save = true;
  332. wsSend_P(client_id, PSTR("{\"action\": \"reload\"}"));
  333. }
  334. } else {
  335. wsSend_P(client_id, PSTR("{\"message\": 7}"));
  336. }
  337. continue;
  338. }
  339. if (!_wsCheckKey(key, value)) {
  340. delSetting(key);
  341. continue;
  342. }
  343. // Store values
  344. if (value.is<JsonArray&>()) {
  345. if (_wsStore(key, value.as<JsonArray&>())) changed = true;
  346. } else {
  347. if (_wsStore(key, value.as<String>())) changed = true;
  348. }
  349. // Update flags if value has changed
  350. if (changed) {
  351. save = true;
  352. }
  353. }
  354. // Save settings
  355. if (save) {
  356. // Callbacks
  357. espurnaReload();
  358. // Persist settings
  359. saveSettings();
  360. wsSend_P(client_id, PSTR("{\"message\": 8}"));
  361. } else {
  362. wsSend_P(client_id, PSTR("{\"message\": 9}"));
  363. }
  364. }
  365. }
  366. void _wsUpdate(JsonObject& root) {
  367. root["heap"] = getFreeHeap();
  368. root["uptime"] = getUptime();
  369. root["rssi"] = WiFi.RSSI();
  370. root["loadaverage"] = systemLoadAverage();
  371. #if ADC_MODE_VALUE == ADC_VCC
  372. root["vcc"] = ESP.getVcc();
  373. #endif
  374. #if NTP_SUPPORT
  375. if (ntpSynced()) root["now"] = now();
  376. #endif
  377. }
  378. void _wsResetUpdateTimer() {
  379. _ws_last_update = millis() + WS_UPDATE_INTERVAL;
  380. }
  381. void _wsDoUpdate(const bool connected) {
  382. if (!connected) return;
  383. if (millis() - _ws_last_update > WS_UPDATE_INTERVAL) {
  384. _ws_last_update = millis();
  385. wsSend(_wsUpdate);
  386. }
  387. }
  388. bool _wsOnKeyCheck(const char * key, JsonVariant& value) {
  389. if (strncmp(key, "ws", 2) == 0) return true;
  390. if (strncmp(key, "admin", 5) == 0) return true;
  391. if (strncmp(key, "hostname", 8) == 0) return true;
  392. if (strncmp(key, "desc", 4) == 0) return true;
  393. if (strncmp(key, "webPort", 7) == 0) return true;
  394. return false;
  395. }
  396. void _wsOnConnected(JsonObject& root) {
  397. char chipid[7];
  398. snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
  399. root["webMode"] = WEB_MODE_NORMAL;
  400. root["app_name"] = APP_NAME;
  401. root["app_version"] = APP_VERSION;
  402. root["app_build"] = buildTime();
  403. #if defined(APP_REVISION)
  404. root["app_revision"] = APP_REVISION;
  405. #endif
  406. root["manufacturer"] = MANUFACTURER;
  407. root["chipid"] = String(chipid);
  408. root["mac"] = WiFi.macAddress();
  409. root["bssid"] = WiFi.BSSIDstr();
  410. root["channel"] = WiFi.channel();
  411. root["device"] = DEVICE;
  412. root["hostname"] = getSetting("hostname");
  413. root["desc"] = getSetting("desc");
  414. root["network"] = getNetwork();
  415. root["deviceip"] = getIP();
  416. root["sketch_size"] = ESP.getSketchSize();
  417. root["free_size"] = ESP.getFreeSketchSpace();
  418. root["sdk"] = ESP.getSdkVersion();
  419. root["core"] = getCoreVersion();
  420. root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
  421. root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
  422. root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
  423. root["hbMode"] = getSetting("hbMode", HEARTBEAT_MODE).toInt();
  424. root["hbInterval"] = getSetting("hbInterval", HEARTBEAT_INTERVAL).toInt();
  425. }
  426. void wsSend(JsonObject& root) {
  427. // TODO: avoid serializing twice?
  428. size_t len = root.measureLength();
  429. AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
  430. if (buffer) {
  431. root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
  432. _ws.textAll(buffer);
  433. }
  434. }
  435. void wsSend(uint32_t client_id, JsonObject& root) {
  436. AsyncWebSocketClient* client = _ws.client(client_id);
  437. if (client == nullptr) return;
  438. // TODO: avoid serializing twice?
  439. size_t len = root.measureLength();
  440. AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
  441. if (buffer) {
  442. root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
  443. client->text(buffer);
  444. }
  445. }
  446. void _wsConnected(uint32_t client_id) {
  447. const bool changePassword = (USE_PASSWORD && WEB_FORCE_PASS_CHANGE)
  448. ? getAdminPass().equals(ADMIN_PASS)
  449. : false;
  450. if (changePassword) {
  451. StaticJsonBuffer<JSON_OBJECT_SIZE(1)> jsonBuffer;
  452. JsonObject& root = jsonBuffer.createObject();
  453. root["webMode"] = WEB_MODE_PASSWORD;
  454. wsSend(client_id, root);
  455. return;
  456. }
  457. wsPostAll(client_id, _ws_callbacks.on_visible);
  458. wsPostSequence(client_id, _ws_callbacks.on_connected);
  459. wsPostSequence(client_id, _ws_callbacks.on_data);
  460. }
  461. void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
  462. if (type == WS_EVT_CONNECT) {
  463. client->_tempObject = nullptr;
  464. #ifndef NOWSAUTH
  465. if (!_wsAuth(client)) {
  466. wsSend_P(client->id(), PSTR("{\"message\": 10}"));
  467. DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
  468. client->close();
  469. return;
  470. }
  471. #endif
  472. IPAddress ip = client->remoteIP();
  473. DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
  474. _wsConnected(client->id());
  475. _wsResetUpdateTimer();
  476. wifiReconnectCheck();
  477. client->_tempObject = new WebSocketIncommingBuffer(_wsParse, true);
  478. } else if(type == WS_EVT_DISCONNECT) {
  479. DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u disconnected\n"), client->id());
  480. if (client->_tempObject) {
  481. delete (WebSocketIncommingBuffer *) client->_tempObject;
  482. }
  483. wifiReconnectCheck();
  484. } else if(type == WS_EVT_ERROR) {
  485. DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u error(%u): %s\n"), client->id(), *((uint16_t*)arg), (char*)data);
  486. } else if(type == WS_EVT_PONG) {
  487. DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u pong(%u): %s\n"), client->id(), len, len ? (char*) data : "");
  488. } else if(type == WS_EVT_DATA) {
  489. //DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u data(%u): %s\n"), client->id(), len, len ? (char*) data : "");
  490. if (!client->_tempObject) return;
  491. WebSocketIncommingBuffer *buffer = (WebSocketIncommingBuffer *)client->_tempObject;
  492. AwsFrameInfo * info = (AwsFrameInfo*)arg;
  493. buffer->data_event(client, info, data, len);
  494. }
  495. }
  496. // TODO: make this generic loop method to queue important ws messages?
  497. // or, if something uses ticker / async ctx to send messages,
  498. // it needs a retry mechanism built into the callback object
  499. void _wsHandleClientData(const bool connected) {
  500. if (!connected && !_ws_client_data.empty()) {
  501. _ws_client_data.pop();
  502. return;
  503. }
  504. if (_ws_client_data.empty()) return;
  505. auto& data = _ws_client_data.front();
  506. // client_id == 0 means we need to send the message to every client
  507. if (data.client_id) {
  508. AsyncWebSocketClient* ws_client = _ws.client(data.client_id);
  509. if (!ws_client) {
  510. _ws_client_data.pop();
  511. return;
  512. }
  513. // wait until we can send the next batch of messages
  514. // XXX: enforce that callbacks send only one message per iteration
  515. if (ws_client->queueIsFull()) {
  516. return;
  517. }
  518. }
  519. // XXX: block allocation will try to create *2 next time,
  520. // likely failing and causing wsSend to reference empty objects
  521. // XXX: arduinojson6 will not do this, but we may need to use per-callback buffers
  522. constexpr const size_t BUFFER_SIZE = 3192;
  523. DynamicJsonBuffer jsonBuffer(BUFFER_SIZE);
  524. JsonObject& root = jsonBuffer.createObject();
  525. data.send(root);
  526. if (data.client_id) {
  527. wsSend(data.client_id, root);
  528. } else {
  529. wsSend(root);
  530. }
  531. yield();
  532. if (data.done()) {
  533. _ws_client_data.pop();
  534. }
  535. }
  536. void _wsLoop() {
  537. const bool connected = wsConnected();
  538. _wsDoUpdate(connected);
  539. _wsHandleClientData(connected);
  540. #if DEBUG_WEB_SUPPORT
  541. _ws_debug.send(connected);
  542. #endif
  543. }
  544. // -----------------------------------------------------------------------------
  545. // Public API
  546. // -----------------------------------------------------------------------------
  547. bool wsConnected() {
  548. return (_ws.count() > 0);
  549. }
  550. bool wsConnected(uint32_t client_id) {
  551. return _ws.hasClient(client_id);
  552. }
  553. ws_callbacks_t& wsRegister() {
  554. return _ws_callbacks;
  555. }
  556. void wsSend(ws_on_send_callback_f callback) {
  557. if (_ws.count() > 0) {
  558. DynamicJsonBuffer jsonBuffer(512);
  559. JsonObject& root = jsonBuffer.createObject();
  560. callback(root);
  561. wsSend(root);
  562. }
  563. }
  564. void wsSend(const char * payload) {
  565. if (_ws.count() > 0) {
  566. _ws.textAll(payload);
  567. }
  568. }
  569. void wsSend_P(PGM_P payload) {
  570. if (_ws.count() > 0) {
  571. char buffer[strlen_P(payload)];
  572. strcpy_P(buffer, payload);
  573. _ws.textAll(buffer);
  574. }
  575. }
  576. void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
  577. AsyncWebSocketClient* client = _ws.client(client_id);
  578. if (client == nullptr) return;
  579. DynamicJsonBuffer jsonBuffer(512);
  580. JsonObject& root = jsonBuffer.createObject();
  581. callback(root);
  582. wsSend(client_id, root);
  583. }
  584. void wsSend(uint32_t client_id, const char * payload) {
  585. _ws.text(client_id, payload);
  586. }
  587. void wsSend_P(uint32_t client_id, PGM_P payload) {
  588. char buffer[strlen_P(payload)];
  589. strcpy_P(buffer, payload);
  590. _ws.text(client_id, buffer);
  591. }
  592. void wsPost(const ws_on_send_callback_f& cb) {
  593. _ws_client_data.emplace(cb);
  594. }
  595. void wsPost(uint32_t client_id, const ws_on_send_callback_f& cb) {
  596. _ws_client_data.emplace(client_id, cb);
  597. }
  598. void wsPostAll(uint32_t client_id, const ws_on_send_callback_list_t& cbs) {
  599. _ws_client_data.emplace(client_id, cbs, ws_data_t::ALL);
  600. }
  601. void wsPostAll(const ws_on_send_callback_list_t& cbs) {
  602. _ws_client_data.emplace(0, cbs, ws_data_t::ALL);
  603. }
  604. void wsPostSequence(uint32_t client_id, const ws_on_send_callback_list_t& cbs) {
  605. _ws_client_data.emplace(client_id, cbs, ws_data_t::SEQUENCE);
  606. }
  607. void wsPostSequence(uint32_t client_id, ws_on_send_callback_list_t&& cbs) {
  608. _ws_client_data.emplace(client_id, std::forward<ws_on_send_callback_list_t>(cbs), ws_data_t::SEQUENCE);
  609. }
  610. void wsPostSequence(const ws_on_send_callback_list_t& cbs) {
  611. _ws_client_data.emplace(0, cbs, ws_data_t::SEQUENCE);
  612. }
  613. void wsSetup() {
  614. _ws.onEvent(_wsEvent);
  615. webServer()->addHandler(&_ws);
  616. // CORS
  617. const String webDomain = getSetting("webDomain", WEB_REMOTE_DOMAIN);
  618. DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", webDomain);
  619. if (!webDomain.equals("*")) {
  620. DefaultHeaders::Instance().addHeader("Access-Control-Allow-Credentials", "true");
  621. }
  622. webServer()->on("/auth", HTTP_GET, _onAuth);
  623. wsRegister()
  624. .onConnected(_wsOnConnected)
  625. .onKeyCheck(_wsOnKeyCheck);
  626. espurnaRegisterLoop(_wsLoop);
  627. }
  628. #endif // WEB_SUPPORT