Browse Source

Fixed size json payload & other WS bugfixes (#1843)

- update every dynamicjsonbuffer with fixed size constructor argument
- change to ws callback registration to use a class builder (just cosmetic)
- test multiple ws data callbacks for each module
- remove some of the static strings in favour of ws data callback
- improve sensor ws callback data size, remove duplicated strings
- use static buffer in wsDebugSend
- postpone wsSend until loop, implement wsPost to allow other modules to queue message callbacks. remove Ticker based ws callbacks for data
- update WebUI files
pull/1851/head
Max Prokhorov 4 years ago
committed by GitHub
parent
commit
21423431ce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2936 additions and 2574 deletions
  1. +6
    -5
      code/espurna/alexa.ino
  2. +49
    -22
      code/espurna/api.ino
  3. +2
    -2
      code/espurna/button.ino
  4. +58
    -7
      code/espurna/config/prototypes.h
  5. +1
    -1
      code/espurna/crash.ino
  6. BIN
      code/espurna/data/index.all.html.gz
  7. BIN
      code/espurna/data/index.sensor.html.gz
  8. +15
    -13
      code/espurna/debug.ino
  9. +13
    -12
      code/espurna/domoticz.ino
  10. +3
    -3
      code/espurna/espurna.ino
  11. +78
    -53
      code/espurna/homeassistant.ino
  12. +9
    -4
      code/espurna/influxdb.ino
  13. +10
    -5
      code/espurna/led.ino
  14. +3
    -2
      code/espurna/libs/HeapStats.h
  15. +11
    -6
      code/espurna/light.ino
  16. +4
    -3
      code/espurna/lightfox.ino
  17. +27
    -11
      code/espurna/mqtt.ino
  18. +9
    -4
      code/espurna/nofuss.ino
  19. +14
    -11
      code/espurna/ntp.ino
  20. +19
    -20
      code/espurna/relay.ino
  21. +24
    -47
      code/espurna/rfbridge.ino
  22. +6
    -5
      code/espurna/rfm69.ino
  23. +11
    -5
      code/espurna/scheduler.ino
  24. +68
    -33
      code/espurna/sensor.ino
  25. +17
    -0
      code/espurna/settings.ino
  26. +730
    -728
      code/espurna/static/index.all.html.gz.h
  27. +1431
    -1429
      code/espurna/static/index.sensor.html.gz.h
  28. +2
    -2
      code/espurna/system.ino
  29. +6
    -5
      code/espurna/telnet.ino
  30. +7
    -1
      code/espurna/terminal.ino
  31. +7
    -6
      code/espurna/thermostat.ino
  32. +9
    -9
      code/espurna/thinkspeak.ino
  33. +9
    -0
      code/espurna/utils.ino
  34. +17
    -12
      code/espurna/web.ino
  35. +6
    -9
      code/espurna/wifi.ino
  36. +235
    -93
      code/espurna/ws.ino
  37. +19
    -5
      code/html/custom.js
  38. +1
    -1
      code/platformio.ini

+ 6
- 5
code/espurna/alexa.ino View File

@ -23,12 +23,11 @@ static std::queue<alexa_queue_element_t> _alexa_queue;
// ALEXA
// -----------------------------------------------------------------------------
bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _alexaWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "alexa", 5) == 0);
}
void _alexaWebSocketOnSend(JsonObject& root) {
root["alexaVisible"] = 1;
void _alexaWebSocketOnConnected(JsonObject& root) {
root["alexaEnabled"] = alexaEnabled();
root["alexaName"] = getSetting("alexaName");
}
@ -123,8 +122,10 @@ void alexaSetup() {
#if WEB_SUPPORT
webBodyRegister(_alexaBodyCallback);
webRequestRegister(_alexaRequestCallback);
wsOnSendRegister(_alexaWebSocketOnSend);
wsOnReceiveRegister(_alexaWebSocketOnReceive);
wsRegister()
.onVisible([](JsonObject& root) { root["alexaVisible"] = 1; })
.onConnected(_alexaWebSocketOnConnected)
.onKeyCheck(_alexaWebSocketOnKeyCheck);
#endif
// Register wifi callback


+ 49
- 22
code/espurna/api.ino View File

@ -22,12 +22,11 @@ std::vector<web_api_t> _apis;
// -----------------------------------------------------------------------------
bool _apiWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _apiWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "api", 3) == 0);
}
void _apiWebSocketOnSend(JsonObject& root) {
root["apiVisible"] = 1;
void _apiWebSocketOnConnected(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
@ -76,6 +75,47 @@ bool _asJson(AsyncWebServerRequest *request) {
return asJson;
}
void _onAPIsText(AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("text/plain");
String output;
output.reserve(48);
for (unsigned int i=0; i < _apis.size(); i++) {
output = "";
output += _apis[i].key;
output += " -> ";
output += "/api/";
output += _apis[i].key;
output += '\n';
response->write(output.c_str());
}
request->send(response);
}
constexpr const size_t API_JSON_BUFFER_SIZE = 1024;
void _onAPIsJson(AsyncWebServerRequest *request) {
DynamicJsonBuffer jsonBuffer(API_JSON_BUFFER_SIZE);
JsonObject& root = jsonBuffer.createObject();
constexpr const int BUFFER_SIZE = 48;
for (unsigned int i=0; i < _apis.size(); i++) {
char buffer[BUFFER_SIZE] = {0};
int res = snprintf(buffer, sizeof(buffer), "/api/%s", _apis[i].key);
if ((res < 0) || (res > (BUFFER_SIZE - 1))) {
request->send(500);
return;
}
root[_apis[i].key] = buffer;
}
AsyncResponseStream *response = request->beginResponseStream("application/json");
root.printTo(*response);
request->send(response);
}
void _onAPIs(AsyncWebServerRequest *request) {
webLog(request);
@ -83,26 +123,11 @@ void _onAPIs(AsyncWebServerRequest *request) {
bool asJson = _asJson(request);
char buffer[40];
String output;
if (asJson) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
for (unsigned int i=0; i < _apis.size(); i++) {
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key);
root[_apis[i].key] = String(buffer);
}
root.printTo(output);
jsonBuffer.clear();
request->send(200, "application/json", output);
_onAPIsJson(request);
} else {
for (unsigned int i=0; i < _apis.size(); i++) {
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key);
output += _apis[i].key + String(" -> ") + String(buffer) + String("\n");
}
request->send(200, "text/plain", output);
_onAPIsText(request);
}
}
@ -220,8 +245,10 @@ void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f
void apiSetup() {
_apiConfigure();
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
wsRegister()
.onVisible([](JsonObject& root) { root["apiVisible"] = 1; })
.onConnected(_apiWebSocketOnConnected)
.onKeyCheck(_apiWebSocketOnKeyCheck);
webRequestRegister(_apiRequestCallback);
espurnaRegisterReload(_apiConfigure);
}


+ 2
- 2
code/espurna/button.ino View File

@ -36,7 +36,7 @@ void buttonMQTT(unsigned char id, uint8_t event) {
#if WEB_SUPPORT
bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "btn", 3) == 0);
}
@ -243,7 +243,7 @@ void buttonSetup() {
// Websocket Callbacks
#if WEB_SUPPORT
wsOnReceiveRegister(_buttonWebSocketOnReceive);
wsRegister().onKeyCheck(_buttonWebSocketOnKeyCheck);
#endif
// Register loop


+ 58
- 7
code/espurna/config/prototypes.h View File

@ -130,6 +130,11 @@ bool gpioValid(unsigned char gpio);
bool gpioGetLock(unsigned char gpio);
bool gpioReleaseLock(unsigned char gpio);
// -----------------------------------------------------------------------------
// Homeassistant
// -----------------------------------------------------------------------------
struct ha_config_t;
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
@ -159,7 +164,7 @@ void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len);
// MQTT
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void(unsigned int, const char *, char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttMagnitude(char * topic);
#else
@ -266,17 +271,63 @@ void webRequestRegister(web_request_callback_f callback);
// WebSockets
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
typedef std::function<void(JsonObject&)> ws_on_send_callback_f;
void wsOnSendRegister(ws_on_send_callback_f callback);
using ws_on_send_callback_f = std::function<void(JsonObject&)>;
using ws_on_action_callback_f = std::function<void(uint32_t, const char *, JsonObject&)>;
using ws_on_keycheck_callback_f = std::function<bool(const char *, JsonVariant&)>;
using ws_on_send_callback_list_t = std::vector<ws_on_send_callback_f>;
using ws_on_action_callback_list_t = std::vector<ws_on_action_callback_f>;
using ws_on_keycheck_callback_list_t = std::vector<ws_on_keycheck_callback_f>;
struct ws_callbacks_t {
ws_on_send_callback_list_t on_visible;
ws_on_send_callback_list_t on_connected;
ws_on_send_callback_list_t on_data;
ws_on_action_callback_list_t on_action;
ws_on_keycheck_callback_list_t on_keycheck;
ws_callbacks_t& onVisible(ws_on_send_callback_f cb) {
on_visible.push_back(cb);
return *this;
}
ws_callbacks_t& onConnected(ws_on_send_callback_f cb) {
on_connected.push_back(cb);
return *this;
}
ws_callbacks_t& onData(ws_on_send_callback_f cb) {
on_data.push_back(cb);
return *this;
}
ws_callbacks_t& onAction(ws_on_action_callback_f cb) {
on_action.push_back(cb);
return *this;
}
ws_callbacks_t& onKeyCheck(ws_on_keycheck_callback_f cb) {
on_keycheck.push_back(cb);
return *this;
}
};
ws_callbacks_t& wsRegister();
void wsSend(uint32_t, JsonObject& root);
void wsSend(JsonObject& root);
void wsSend(ws_on_send_callback_f sender);
typedef std::function<void(uint32_t, const char *, JsonObject&)> ws_on_action_callback_f;
void wsOnActionRegister(ws_on_action_callback_f callback);
void wsPost(const ws_on_send_callback_list_t&);
void wsPost(uint32_t, const ws_on_send_callback_list_t&);
void wsPostAll(uint32_t, const ws_on_send_callback_list_t&);
void wsPostAll(const ws_on_send_callback_list_t&);
typedef std::function<bool(const char *, JsonVariant&)> ws_on_receive_callback_f;
void wsOnReceiveRegister(ws_on_receive_callback_f callback);
void wsPostSequence(uint32_t, const ws_on_send_callback_list_t&);
void wsPostSequence(const ws_on_send_callback_list_t&);
bool wsConnected();
bool wsConnected(uint32_t);


+ 1
- 1
code/espurna/crash.ino View File

@ -49,7 +49,7 @@ extern "C" {
#define SAVE_CRASH_STACK_TRACE_MAX 0x80 // limit at 128 bytes (increment/decrement by 16)
uint16_t _save_crash_stack_trace_max = SAVE_CRASH_STACK_TRACE_MAX;
uint16_t _save_crash_enabled = true;
bool _save_crash_enabled = true;
/**
* Save crash information in EEPROM


BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


+ 15
- 13
code/espurna/debug.ino View File

@ -90,24 +90,26 @@ void debugSendImpl(const char * message) {
#if DEBUG_WEB_SUPPORT
void debugWebSetup() {
wsOnSendRegister([](JsonObject& root) {
root["dbgVisible"] = 1;
});
wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) {
void _debugWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
#if TERMINAL_SUPPORT
if (strcmp(action, "dbgcmd") == 0) {
const char* command = data.get<const char*>("command");
char buffer[strlen(command) + 2];
snprintf(buffer, sizeof(buffer), "%s\n", command);
terminalInject((void*) buffer, strlen(buffer));
if (!data.containsKey("command") || !data["command"].is<const char*>()) return;
const char* command = data["command"];
if (command && strlen(command)) {
auto command = data.get<const char*>("command");
terminalInject((void*) command, strlen(command));
terminalInject('\n');
}
}
#endif
});
}
void debugWebSetup() {
wsRegister()
.onVisible([](JsonObject& root) { root["dbgVisible"] = 1; })
.onAction(_debugWebSocketOnAction);
#if DEBUG_UDP_SUPPORT
#if DEBUG_UDP_PORT == 514


+ 13
- 12
code/espurna/domoticz.ino View File

@ -96,7 +96,7 @@ void _domoticzLight(unsigned int idx, const JsonObject& root) {
#endif
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
void _domoticzMqtt(unsigned int type, const char * topic, char * payload) {
if (!_dcz_enabled) return;
@ -118,8 +118,8 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (dczTopicOut.equals(topic)) {
// Parse response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
DynamicJsonBuffer jsonBuffer(1024);
JsonObject& root = jsonBuffer.parseObject(payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[DOMOTICZ] Error parsing data\n"));
return;
@ -166,13 +166,16 @@ void _domoticzBrokerCallback(const unsigned char type, const char * topic, unsig
#if WEB_SUPPORT
bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _domoticzWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "dcz", 3) == 0);
}
void _domoticzWebSocketOnSend(JsonObject& root) {
void _domoticzWebSocketOnVisible(JsonObject& root) {
root["dczVisible"] = static_cast<unsigned char>(haveRelaysOrSensors());
}
void _domoticzWebSocketOnConnected(JsonObject& root) {
unsigned char visible = 0;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -181,15 +184,11 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
for (unsigned char i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
visible = (relayCount() > 0);
#if SENSOR_SUPPORT
_sensorWebSocketMagnitudes(root, "dcz");
visible = visible || (magnitudeCount() > 0);
#endif
root["dczVisible"] = visible;
}
#endif // WEB_SUPPORT
@ -248,8 +247,10 @@ void domoticzSetup() {
_domoticzConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_domoticzWebSocketOnSend);
wsOnReceiveRegister(_domoticzWebSocketOnReceive);
wsRegister()
.onVisible(_domoticzWebSocketOnVisible)
.onConnected(_domoticzWebSocketOnConnected)
.onKeyCheck(_domoticzWebSocketOnKeyCheck);
#endif
#if BROKER_SUPPORT


+ 3
- 3
code/espurna/espurna.ino View File

@ -192,6 +192,9 @@ void setup() {
#if NOFUSS_SUPPORT
nofussSetup();
#endif
#if SENSOR_SUPPORT
sensorSetup();
#endif
#if INFLUXDB_SUPPORT
idbSetup();
#endif
@ -210,9 +213,6 @@ void setup() {
#if HOMEASSISTANT_SUPPORT
haSetup();
#endif
#if SENSOR_SUPPORT
sensorSetup();
#endif
#if SCHEDULER_SUPPORT
schSetup();
#endif


+ 78
- 53
code/espurna/homeassistant.ino View File

@ -25,6 +25,45 @@ String _haFixName(String name) {
return name;
}
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
const String switchType("light");
#else
const String switchType("switch");
#endif
struct ha_config_t {
static const size_t DEFAULT_BUFFER_SIZE = 2048;
ha_config_t(size_t size) :
jsonBuffer(size),
deviceConfig(jsonBuffer.createObject()),
root(jsonBuffer.createObject()),
identifier(getIdentifier()),
name(getSetting("desc", getSetting("hostname"))),
version(String(APP_NAME " " APP_VERSION " (") + getCoreVersion() + ")")
{
deviceConfig.createNestedArray("identifiers").add(identifier.c_str());
deviceConfig["name"] = name.c_str();
deviceConfig["sw_version"] = version.c_str();
deviceConfig["manufacturer"] = MANUFACTURER;
deviceConfig["model"] = DEVICE;
}
ha_config_t() : ha_config_t(DEFAULT_BUFFER_SIZE) {}
size_t size() { return jsonBuffer.size(); }
DynamicJsonBuffer jsonBuffer;
JsonObject& deviceConfig;
JsonObject& root;
const String identifier;
const String name;
const String version;
};
// -----------------------------------------------------------------------------
// SENSORS
// -----------------------------------------------------------------------------
@ -40,7 +79,7 @@ void _haSendMagnitude(unsigned char i, JsonObject& config) {
config["unit_of_measurement"] = magnitudeUnits(type);
}
void _haSendMagnitudes(const JsonObject& deviceConfig) {
void _haSendMagnitudes(ha_config_t& config) {
for (unsigned char i=0; i<magnitudeCount(); i++) {
@ -51,21 +90,20 @@ void _haSendMagnitudes(const JsonObject& deviceConfig) {
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config["uniq_id"] = getIdentifier() + "_" + magnitudeTopic(magnitudeType(i)) + "_" + String(i);
config["device"] = deviceConfig;
_haSendMagnitude(i, config.root);
config.root["uniq_id"] = getIdentifier() + "_" + magnitudeTopic(magnitudeType(i)) + "_" + String(i);
config.root["device"] = config.deviceConfig;
config.printTo(output);
jsonBuffer.clear();
output.reserve(config.root.measureLength());
config.root.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
#endif // SENSOR_SUPPORT
@ -120,31 +158,23 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
}
void _haSendSwitches(const JsonObject& deviceConfig) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
void _haSendSwitches(ha_config_t& config) {
for (unsigned char i=0; i<relayCount(); i++) {
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + type +
"/" + switchType +
"/" + getSetting("hostname") + "_" + String(i) +
"/config";
String output;
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config["uniq_id"] = getIdentifier() + "_" + type + "_" + String(i);
config["device"] = deviceConfig;
_haSendSwitch(i, config.root);
config.root["uniq_id"] = getIdentifier() + "_" + switchType + "_" + String(i);
config.root["device"] = config.deviceConfig;
config.printTo(output);
jsonBuffer.clear();
output.reserve(config.root.measureLength());
config.root.printTo(output);
}
mqttSendRaw(topic.c_str(), output.c_str());
@ -158,26 +188,22 @@ void _haSendSwitches(const JsonObject& deviceConfig) {
void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
#else
String type = String("switch");
#endif
constexpr const size_t BUFFER_SIZE = 1024;
String output;
output.reserve(BUFFER_SIZE + 64);
DynamicJsonBuffer jsonBuffer(BUFFER_SIZE);
for (unsigned char i=0; i<relayCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
output += "\n\n" + type + ":\n";
output += "\n\n" + switchType + ":\n";
bool first = true;
for (auto kv : config) {
@ -199,8 +225,8 @@ void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false)
}
jsonBuffer.clear();
printer(output);
output = "";
}
@ -208,13 +234,9 @@ void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false)
for (unsigned char i=0; i<magnitudeCount(); i++) {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
String output;
output.reserve(config.measureLength() + 32);
if (wrapJson) {
output += "{\"haConfig\": \"";
}
@ -243,8 +265,8 @@ void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false)
}
jsonBuffer.clear();
printer(output);
output = "";
}
@ -272,17 +294,14 @@ void _haSend() {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Get common device config
DynamicJsonBuffer jsonBuffer;
JsonObject& deviceConfig = jsonBuffer.createObject();
_haGetDeviceConfig(deviceConfig);
ha_config_t config;
// Send messages
_haSendSwitches(deviceConfig);
_haSendSwitches(config);
#if SENSOR_SUPPORT
_haSendMagnitudes(deviceConfig);
_haSendMagnitudes(config);
#endif
jsonBuffer.clear();
_haSendFlag = false;
}
@ -298,12 +317,15 @@ void _haConfigure() {
std::queue<uint32_t> _ha_send_config;
bool _haWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _haWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "ha", 2) == 0);
}
void _haWebSocketOnSend(JsonObject& root) {
void _haWebSocketOnVisible(JsonObject& root) {
root["haVisible"] = 1;
}
void _haWebSocketOnConnected(JsonObject& root) {
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
}
@ -332,7 +354,7 @@ void _haInitCommands() {
setSetting("haEnabled", "1");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
wsPost(_haWebSocketOnConnected);
#endif
terminalOK();
});
@ -341,7 +363,7 @@ void _haInitCommands() {
setSetting("haEnabled", "0");
_haConfigure();
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
wsPost(_haWebSocketOnConnected);
#endif
terminalOK();
});
@ -374,9 +396,11 @@ void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnActionRegister(_haWebSocketOnAction);
wsOnReceiveRegister(_haWebSocketOnReceive);
wsRegister()
.onVisible(_haWebSocketOnVisible)
.onConnected(_haWebSocketOnConnected)
.onAction(_haWebSocketOnAction)
.onKeyCheck(_haWebSocketOnKeyCheck);
espurnaRegisterLoop(_haLoop);
#endif
@ -387,6 +411,7 @@ void haSetup() {
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) _haSend();
if (type == MQTT_DISCONNECT_EVENT) _haSendFlag = false;
});
// Main callbacks


+ 9
- 4
code/espurna/influxdb.ino View File

@ -17,12 +17,15 @@ SyncClientWrap * _idb_client;
// -----------------------------------------------------------------------------
bool _idbWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _idbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "idb", 3) == 0);
}
void _idbWebSocketOnSend(JsonObject& root) {
void _idbWebSocketOnVisible(JsonObject& root) {
root["idbVisible"] = 1;
}
void _idbWebSocketOnConnected(JsonObject& root) {
root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1;
root["idbHost"] = getSetting("idbHost", INFLUXDB_HOST);
root["idbPort"] = getSetting("idbPort", INFLUXDB_PORT).toInt();
@ -118,8 +121,10 @@ void idbSetup() {
_idbConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_idbWebSocketOnSend);
wsOnReceiveRegister(_idbWebSocketOnReceive);
wsRegister()
.onVisible(_idbWebSocketOnVisible)
.onConnected(_idbWebSocketOnConnected)
.onKeyCheck(_idbWebSocketOnKeyCheck);
#endif
#if BROKER_SUPPORT


+ 10
- 5
code/espurna/led.ino View File

@ -71,13 +71,16 @@ void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn)
#if WEB_SUPPORT
bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _ledWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "led", 3) == 0);
}
void _ledWebSocketOnSend(JsonObject& root) {
if (_ledCount() == 0) return;
void _ledWebSocketOnVisible(JsonObject& root) {
root["ledVisible"] = 1;
}
void _ledWebSocketOnConnected(JsonObject& root) {
if (_ledCount() == 0) return;
JsonArray& leds = root.createNestedArray("ledConfig");
for (byte i=0; i<_ledCount(); i++) {
JsonObject& led = leds.createNestedObject();
@ -200,8 +203,10 @@ void ledSetup() {
#endif
#if WEB_SUPPORT
wsOnSendRegister(_ledWebSocketOnSend);
wsOnReceiveRegister(_ledWebSocketOnReceive);
wsRegister()
.onVisible(_ledWebSocketOnVisible)
.onConnected(_ledWebSocketOnConnected)
.onKeyCheck(_ledWebSocketOnKeyCheck);
#endif
#if BROKER_SUPPORT


+ 3
- 2
code/espurna/libs/HeapStats.h View File

@ -107,8 +107,9 @@ void infoHeapStats(const char* name, const heap_stats_t& stats) {
}
void infoHeapStats(bool show_frag_stats = true) {
infoMemory("Heap", getHeapStats());
const auto stats = getHeapStats();
infoMemory("Heap", stats);
if (show_frag_stats && EspClass_has_getHeapStats::check) {
infoHeapStats("Heap", getHeapStats());
infoHeapStats("Heap", stats);
}
}

+ 11
- 6
code/espurna/light.ino View File

@ -770,7 +770,7 @@ void _lightComms(unsigned char mask) {
// Report color to WS clients (using current brightness setting)
#if WEB_SUPPORT
wsSend(_lightWebSocketStatus);
wsPost(_lightWebSocketStatus);
#endif
// Report channels to local broker
@ -921,7 +921,7 @@ void lightTransitionTime(unsigned long m) {
#if WEB_SUPPORT
bool _lightWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _lightWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "light", 5) == 0) return true;
if (strncmp(key, "use", 3) == 0) return true;
return false;
@ -946,8 +946,11 @@ void _lightWebSocketStatus(JsonObject& root) {
root["brightness"] = lightBrightness();
}
void _lightWebSocketOnSend(JsonObject& root) {
void _lightWebSocketOnVisible(JsonObject& root) {
root["colorVisible"] = 1;
}
void _lightWebSocketOnConnected(JsonObject& root) {
root["mqttGroupColor"] = getSetting("mqttGroupColor");
root["useColor"] = _light_has_color;
root["useWhite"] = _light_use_white;
@ -1290,9 +1293,11 @@ void lightSetup() {
}
#if WEB_SUPPORT
wsOnSendRegister(_lightWebSocketOnSend);
wsOnActionRegister(_lightWebSocketOnAction);
wsOnReceiveRegister(_lightWebSocketOnReceive);
wsRegister()
.onVisible(_lightWebSocketOnVisible)
.onConnected(_lightWebSocketOnConnected)
.onAction(_lightWebSocketOnAction)
.onKeyCheck(_lightWebSocketOnKeyCheck);
#endif
#if API_SUPPORT


+ 4
- 3
code/espurna/lightfox.ino View File

@ -47,7 +47,7 @@ void lightfoxClear() {
#if WEB_SUPPORT
void _lightfoxWebSocketOnSend(JsonObject& root) {
void _lightfoxWebSocketOnConnected(JsonObject& root) {
root["lightfoxVisible"] = 1;
uint8_t buttonsCount = _buttons.size();
root["lightfoxRelayCount"] = relayCount();
@ -94,8 +94,9 @@ void _lightfoxInitCommands() {
void lightfoxSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_lightfoxWebSocketOnSend);
wsOnActionRegister(_lightfoxWebSocketOnAction);
wsRegister()
.onConnected(_lightfoxWebSocketOnConnected)
.onAction(_lightfoxWebSocketOnAction);
#endif
#if TERMINAL_SUPPORT


+ 27
- 11
code/espurna/mqtt.ino View File

@ -56,11 +56,12 @@ String _mqtt_clientid;
std::vector<mqtt_callback_f> _mqtt_callbacks;
typedef struct {
unsigned char parent = 255;
struct mqtt_message_t {
static const unsigned char END = 255;
unsigned char parent = END;
char * topic;
char * message = NULL;
} mqtt_message_t;
};
std::vector<mqtt_message_t> _mqtt_queue;
Ticker _mqtt_flush_ticker;
@ -341,13 +342,22 @@ void _mqttInfo() {
#if WEB_SUPPORT
bool _mqttWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _mqttWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "mqtt", 3) == 0);
}
void _mqttWebSocketOnSend(JsonObject& root) {
void _mqttWebSocketOnVisible(JsonObject& root) {
root["mqttVisible"] = 1;
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
#endif
}
void _mqttWebSocketOnData(JsonObject& root) {
root["mqttStatus"] = mqttConnected();
}
void _mqttWebSocketOnConnected(JsonObject& root) {
root["mqttEnabled"] = mqttEnabled();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
@ -358,7 +368,6 @@ void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttRetain"] = _mqtt_retain;
root["mqttQoS"] = _mqtt_qos;
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
root["mqttUseSSL"] = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT);
#endif
@ -639,9 +648,9 @@ void mqttFlush() {
if (_mqtt_queue.size() == 0) return;
// Build tree recursively
DynamicJsonBuffer jsonBuffer;
DynamicJsonBuffer jsonBuffer(1024);
JsonObject& root = jsonBuffer.createObject();
_mqttBuildTree(root, 255);
_mqttBuildTree(root, mqtt_message_t::END);
// Add extra propeties
#if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME
@ -704,7 +713,7 @@ int8_t mqttEnqueue(const char * topic, const char * message, unsigned char paren
}
int8_t mqttEnqueue(const char * topic, const char * message) {
return mqttEnqueue(topic, message, 255);
return mqttEnqueue(topic, message, mqtt_message_t::END);
}
// -----------------------------------------------------------------------------
@ -843,8 +852,15 @@ void mqttSetup() {
mqttRegister(_mqttCallback);
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnReceiveRegister(_mqttWebSocketOnReceive);
wsRegister()
.onVisible(_mqttWebSocketOnVisible)
.onData(_mqttWebSocketOnData)
.onConnected(_mqttWebSocketOnConnected)
.onKeyCheck(_mqttWebSocketOnKeyCheck);
mqttRegister([](unsigned int type, const char*, const char*) {
if ((type == MQTT_CONNECT_EVENT) || (type == MQTT_DISCONNECT_EVENT)) wsPost(_mqttWebSocketOnData);
});
#endif
#if TERMINAL_SUPPORT


+ 9
- 4
code/espurna/nofuss.ino View File

@ -20,12 +20,15 @@ bool _nofussEnabled = false;
#if WEB_SUPPORT
bool _nofussWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _nofussWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "nofuss", 6) == 0);
}
void _nofussWebSocketOnSend(JsonObject& root) {
void _nofussWebSocketOnVisible(JsonObject& root) {
root["nofussVisible"] = 1;
}
void _nofussWebSocketOnConnected(JsonObject& root) {
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
}
@ -161,8 +164,10 @@ void nofussSetup() {
});
#if WEB_SUPPORT
wsOnSendRegister(_nofussWebSocketOnSend);
wsOnReceiveRegister(_nofussWebSocketOnReceive);
wsRegister()
.onVisible(_nofussWebSocketOnVisible)
.onConnected(_nofussWebSocketOnConnected)
.onKeyCheck(_nofussWebSocketOnKeyCheck);
#endif
#if TERMINAL_SUPPORT


+ 14
- 11
code/espurna/ntp.ino View File

@ -26,13 +26,19 @@ bool _ntp_want_sync = false;
#if WEB_SUPPORT
bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _ntpWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "ntp", 3) == 0);
}
void _ntpWebSocketOnSend(JsonObject& root) {
void _ntpWebSocketOnVisible(JsonObject& root) {
root["ntpVisible"] = 1;
}
void _ntpWebSocketOnData(JsonObject& root) {
root["ntpStatus"] = (timeStatus() == timeSet);
}
void _ntpWebSocketOnConnected(JsonObject& root) {
root["ntpServer"] = getSetting("ntpServer", NTP_SERVER);
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
@ -116,10 +122,6 @@ void _ntpReport() {
_ntp_report = false;
#if WEB_SUPPORT
wsSend(_ntpWebSocketOnSend);
#endif
if (ntpSynced()) {
time_t t = now();
DEBUG_MSG_P(PSTR("[NTP] UTC Time : %s\n"), ntpDateTime(ntpLocal2UTC(t)).c_str());
@ -232,14 +234,12 @@ void ntpSetup() {
NTPw.onNTPSyncEvent([](NTPSyncEvent_t error) {
if (error) {
#if WEB_SUPPORT
wsSend_P(PSTR("{\"ntpStatus\": false}"));
#endif
if (error == noResponse) {
DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n"));
} else if (error == invalidAddress) {
DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n"));
}
wsPost(_ntpWebSocketOnData);
} else {
_ntp_report = true;
setTime(NTPw.getLastNTPSync());
@ -255,8 +255,11 @@ void ntpSetup() {
});
#if WEB_SUPPORT
wsOnSendRegister(_ntpWebSocketOnSend);
wsOnReceiveRegister(_ntpWebSocketOnReceive);
wsRegister()
.onVisible(_ntpWebSocketOnVisible)
.onConnected(_ntpWebSocketOnConnected)
.onData(_ntpWebSocketOnData)
.onKeyCheck(_ntpWebSocketOnKeyCheck);
#endif
// Main callbacks


+ 19
- 20
code/espurna/relay.ino View File

@ -203,7 +203,7 @@ void _relayProcess(bool mode) {
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave, save_eeprom);
#if WEB_SUPPORT
wsSend(_relayWebSocketUpdate);
wsPost(_relayWebSocketUpdate);
#endif
}
@ -594,7 +594,7 @@ void _relayConfigure() {
#if WEB_SUPPORT
bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _relayWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "relay", 5) == 0);
}
@ -634,9 +634,7 @@ String _relayFriendlyName(unsigned char i) {
return res;
}
void _relayWebSocketSendRelays() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
void _relayWebSocketSendRelays(JsonObject& root) {
JsonObject& relays = root.createNestedObject("relayConfig");
relays["size"] = relayCount();
@ -671,27 +669,25 @@ void _relayWebSocketSendRelays() {
on_disconnect.add(getSetting("relayOnDisc", i, 0).toInt());
#endif
}
wsSend(root);
}
void _relayWebSocketOnStart(JsonObject& root) {
void _relayWebSocketOnVisible(JsonObject& root) {
if (relayCount() == 0) return;
// Per-relay configuration
_relayWebSocketSendRelays();
// Statuses
_relayWebSocketUpdate(root);
// Options
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
}
root["relayVisible"] = 1;
}
void _relayWebSocketOnConnected(JsonObject& root) {
if (relayCount() == 0) return;
// Per-relay configuration
_relayWebSocketSendRelays(root);
}
@ -705,7 +701,7 @@ void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
if (value == 3) {
wsSend(_relayWebSocketUpdate);
wsPost(_relayWebSocketUpdate);
} else if (value < 3) {
@ -731,9 +727,12 @@ void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
}
void relaySetupWS() {
wsOnSendRegister(_relayWebSocketOnStart);
wsOnActionRegister(_relayWebSocketOnAction);
wsOnReceiveRegister(_relayWebSocketOnReceive);
wsRegister()
.onVisible(_relayWebSocketOnVisible)
.onConnected(_relayWebSocketOnConnected)
.onData(_relayWebSocketUpdate)
.onAction(_relayWebSocketOnAction)
.onKeyCheck(_relayWebSocketOnKeyCheck);
}
#endif // WEB_SUPPORT


+ 24
- 47
code/espurna/rfbridge.ino View File

@ -69,10 +69,6 @@ bool _rfb_receive = false;
bool _rfb_transmit = false;
unsigned char _rfb_repeat = RF_SEND_TIMES;
#if WEB_SUPPORT
Ticker _rfb_sendcodes;
#endif
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
@ -89,11 +85,7 @@ static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
#if WEB_SUPPORT
void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
void _rfbWebSocketSendCodeArray(JsonObject& root, unsigned char start, unsigned char size) {
JsonObject& rfb = root.createNestedObject("rfb");
rfb["size"] = size;
rfb["start"] = start;
@ -105,21 +97,13 @@ void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
on.add(rfbRetrieve(id, true));
off.add(rfbRetrieve(id, false));
}
wsSend(root);
}
void _rfbWebSocketSendCode(unsigned char id) {
_rfbWebSocketSendCodeArray(id, 1);
}
void _rfbWebSocketSendCodes() {
_rfbWebSocketSendCodeArray(0, relayCount());
void _rfbWebSocketOnVisible(JsonObject& root) {
root["rfbVisible"] = 1;
}
void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbVisible"] = 1;
void _rfbWebSocketOnConnected(JsonObject& root) {
root["rfbRepeat"] = getSetting("rfbRepeat", RF_SEND_TIMES).toInt();
root["rfbCount"] = relayCount();
#if RFB_DIRECT
@ -127,7 +111,6 @@ void _rfbWebSocketOnSend(JsonObject& root) {
root["rfbRX"] = getSetting("rfbRX", RFB_RX_PIN).toInt();
root["rfbTX"] = getSetting("rfbTX", RFB_TX_PIN).toInt();
#endif
_rfb_sendcodes.once_ms(1000, _rfbWebSocketSendCodes);
}
void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
@ -136,10 +119,14 @@ void _rfbWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
if (strcmp(action, "rfbsend") == 0) rfbStore(data["id"], data["status"], data["data"].as<const char*>());
}
bool _rfbWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "rfb", 3) == 0);
}
void _rfbWebSocketOnData(JsonObject& root) {
_rfbWebSocketSendCodeArray(root, 0, relayCount());
}
#endif // WEB_SUPPORT
/*
@ -267,9 +254,6 @@ void _rfbDecode() {
if (action == RF_CODE_LEARN_KO) {
_rfbAck();
DEBUG_MSG_P(PSTR("[RF] Learn timeout\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"rfbTimeout\"}"));
#endif
}
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
@ -288,7 +272,9 @@ void _rfbDecode() {
// Websocket update
#if WEB_SUPPORT
_rfbWebSocketSendCode(_learnId);
wsPost([](JsonObject& root) {
_rfbWebSocketSendCodeArray(root, _learnId, 1);
});
#endif
}
@ -518,18 +504,6 @@ void _rfbReceive() {
#endif // RFB_DIRECT
void _rfbLearn() {
_rfbLearnImpl();
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0);
wsSend(buffer);
#endif
}
#if MQTT_SUPPORT
void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
@ -564,7 +538,7 @@ void _rfbMqttCallback(unsigned int type, const char * topic, const char * payloa
return;
}
_learnStatus = (char)payload[0] != '0';
_rfbLearn();
_rfbLearnImpl();
return;
}
@ -615,7 +589,7 @@ void _rfbAPISetup() {
tok = strtok(NULL, ",");
if (NULL == tok) return;
_learnStatus = (char) tok[0] != '0';
_rfbLearn();
_rfbLearnImpl();
}
);
@ -735,7 +709,7 @@ void rfbStatus(unsigned char id, bool status) {
void rfbLearn(unsigned char id, bool status) {
_learnId = id;
_learnStatus = status;
_rfbLearn();
_rfbLearnImpl();
}
void rfbForget(unsigned char id, bool status) {
@ -746,9 +720,9 @@ void rfbForget(unsigned char id, bool status) {
// Websocket update
#if WEB_SUPPORT
char wsb[100];
snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
wsPost([id](JsonObject& root) {
_rfbWebSocketSendCodeArray(root, id, 1);
});
#endif
}
@ -768,9 +742,12 @@ void rfbSetup() {
#endif
#if WEB_SUPPORT
wsOnSendRegister(_rfbWebSocketOnSend);
wsOnActionRegister(_rfbWebSocketOnAction);
wsOnReceiveRegister(_rfbWebSocketOnReceive);
wsRegister()
.onVisible(_rfbWebSocketOnVisible)
.onConnected(_rfbWebSocketOnConnected)
.onData(_rfbWebSocketOnData)
.onAction(_rfbWebSocketOnAction)
.onKeyCheck(_rfbWebSocketOnKeyCheck);
#endif
#if TERMINAL_SUPPORT


+ 6
- 5
code/espurna/rfm69.ino View File

@ -35,7 +35,7 @@ unsigned long _rfm69_packet_count;
#if WEB_SUPPORT
void _rfm69WebSocketOnSend(JsonObject& root) {
void _rfm69WebSocketOnConnected(JsonObject& root) {
root["rfm69Visible"] = 1;
root["rfm69Topic"] = getSetting("rfm69Topic", RFM69_DEFAULT_TOPIC);
@ -53,7 +53,7 @@ void _rfm69WebSocketOnSend(JsonObject& root) {
}
bool _rfm69WebSocketOnReceive(const char * key, JsonVariant& value) {
bool _rfm69WebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "rfm69", 5) == 0) return true;
if (strncmp(key, "node", 4) == 0) return true;
if (strncmp(key, "key", 3) == 0) return true;
@ -269,9 +269,10 @@ void rfm69Setup() {
DEBUG_MSG_P(PSTR("[RFM69] Promiscuous mode %s\n"), RFM69_PROMISCUOUS ? "ON" : "OFF");
#if WEB_SUPPORT
wsOnSendRegister(_rfm69WebSocketOnSend);
wsOnReceiveRegister(_rfm69WebSocketOnReceive);
wsOnActionRegister(_rfm69WebSocketOnAction);
wsRegister()
.onConnected(_rfm69WebSocketOnConnected)
.onAction(_rfm69WebSocketOnAction)
.onKeyCheck(_rfm69WebSocketOnKeyCheck);
#endif
// Main callbacks


+ 11
- 5
code/espurna/scheduler.ino View File

@ -15,15 +15,19 @@ Adapted by Xose Pérez <xose dot perez at gmail dot com>
#if WEB_SUPPORT
bool _schWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _schWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "sch", 3) == 0);
}
void _schWebSocketOnSend(JsonObject &root){
void _schWebSocketOnVisible(JsonObject& root) {
if (!relayCount()) return;
root["schVisible"] = 1;
}
void _schWebSocketOnConnected(JsonObject &root){
if (!relayCount()) return;
root["schVisible"] = 1;
root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES;
JsonObject &schedules = root.createNestedObject("schedules");
@ -229,8 +233,10 @@ void schSetup() {
// Update websocket clients
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnReceiveRegister(_schWebSocketOnReceive);
wsRegister()
.onVisible(_schWebSocketOnVisible)
.onConnected(_schWebSocketOnConnected)
.onKeyCheck(_schWebSocketOnKeyCheck);
#endif
// Main callbacks


+ 68
- 33
code/espurna/sensor.ino View File

@ -111,32 +111,46 @@ double _magnitudeProcess(unsigned char type, unsigned char decimals, double valu
#if WEB_SUPPORT
//void _sensorWebSocketMagnitudes(JsonObject& root, const String& ws_name, const String& conf_name) {
template<typename T> void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
String ws_name = String(prefix);
ws_name.concat("Magnitudes");
const String ws_name = String(prefix) + "Magnitudes";
// config uses <prefix>Magnitude<index> (cut 's')
String conf_name = ws_name.substring(0, ws_name.length() - 1);
const String conf_name = ws_name.substring(0, ws_name.length() - 1);
JsonObject& list = root.createNestedObject(ws_name);
list["size"] = magnitudeCount();
JsonArray& name = list.createNestedArray("name");
//JsonArray& name = list.createNestedArray("name");
JsonArray& type = list.createNestedArray("type");
JsonArray& index = list.createNestedArray("index");
JsonArray& idx = list.createNestedArray("idx");
for (unsigned char i=0; i<magnitudeCount(); ++i) {
name.add(magnitudeName(i));
//name.add(magnitudeName(i));
type.add(magnitudeType(i));
index.add(magnitudeIndex(i));
idx.add(getSetting(conf_name, i, 0).toInt());
}
}
bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
/*
template<typename T> void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
const String ws_name = String(prefix) + "Magnitudes";
// config uses <prefix>Magnitude<index> (cut 's')
const String conf_name = ws_name.substring(0, ws_name.length() - 1);
_sensorWebSocketMagnitudes(root, ws_name, conf_name);
}
*/
bool _sensorWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "pwr", 3) == 0) return true;
if (strncmp(key, "sns", 3) == 0) return true;
if (strncmp(key, "tmp", 3) == 0) return true;
@ -146,21 +160,28 @@ bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) {
return false;
}
void _sensorWebSocketSendData(JsonObject& root) {
void _sensorWebSocketOnVisible(JsonObject& root) {
char buffer[10];
bool hasTemperature = false;
bool hasHumidity = false;
bool hasMICS = false;
root["snsVisible"] = 1;
JsonObject& magnitudes = root.createNestedObject("magnitudes");
for (auto& magnitude : _magnitudes) {
if (magnitude.type == MAGNITUDE_TEMPERATURE) root["temperatureVisible"] = 1;
if (magnitude.type == MAGNITUDE_HUMIDITY) root["humidityVisible"] = 1;
#if MICS2710_SUPPORT || MICS5525_SUPPORT
if (magnitude.type == MAGNITUDE_CO || magnitude.type == MAGNITUDE_NO2) root["micsVisible"] = 1;
#endif
}
}
void _sensorWebSocketMagnitudesConfig(JsonObject& root) {
JsonObject& magnitudes = root.createNestedObject("magnitudesConfig");
uint8_t size = 0;
JsonArray& index = magnitudes.createNestedArray("index");
JsonArray& type = magnitudes.createNestedArray("type");
JsonArray& value = magnitudes.createNestedArray("value");
JsonArray& units = magnitudes.createNestedArray("units");
JsonArray& error = magnitudes.createNestedArray("error");
JsonArray& description = magnitudes.createNestedArray("description");
for (unsigned char i=0; i<magnitudeCount(); i++) {
@ -169,14 +190,9 @@ void _sensorWebSocketSendData(JsonObject& root) {
if (magnitude.type == MAGNITUDE_EVENT) continue;
++size;
double value_show = _magnitudeProcess(magnitude.type, magnitude.decimals, magnitude.last);
dtostrf(value_show, 1-sizeof(buffer), magnitude.decimals, buffer);
index.add<uint8_t>(magnitude.global);
type.add<uint8_t>(magnitude.type);
value.add(buffer);
units.add(magnitudeUnits(magnitude.type));
error.add(magnitude.sensor->error());
if (magnitude.type == MAGNITUDE_ENERGY) {
if (_sensor_energy_reset_ts.length() == 0) _sensorResetTS();
@ -185,22 +201,39 @@ void _sensorWebSocketSendData(JsonObject& root) {
description.add(magnitude.sensor->slot(magnitude.local));
}
if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true;
if (magnitude.type == MAGNITUDE_HUMIDITY) hasHumidity = true;
#if MICS2710_SUPPORT || MICS5525_SUPPORT
if (magnitude.type == MAGNITUDE_CO || magnitude.type == MAGNITUDE_NO2) hasMICS = true;
#endif
}
magnitudes["size"] = size;
if (hasTemperature) root["temperatureVisible"] = 1;
if (hasHumidity) root["humidityVisible"] = 1;
if (hasMICS) root["micsVisible"] = 1;
}
void _sensorWebSocketSendData(JsonObject& root) {
char buffer[10];
JsonObject& magnitudes = root.createNestedObject("magnitudes");
uint8_t size = 0;
JsonArray& value = magnitudes.createNestedArray("value");
JsonArray& error = magnitudes.createNestedArray("error");
for (unsigned char i=0; i<magnitudeCount(); i++) {
sensor_magnitude_t magnitude = _magnitudes[i];
if (magnitude.type == MAGNITUDE_EVENT) continue;
++size;
double value_show = _magnitudeProcess(magnitude.type, magnitude.decimals, magnitude.last);
dtostrf(value_show, 1-sizeof(buffer), magnitude.decimals, buffer);
value.add(buffer);
error.add(magnitude.sensor->error());
}
magnitudes["size"] = size;
}
void _sensorWebSocketStart(JsonObject& root) {
void _sensorWebSocketOnConnected(JsonObject& root) {
for (unsigned char i=0; i<_sensors.size(); i++) {
@ -257,7 +290,6 @@ void _sensorWebSocketStart(JsonObject& root) {
}
if (magnitudeCount()) {
root["snsVisible"] = 1;
//root["apiRealTime"] = _sensor_realtime;
root["pwrUnits"] = _sensor_power_units;
root["eneUnits"] = _sensor_energy_units;
@ -267,6 +299,7 @@ void _sensorWebSocketStart(JsonObject& root) {
root["snsRead"] = _sensor_read_interval / 1000;
root["snsReport"] = _sensor_report_every;
root["snsSave"] = _sensor_save_every;
_sensorWebSocketMagnitudesConfig(root);
}
/*
@ -1601,9 +1634,11 @@ void sensorSetup() {
// Websockets
#if WEB_SUPPORT
wsOnSendRegister(_sensorWebSocketStart);
wsOnReceiveRegister(_sensorWebSocketOnReceive);
wsOnSendRegister(_sensorWebSocketSendData);
wsRegister()
.onVisible(_sensorWebSocketOnVisible)
.onConnected(_sensorWebSocketOnConnected)
.onData(_sensorWebSocketSendData)
.onKeyCheck(_sensorWebSocketOnKeyCheck);
#endif
// API
@ -1761,7 +1796,7 @@ void sensorLoop() {
_sensorPost();
#if WEB_SUPPORT
wsSend(_sensorWebSocketSendData);
wsPost(_sensorWebSocketSendData);
#endif
#if THINGSPEAK_SUPPORT


+ 17
- 0
code/espurna/settings.ino View File

@ -215,6 +215,23 @@ bool settingsRestoreJson(JsonObject& data) {
}
bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024) {
// XXX: as of right now, arduinojson cannot trigger callbacks for each key individually
// Manually separating kv pairs can allow to parse only a small chunk, since we know that there is only string type used (even with bools / ints). Can be problematic when parsing data that was not generated by us.
// Current parsing method is limited only by keys (~sizeof(uintptr_t) bytes per key, data is not copied when string is non-const)
DynamicJsonBuffer jsonBuffer(json_buffer_size);
JsonObject& root = jsonBuffer.parseObject((char *) json_string);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[SETTINGS] JSON parsing error\n"));
return false;
}
return settingsRestoreJson(root);
}
void settingsGetJson(JsonObject& root) {
// Get sorted list of keys


+ 730
- 728
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 1431
- 1429
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 2
- 2
code/espurna/system.ino View File

@ -163,7 +163,7 @@ void _systemSetupHeartbeat() {
}
#if WEB_SUPPORT
bool _systemWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _systemWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "sys", 3) == 0) return true;
if (strncmp(key, "hb", 2) == 0) return true;
return false;
@ -261,7 +261,7 @@ void systemSetup() {
#endif
#if WEB_SUPPORT
wsOnReceiveRegister(_systemWebSocketOnReceive);
wsRegister().onKeyCheck(_systemWebSocketOnKeyCheck);
#endif
// Init device-specific hardware


+ 6
- 5
code/espurna/telnet.ino View File

@ -31,12 +31,11 @@ bool _telnetClientsAuth[TELNET_MAX_CLIENTS];
#if WEB_SUPPORT
bool _telnetWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _telnetWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "telnet", 6) == 0);
}
void _telnetWebSocketOnSend(JsonObject& root) {
root["telnetVisible"] = 1;
void _telnetWebSocketOnConnected(JsonObject& root) {
root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
root["telnetAuth"] = getSetting("telnetAuth", TELNET_AUTHENTICATION).toInt() == 1;
}
@ -313,8 +312,10 @@ void telnetSetup() {
#endif
#if WEB_SUPPORT
wsOnSendRegister(_telnetWebSocketOnSend);
wsOnReceiveRegister(_telnetWebSocketOnReceive);
wsRegister()
.onVisible([](JsonObject& root) { root["telnetVisible"] = 1; })
.onConnected(_telnetWebSocketOnConnected)
.onKeyCheck(_telnetWebSocketOnKeyCheck);
#endif
espurnaRegisterReload(_telnetConfigure);


+ 7
- 1
code/espurna/terminal.ino View File

@ -192,9 +192,10 @@ void _terminalInitCommand() {
});
terminalRegisterCommand(F("CONFIG"), [](Embedis* e) {
DynamicJsonBuffer jsonBuffer;
DynamicJsonBuffer jsonBuffer(1024);
JsonObject& root = jsonBuffer.createObject();
settingsGetJson(root);
// XXX: replace with streaming
String output;
root.printTo(output);
DEBUG_MSG(output.c_str());
@ -264,6 +265,11 @@ void terminalInject(void *data, size_t len) {
_serial.inject((char *) data, len);
}
void terminalInject(char ch) {
_serial.inject(ch);
}
Stream & terminalSerial() {
return (Stream &) _serial;
}


+ 7
- 6
code/espurna/thermostat.ino View File

@ -298,7 +298,7 @@ void _thermostatReload() {
#if WEB_SUPPORT
//------------------------------------------------------------------------------
void _thermostatWebSocketOnSend(JsonObject& root) {
void _thermostatWebSocketOnConnected(JsonObject& root) {
root["thermostatEnabled"] = thermostatEnabled();
root["thermostatMode"] = thermostatModeCooler();
root["thermostatVisible"] = 1;
@ -328,7 +328,7 @@ void _thermostatWebSocketOnSend(JsonObject& root) {
}
//------------------------------------------------------------------------------
bool _thermostatWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _thermostatWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, NAME_THERMOSTAT_ENABLED, strlen(NAME_THERMOSTAT_ENABLED)) == 0) return true;
if (strncmp(key, NAME_THERMOSTAT_MODE, strlen(NAME_THERMOSTAT_MODE)) == 0) return true;
if (strncmp(key, NAME_TEMP_RANGE_MIN, strlen(NAME_TEMP_RANGE_MIN)) == 0) return true;
@ -358,9 +358,10 @@ void thermostatSetup() {
// Websockets
#if WEB_SUPPORT
wsOnSendRegister(_thermostatWebSocketOnSend);
wsOnReceiveRegister(_thermostatWebSocketOnReceive);
wsOnActionRegister(_thermostatWebSocketOnAction);
wsRegister()
.onConnected(_thermostatWebSocketOnConnected)
.onKeyCheck(_thermostatWebSocketOnKeyCheck)
.onAction(_thermostatWebSocketOnAction);
#endif
espurnaRegisterLoop(thermostatLoop);
@ -822,4 +823,4 @@ void displayLoop() {
}
}
#endif // THERMOSTAT_DISPLAY_SUPPORT
#endif // THERMOSTAT_DISPLAY_SUPPORT

+ 9
- 9
code/espurna/thinkspeak.ino View File

@ -63,13 +63,15 @@ void _tspkBrokerCallback(const unsigned char type, const char * topic, unsigned
#if WEB_SUPPORT
bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _tspkWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
return (strncmp(key, "tspk", 4) == 0);
}
void _tspkWebSocketOnSend(JsonObject& root) {
void _tspkWebSocketOnVisible(JsonObject& root) {
root["tspkVisible"] = static_cast<unsigned char>(haveRelaysOrSensors());
}
unsigned char visible = 0;
void _tspkWebSocketOnConnected(JsonObject& root) {
root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
root["tspkKey"] = getSetting("tspkKey");
@ -79,15 +81,11 @@ void _tspkWebSocketOnSend(JsonObject& root) {
for (byte i=0; i<relayCount(); i++) {
relays.add(getSetting("tspkRelay", i, 0).toInt());
}
if (relayCount() > 0) visible = 1;
#if SENSOR_SUPPORT
_sensorWebSocketMagnitudes(root, "tspk");
visible = visible || (magnitudeCount() > 0);
#endif
root["tspkVisible"] = visible;
}
#endif
@ -386,8 +384,10 @@ void tspkSetup() {
_tspkConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend);
wsOnReceiveRegister(_tspkWebSocketOnReceive);
wsRegister()
.onVisible(_tspkWebSocketOnVisible)
.onConnected(_tspkWebSocketOnConnected)
.onKeyCheck(_tspkWebSocketOnKeyCheck);
#endif
#if BROKER_SUPPORT


+ 9
- 0
code/espurna/utils.ino View File

@ -109,6 +109,15 @@ unsigned long getUptime() {
}
bool haveRelaysOrSensors() {
bool result = false;
result = (relayCount() > 0);
#if SENSOR_SUPPORT
result = result || (magnitudeCount() > 0);
#endif
return result;
}
// -----------------------------------------------------------------------------
// Heartbeat helper
// -----------------------------------------------------------------------------


+ 17
- 12
code/espurna/web.ino View File

@ -52,6 +52,8 @@ bool _webConfigSuccess = false;
std::vector<web_request_callback_f> _web_request_callbacks;
std::vector<web_body_callback_f> _web_body_callbacks;
constexpr const size_t WEB_CONFIG_BUFFER_MAX = 4096;
// -----------------------------------------------------------------------------
// HOOKS
// -----------------------------------------------------------------------------
@ -65,14 +67,17 @@ void _onDiscover(AsyncWebServerRequest *request) {
webLog(request);
AsyncResponseStream *response = request->beginResponseStream("text/json");
AsyncResponseStream *response = request->beginResponseStream("application/json");
const String device = getBoardName();
const String hostname = getSetting("hostname");
DynamicJsonBuffer jsonBuffer;
StaticJsonBuffer<JSON_OBJECT_SIZE(4)> jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
root["hostname"] = getSetting("hostname");
root["device"] = getBoardName();
root["device"] = device.c_str();
root["hostname"] = hostname.c_str();
root.printTo(*response);
request->send(response);
@ -131,9 +136,7 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
// No buffer
if (final && (index == 0)) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) data);
if (root.success()) _webConfigSuccess = settingsRestoreJson(root);
_webConfigSuccess = settingsRestoreJson((char*) data);
return;
}
@ -148,6 +151,12 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
// Copy
if (len > 0) {
if ((_webConfigBuffer->size() + len) > std::min(WEB_CONFIG_BUFFER_MAX, getFreeHeap() - sizeof(std::vector<uint8_t>))) {
delete _webConfigBuffer;
_webConfigBuffer = nullptr;
request->send(500);
return;
}
_webConfigBuffer->reserve(_webConfigBuffer->size() + len);
_webConfigBuffer->insert(_webConfigBuffer->end(), data, data + len);
}
@ -156,11 +165,7 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
if (final) {
_webConfigBuffer->push_back(0);
// Parse JSON
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) _webConfigBuffer->data());
if (root.success()) _webConfigSuccess = settingsRestoreJson(root);
_webConfigSuccess = settingsRestoreJson((char*) _webConfigBuffer->data());
delete _webConfigBuffer;
}


+ 6
- 9
code/espurna/wifi.ino View File

@ -458,7 +458,7 @@ void _wifiInitCommands() {
#if WEB_SUPPORT
bool _wifiWebSocketOnReceive(const char * key, JsonVariant& value) {
bool _wifiWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "wifi", 4) == 0) return true;
if (strncmp(key, "ssid", 4) == 0) return true;
if (strncmp(key, "pass", 4) == 0) return true;
@ -469,7 +469,7 @@ bool _wifiWebSocketOnReceive(const char * key, JsonVariant& value) {
return false;
}
void _wifiWebSocketOnSend(JsonObject& root) {
void _wifiWebSocketOnConnected(JsonObject& root) {
root["maxNetworks"] = WIFI_MAX_NETWORKS;
root["wifiScan"] = getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1;
JsonArray& wifi = root.createNestedArray("wifi");
@ -502,7 +502,6 @@ void wifiDebug(WiFiMode_t modes) {
if (((modes & WIFI_STA) > 0) && ((WiFi.getMode() & WIFI_STA) > 0)) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] ------------------------------------- MODE STA\n"));
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
@ -511,9 +510,7 @@ void wifiDebug(WiFiMode_t modes) {
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST http://%s.local\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] BSSID %s\n"), WiFi.BSSIDstr().c_str());
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
footer = true;
@ -648,9 +645,9 @@ void wifiSetup() {
#endif
#if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend);
wsOnReceiveRegister(_wifiWebSocketOnReceive);
wsOnActionRegister(_wifiWebSocketOnAction);
wsRegister()
.onConnected(_wifiWebSocketOnConnected)
.onKeyCheck(_wifiWebSocketOnKeyCheck);
#endif
#if TERMINAL_SUPPORT


+ 235
- 93
code/espurna/ws.ino View File

@ -18,19 +18,102 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
AsyncWebSocket _ws("/ws");
Ticker _web_defer;
std::vector<ws_on_send_callback_f> _ws_on_send_callbacks;
std::vector<ws_on_action_callback_f> _ws_on_action_callbacks;
std::vector<ws_on_receive_callback_f> _ws_on_receive_callbacks;
// -----------------------------------------------------------------------------
// WS callbacks
// -----------------------------------------------------------------------------
ws_callbacks_t _ws_callbacks;
struct ws_counter_t {
ws_counter_t() : current(0), start(0), stop(0) {}
ws_counter_t(uint32_t start, uint32_t stop) :
current(start), start(start), stop(stop) {}
void reset() {
current = start;
}
void next() {
if (current < stop) {
++current;
}
}
bool done() {
return (current >= stop);
}
uint32_t current;
uint32_t start;
uint32_t stop;
};
struct ws_data_t {
enum mode_t {
SEQUENCE,
ALL
};
ws_data_t(const ws_on_send_callback_f& cb) :
storage(new ws_on_send_callback_list_t {cb}),
client_id(0),
mode(ALL),
callbacks(*storage.get()),
counter(0, 1)
{}
ws_data_t(const uint32_t client_id, const ws_on_send_callback_list_t& callbacks, mode_t mode = SEQUENCE) :
client_id(client_id),
mode(mode),
callbacks(callbacks),
counter(0, callbacks.size())
{}
bool done() {
return counter.done();
}
void sendAll(JsonObject& root) {
while (!counter.done()) counter.next();
for (auto& callback : callbacks) {
callback(root);
}
}
void sendCurrent(JsonObject& root) {
callbacks[counter.current](root);
counter.next();
}
void send(JsonObject& root) {
switch (mode) {
case SEQUENCE: sendCurrent(root); break;
case ALL: sendAll(root); break;
}
}
std::unique_ptr<ws_on_send_callback_list_t> storage;
const uint32_t client_id;
const mode_t mode;
const ws_on_send_callback_list_t& callbacks;
ws_counter_t counter;
};
std::queue<ws_data_t> _ws_client_data;
// -----------------------------------------------------------------------------
// Private methods
// WS authentication
// -----------------------------------------------------------------------------
typedef struct {
struct ws_ticket_t {
IPAddress ip;
unsigned long timestamp = 0;
} ws_ticket_t;
ws_ticket_t _ticket[WS_BUFFER_SIZE];
};
ws_ticket_t _ws_tickets[WS_BUFFER_SIZE];
void _onAuth(AsyncWebServerRequest *request) {
@ -41,15 +124,15 @@ void _onAuth(AsyncWebServerRequest *request) {
unsigned long now = millis();
unsigned short index;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if (_ticket[index].ip == ip) break;
if (_ticket[index].timestamp == 0) break;
if (now - _ticket[index].timestamp > WS_TIMEOUT) break;
if (_ws_tickets[index].ip == ip) break;
if (_ws_tickets[index].timestamp == 0) break;
if (now - _ws_tickets[index].timestamp > WS_TIMEOUT) break;
}
if (index == WS_BUFFER_SIZE) {
request->send(429);
} else {
_ticket[index].ip = ip;
_ticket[index].timestamp = now;
_ws_tickets[index].ip = ip;
_ws_tickets[index].timestamp = now;
request->send(200, "text/plain", "OK");
}
@ -62,7 +145,7 @@ bool _wsAuth(AsyncWebSocketClient * client) {
unsigned short index = 0;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if ((_ticket[index].ip == ip) && (now - _ticket[index].timestamp < WS_TIMEOUT)) break;
if ((_ws_tickets[index].ip == ip) && (now - _ws_tickets[index].timestamp < WS_TIMEOUT)) break;
}
if (index == WS_BUFFER_SIZE) {
@ -73,39 +156,43 @@ bool _wsAuth(AsyncWebSocketClient * client) {
}
// -----------------------------------------------------------------------------
// Debug
// -----------------------------------------------------------------------------
#if DEBUG_WEB_SUPPORT
bool wsDebugSend(const char* prefix, const char* message) {
if (!wsConnected()) return false;
if (getFreeHeap() < (strlen(message) * 3)) return false;
DynamicJsonBuffer jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
JsonObject &weblog = root.createNestedObject("weblog");
// via: https://arduinojson.org/v6/assistant/
// we use 1 object for "weblog", 2nd one for "message". "prefix", optional
StaticJsonBuffer<JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2)> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonObject& weblog = root.createNestedObject("weblog");
weblog.set("message", message);
weblog["message"] = message;
if (prefix && (prefix[0] != '\0')) {
weblog.set("prefix", prefix);
weblog["prefix"] = prefix;
}
// TODO: avoid serializing twice and just measure json ourselves?
//const size_t len = strlen(message) + strlen(prefix)
// + strlen("{\"weblog\":}")
// + strlen("{\"message\":\"\"}")
// + (strlen(prefix) ? strlen("\",\"prefix\":\"\"") : 0);
//wsSend(root, len);
wsSend(root);
return true;
}
#endif
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}"));
if (type == MQTT_DISCONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": false}"));
}
#endif
bool _wsStore(String key, String value) {
// Check the existing setting before saving it
// TODO: this should know of the default values, somehow?
// TODO: move webPort handling somewhere else?
bool _wsStore(const String& key, const String& value) {
// HTTP port
if (key == "webPort") {
if ((value.toInt() == 0) || (value.toInt() == 80)) {
return delSetting(key);
@ -120,7 +207,11 @@ bool _wsStore(String key, String value) {
}
bool _wsStore(String key, JsonArray& value) {
// -----------------------------------------------------------------------------
// Store indexed key (key0, key1, etc.) from array
// -----------------------------------------------------------------------------
bool _wsStore(const String& key, JsonArray& value) {
bool changed = false;
@ -140,6 +231,15 @@ bool _wsStore(String key, JsonArray& value) {
}
bool _wsCheckKey(const String& key, JsonVariant& value) {
for (auto& callback : _ws_callbacks.on_keycheck) {
if (callback(key.c_str(), value)) return true;
// TODO: remove this to call all OnKeyCheckCallbacks with the
// current key/value
}
return false;
}
void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : "");
@ -147,11 +247,22 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
// Get client ID
uint32_t client_id = client->id();
// Check early for empty object / nothing
if ((length == 0) || (length == 1)) {
return;
}
if ((length == 3) && (strcmp((char*) payload, "{}") == 0)) {
return;
}
// Parse JSON input
DynamicJsonBuffer jsonBuffer;
// TODO: json buffer should be pretty efficient with the non-const payload,
// most of the space is taken by the object key references
DynamicJsonBuffer jsonBuffer(512);
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Error parsing data\n"));
DEBUG_MSG_P(PSTR("[WEBSOCKET] JSON parsing error\n"));
wsSend_P(client_id, PSTR("{\"message\": 3}"));
return;
}
@ -184,8 +295,8 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (data.success()) {
// Callbacks
for (unsigned char i = 0; i < _ws_on_action_callbacks.size(); i++) {
(_ws_on_action_callbacks[i])(client_id, action, data);
for (auto& callback : _ws_callbacks.on_action) {
callback(client_id, action, data);
}
// Restore configuration via websockets
@ -237,15 +348,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
continue;
}
// Check if key has to be processed
bool found = false;
for (unsigned char i = 0; i < _ws_on_receive_callbacks.size(); i++) {
found |= (_ws_on_receive_callbacks[i])(key.c_str(), value);
// TODO: remove this to call all OnReceiveCallbacks with the
// current key/value
if (found) break;
}
if (!found) {
if (!_wsCheckKey(key, value)) {
delSetting(key);
continue;
}
@ -312,7 +415,7 @@ void _wsDoUpdate(bool reset = false) {
}
bool _wsOnReceive(const char * key, JsonVariant& value) {
bool _wsOnKeyCheck(const char * key, JsonVariant& value) {
if (strncmp(key, "ws", 2) == 0) return true;
if (strncmp(key, "admin", 5) == 0) return true;
if (strncmp(key, "hostname", 8) == 0) return true;
@ -321,15 +424,9 @@ bool _wsOnReceive(const char * key, JsonVariant& value) {
return false;
}
void _wsOnStart(JsonObject& root) {
void _wsOnConnected(JsonObject& root) {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
@ -342,7 +439,7 @@ void _wsOnStart(JsonObject& root) {
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["bssid"] = WiFi.BSSIDstr();
root["channel"] = WiFi.channel();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
@ -368,6 +465,7 @@ void _wsOnStart(JsonObject& root) {
}
void wsSend(JsonObject& root) {
// TODO: avoid serializing twice?
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
@ -381,6 +479,7 @@ void wsSend(uint32_t client_id, JsonObject& root) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
// TODO: avoid serializing twice?
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
@ -390,27 +489,24 @@ void wsSend(uint32_t client_id, JsonObject& root) {
}
}
void _wsStart(uint32_t client_id) {
#if USE_PASSWORD && WEB_FORCE_PASS_CHANGE
bool changePassword = getAdminPass().equals(ADMIN_PASS);
#else
bool changePassword = false;
#endif
void _wsConnected(uint32_t client_id) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
const bool changePassword = (USE_PASSWORD && WEB_FORCE_PASS_CHANGE)
? getAdminPass().equals(ADMIN_PASS)
: false;
if (changePassword) {
StaticJsonBuffer<JSON_OBJECT_SIZE(1)> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["webMode"] = WEB_MODE_PASSWORD;
wsSend(root);
wsSend(client_id, root);
return;
}
for (auto& callback : _ws_on_send_callbacks) {
callback(root);
}
wsPostAll(client_id, _ws_callbacks.on_visible);
wsPostSequence(client_id, _ws_callbacks.on_connected);
wsPostSequence(client_id, _ws_callbacks.on_data);
wsSend(client_id, root);
}
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
@ -430,8 +526,7 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
IPAddress ip = client->remoteIP();
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());
_wsStart(client->id());
client->_tempObject = new WebSocketIncommingBuffer(&_wsParse, true);
_wsConnected(client->id());
wifiReconnectCheck();
} else if(type == WS_EVT_DISCONNECT) {
@ -449,6 +544,7 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
} else if(type == WS_EVT_DATA) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u data(%u): %s\n"), client->id(), len, len ? (char*) data : "");
if (!client->_tempObject) return;
WebSocketIncommingBuffer *buffer = (WebSocketIncommingBuffer *)client->_tempObject;
AwsFrameInfo * info = (AwsFrameInfo*)arg;
buffer->data_event(client, info, data, len);
@ -457,9 +553,52 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
}
// TODO: make this generic loop method to queue important ws messages?
// or, if something uses ticker / async ctx to send messages,
// it needs a retry mechanism built into the callback object
void _wsHandleClientData() {
if (_ws_client_data.empty()) return;
auto& data = _ws_client_data.front();
AsyncWebSocketClient* ws_client = _ws.client(data.client_id);
if (!ws_client) {
_ws_client_data.pop();
return;
}
// wait until we can send the next batch of messages
// XXX: enforce that callbacks send only one message per iteration
if (ws_client->queueIsFull()) {
return;
}
// XXX: block allocation will try to create *2 next time,
// likely failing and causing wsSend to reference empty objects
// XXX: arduinojson6 will not do this, but we may need to use per-callback buffers
constexpr const size_t BUFFER_SIZE = 3192;
DynamicJsonBuffer jsonBuffer(BUFFER_SIZE);
JsonObject& root = jsonBuffer.createObject();
data.send(root);
if (data.client_id) {
wsSend(data.client_id, root);
} else {
wsSend(root);
}
yield();
if (data.done()) {
// push the queue and finally allow incoming messages
_ws_client_data.pop();
ws_client->_tempObject = new WebSocketIncommingBuffer(_wsParse, true);
}
}
void _wsLoop() {
if (!wsConnected()) return;
_wsDoUpdate();
_wsHandleClientData();
}
// -----------------------------------------------------------------------------
@ -474,21 +613,13 @@ bool wsConnected(uint32_t client_id) {
return _ws.hasClient(client_id);
}
void wsOnSendRegister(ws_on_send_callback_f callback) {
_ws_on_send_callbacks.push_back(callback);
}
void wsOnReceiveRegister(ws_on_receive_callback_f callback) {
_ws_on_receive_callbacks.push_back(callback);
}
void wsOnActionRegister(ws_on_action_callback_f callback) {
_ws_on_action_callbacks.push_back(callback);
ws_callbacks_t& wsRegister() {
return _ws_callbacks;
}
void wsSend(ws_on_send_callback_f callback) {
if (_ws.count() > 0) {
DynamicJsonBuffer jsonBuffer;
DynamicJsonBuffer jsonBuffer(512);
JsonObject& root = jsonBuffer.createObject();
callback(root);
@ -514,17 +645,10 @@ void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
AsyncWebSocketClient* client = _ws.client(client_id);
if (client == nullptr) return;
DynamicJsonBuffer jsonBuffer;
DynamicJsonBuffer jsonBuffer(512);
JsonObject& root = jsonBuffer.createObject();
callback(root);
size_t len = root.measureLength();
AsyncWebSocketMessageBuffer* buffer = _ws.makeBuffer(len);
if (buffer) {
root.printTo(reinterpret_cast<char*>(buffer->get()), len + 1);
client->text(buffer);
}
wsSend(client_id, root);
}
void wsSend(uint32_t client_id, const char * payload) {
@ -537,6 +661,26 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_ws.text(client_id, buffer);
}
void wsPost(const ws_on_send_callback_f& cb) {
_ws_client_data.emplace(cb);
}
void wsPostAll(uint32_t client_id, const ws_on_send_callback_list_t& cbs) {
_ws_client_data.emplace(client_id, cbs, ws_data_t::ALL);
}
void wsPostAll(const ws_on_send_callback_list_t& cbs) {
_ws_client_data.emplace(0, cbs, ws_data_t::ALL);
}
void wsPostSequence(uint32_t client_id, const ws_on_send_callback_list_t& cbs) {
_ws_client_data.emplace(client_id, cbs, ws_data_t::SEQUENCE);
}
void wsPostSequence(const ws_on_send_callback_list_t& cbs) {
_ws_client_data.emplace(0, cbs, ws_data_t::SEQUENCE);
}
void wsSetup() {
_ws.onEvent(_wsEvent);
@ -551,12 +695,10 @@ void wsSetup() {
webServer()->on("/auth", HTTP_GET, _onAuth);
#if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback);
#endif
wsRegister()
.onConnected(_wsOnConnected)
.onKeyCheck(_wsOnKeyCheck);
wsOnSendRegister(_wsOnStart);
wsOnReceiveRegister(_wsOnReceive);
espurnaRegisterLoop(_wsLoop);
}


+ 19
- 5
code/html/custom.js View File

@ -23,6 +23,10 @@ var packets;
var filters = [];
<!-- endRemoveIf(!rfm69)-->
<!-- removeIf(!sensor)-->
var magnitudes = [];
<!-- endRemoveIf(!sensor)-->
// -----------------------------------------------------------------------------
// Messages
// -----------------------------------------------------------------------------
@ -834,7 +838,7 @@ function createMagnitudeList(data, container, template_name) {
for (var i=0; i<size; ++i) {
var line = $(template).clone();
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.name[i]);
$("div.hint", line).html(magnitudes[i].description);
$("input", line).attr("tabindex", 40 + i).val(data.idx[i]);
line.appendTo("#" + container);
}
@ -1063,9 +1067,16 @@ function initMagnitudes(data) {
var template = $("#magnitudeTemplate").children();
for (var i=0; i<size; ++i) {
var magnitude = {
"name": magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10),
"units": data.units[i],
"description": data.description[i]
};
magnitudes.push(magnitude);
var line = $(template).clone();
$("label", line).html(magnitudeType(data.type[i]) + " #" + parseInt(data.index[i], 10));
$("div.hint", line).html(data.description[i]);
$("label", line).html(magnitude.name);
$("div.hint", line).html(magnitude.description);
$("input", line).attr("data", i);
line.appendTo("#magnitudes");
}
@ -1471,12 +1482,15 @@ function processData(data) {
<!-- removeIf(!sensor)-->
if ("magnitudes" === key) {
if ("magnitudesConfig" === key) {
initMagnitudes(value);
}
if ("magnitudes" === key) {
for (var i=0; i<value.size; ++i) {
var error = value.error[i] || 0;
var text = (0 === error) ?
value.value[i] + value.units[i] :
value.value[i] + magnitudes[i].units :
magnitudeError(error);
var element = $("input[name='magnitude'][data='" + i + "']");
element.val(text);


+ 1
- 1
code/platformio.ini View File

@ -107,7 +107,7 @@ lib_deps =
Embedis
https://github.com/plerup/espsoftwareserial#3.4.1
https://github.com/me-no-dev/ESPAsyncTCP#7e9ed22
https://github.com/me-no-dev/ESPAsyncWebServer#05306e4
https://github.com/me-no-dev/ESPAsyncWebServer#b0c6144
https://bitbucket.org/xoseperez/fauxmoesp.git#3.1.0
https://github.com/xoseperez/hlw8012.git#1.1.0
https://github.com/markszabo/IRremoteESP8266#v2.2.0


Loading…
Cancel
Save