diff --git a/code/espurna/config/deprecated.h b/code/espurna/config/deprecated.h index 563540d3..9480a0cf 100644 --- a/code/espurna/config/deprecated.h +++ b/code/espurna/config/deprecated.h @@ -24,4 +24,21 @@ #ifdef EVENTS_PIN #warning EVENTS_PIN is deprecated! Please use EVENTS1_PIN instead #define EVENTS1_PIN EVENTS_PIN -#endif \ No newline at end of file +#endif + +// 1.13.6 unifies mqtt payload options +#ifdef HOMEASSISTANT_PAYLOAD_ON +#warning HOMEASSISTANT_PAYLOAD_ON is deprecated! Global RELAY_MQTT_ON is used instead +#endif + +#ifdef HOMEASSISTANT_PAYLOAD_OFF +#warning HOMEASSISTANT_PAYLOAD_OFF is deprecated! Global RELAY_MQTT_OFF is used instead +#endif + +#ifdef HOMEASSISTANT_PAYLOAD_AVAILABLE +#warning HOMEASSISTANT_PAYLOAD_AVAILABLE is deprecated! Global MQTT_STATUS_ONLINE is used instead +#endif + +#ifdef HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE +#warning HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE is deprecated! Global MQTT_STATUS_OFFLINE is used instead +#endif diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 475594be..df05143b 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -408,6 +408,7 @@ #ifndef RELAY_MQTT_ON #define RELAY_MQTT_ON "1" #endif + #ifndef RELAY_MQTT_OFF #define RELAY_MQTT_OFF "0" #endif @@ -1076,15 +1077,16 @@ #define MQTT_TOPIC_NOTIFY_TEMP_RANGE_MAX "notify_temp_range_max" +#ifndef MQTT_STATUS_ONLINE #define MQTT_STATUS_ONLINE "1" // Value for the device ON message +#endif + #ifndef MQTT_STATUS_OFFLINE #define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) #endif #define MQTT_ACTION_RESET "reboot" // RESET MQTT topic particle -#define MQTT_MESSAGE_ID_SHIFT 1000 // Store MQTT message id into EEPROM every these many - // Custom get and set postfixes // Use something like "/status" or "/set", with leading slash // Since 1.9.0 the default value is "" for getter and "/set" for setter @@ -1286,22 +1288,6 @@ #define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix #endif -#ifndef HOMEASSISTANT_PAYLOAD_ON -#define HOMEASSISTANT_PAYLOAD_ON "1" // Payload for ON and available messages -#endif - -#ifndef HOMEASSISTANT_PAYLOAD_OFF -#define HOMEASSISTANT_PAYLOAD_OFF "0" // Payload for OFF and unavailable messages -#endif - -#ifndef HOMEASSISTANT_PAYLOAD_AVAILABLE -#define HOMEASSISTANT_PAYLOAD_AVAILABLE "1" // Payload for available messages -#endif - -#ifndef HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE -#define HOMEASSISTANT_PAYLOAD_NOT_AVAILABLE "0" // Payload for available messages -#endif - // ----------------------------------------------------------------------------- // INFLUXDB // ----------------------------------------------------------------------------- diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 7c53b3cb..72b68ddf 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -194,10 +194,28 @@ void lightChannel(unsigned char id, unsigned char value); using mqtt_callback_f = std::function; -#if MQTT_SUPPORT - void mqttRegister(mqtt_callback_f callback); - String mqttMagnitude(char * topic); -#endif +void mqttRegister(mqtt_callback_f callback); + +String mqttTopic(const char * magnitude, bool is_set); +String mqttTopic(const char * magnitude, unsigned int index, bool is_set); + +String mqttMagnitude(char * topic); + +void mqttSendRaw(const char * topic, const char * message, bool retain); +void mqttSendRaw(const char * topic, const char * message); + +void mqttSend(const char * topic, const char * message, bool force, bool retain); +void mqttSend(const char * topic, const char * message, bool force); +void mqttSend(const char * topic, const char * message); + +void mqttSend(const char * topic, unsigned int index, const char * message, bool force); +void mqttSend(const char * topic, unsigned int index, const char * message); + +const String& mqttPayloadOnline(); +const String& mqttPayloadOffline(); +const char* mqttPayloadStatus(bool status); + +void mqttSendStatus(); // ----------------------------------------------------------------------------- // OTA @@ -236,10 +254,25 @@ typedef struct { // ----------------------------------------------------------------------------- #include +bool relayStatus(unsigned char id, bool status, bool report, bool group_report); +bool relayStatus(unsigned char id, bool status); +bool relayStatus(unsigned char id); + +void relayToggle(unsigned char id, bool report, bool group_report); +void relayToggle(unsigned char id); + +unsigned char relayCount(); +unsigned char relayParsePayload(const char * payload); + +const String& relayPayloadOn(); +const String& relayPayloadOff(); +const char* relayPayload(bool status); + // ----------------------------------------------------------------------------- // Settings // ----------------------------------------------------------------------------- #include + template bool setSetting(const String& key, T value); template bool setSetting(const String& key, unsigned int index, T value); template String getSetting(const String& key, T defaultValue); @@ -247,6 +280,17 @@ template String getSetting(const String& key, unsigned int index, T void settingsGetJson(JsonObject& data); bool settingsRestoreJson(JsonObject& data); +struct settings_cfg_t { + String& setting; + const char* key; + const char* default_value; +}; + +using settings_filter_t = std::function; +using settings_cfg_list_t = std::initializer_list; + +void settingsProcessConfig(const settings_cfg_list_t& config, settings_filter_t filter = nullptr); + // ----------------------------------------------------------------------------- // Terminal // ----------------------------------------------------------------------------- @@ -302,9 +346,9 @@ struct ws_data_t; struct ws_debug_t; struct ws_callbacks_t; -using ws_on_send_callback_f = std::function; -using ws_on_action_callback_f = std::function; -using ws_on_keycheck_callback_f = std::function; +using ws_on_send_callback_f = std::function; +using ws_on_action_callback_f = std::function; +using ws_on_keycheck_callback_f = std::function; using ws_on_send_callback_list_t = std::vector; using ws_on_action_callback_list_t = std::vector; @@ -329,27 +373,28 @@ using ws_on_keycheck_callback_list_t = std::vector; ws_callbacks_t& wsRegister(); void wsSetup(); - void wsSend(uint32_t, const char*); - void wsSend(uint32_t, JsonObject&); - void wsSend(JsonObject&); - void wsSend(ws_on_send_callback_f); + void wsSend(uint32_t client_id, const char* data); + void wsSend(uint32_t client_id, JsonObject& root); + void wsSend(JsonObject& root); + void wsSend(ws_on_send_callback_f callback); - void wsSend_P(PGM_P); - void wsSend_P(uint32_t, PGM_P); + void wsSend_P(PGM_P data); + void wsSend_P(uint32_t client_id, PGM_P data); - void wsPost(const ws_on_send_callback_f&); - void wsPost(const ws_on_send_callback_list_t&); - void wsPost(uint32_t, const ws_on_send_callback_list_t&); + void wsPost(const ws_on_send_callback_f& callback); + void wsPost(const ws_on_send_callback_list_t& callbacks); + void wsPost(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); - void wsPostAll(uint32_t, const ws_on_send_callback_list_t&); - void wsPostAll(const ws_on_send_callback_list_t&); + void wsPostAll(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); + void wsPostAll(const ws_on_send_callback_list_t& callbacks); - void wsPostSequence(uint32_t, const ws_on_send_callback_list_t&); - void wsPostSequence(const ws_on_send_callback_list_t&); + void wsPostSequence(uint32_t client_id, const ws_on_send_callback_list_t& callbacks); + void wsPostSequence(uint32_t client_id, ws_on_send_callback_list_t&& callbacks); + void wsPostSequence(const ws_on_send_callback_list_t& callbacks); bool wsConnected(); - bool wsConnected(uint32_t); - bool wsDebugSend(const char*, const char*); + bool wsConnected(uint32_t client_id); + bool wsDebugSend(const char* prefix, const char* message); #endif // ----------------------------------------------------------------------------- diff --git a/code/espurna/homeassistant.ino b/code/espurna/homeassistant.ino index a3f2f691..d1f3bf45 100644 --- a/code/espurna/homeassistant.ino +++ b/code/espurna/homeassistant.ino @@ -9,7 +9,6 @@ Copyright (C) 2017-2019 by Xose PĂ©rez #if HOMEASSISTANT_SUPPORT #include -#include bool _haEnabled = false; bool _haSendFlag = false; @@ -18,7 +17,29 @@ bool _haSendFlag = false; // UTILS // ----------------------------------------------------------------------------- -String _haFixName(String name) { +// per yaml 1.1 spec, following scalars are converted to bool. we want the string, so quoting the output +// y|Y|yes|Yes|YES|n|N|no|No|NO |true|True|TRUE|false|False|FALSE |on|On|ON|off|Off|OFF +String _haFixPayload(const String& value) { + if (value.equalsIgnoreCase("y") + || value.equalsIgnoreCase("n") + || value.equalsIgnoreCase("yes") + || value.equalsIgnoreCase("no") + || value.equalsIgnoreCase("true") + || value.equalsIgnoreCase("false") + || value.equalsIgnoreCase("on") + || value.equalsIgnoreCase("off") + ) { + String temp; + temp.reserve(value.length() + 2); + temp = "\""; + temp += value; + temp += "\""; + return temp; + } + return value; +} + +String& _haFixName(String& name) { for (unsigned char i=0; i printer, bool wrapJson = false) { +constexpr const size_t HA_YAML_BUFFER_SIZE = 1024; - constexpr const size_t BUFFER_SIZE = 1024; +void _haSwitchYaml(unsigned char index, JsonObject& root) { String output; - output.reserve(BUFFER_SIZE + 64); - DynamicJsonBuffer jsonBuffer(BUFFER_SIZE); + output.reserve(HA_YAML_BUFFER_SIZE); - for (unsigned char i=0; i()); + } else { output += kv.value.as(); - output += "\n"; - } - output += " "; - - if (wrapJson) { - output += "\"}"; } - - jsonBuffer.clear(); - printer(output); - output = ""; - + output += "\n"; } + output += " "; - #if SENSOR_SUPPORT - - for (unsigned char i=0; i(); - value.replace("%", "'%'"); - output += kv.key; - output += ": "; - output += value; - output += "\n"; - } - output += " "; + String output; + output.reserve(HA_YAML_BUFFER_SIZE); - if (wrapJson) { - output += "\"}"; - } + JsonObject& config = root.createNestedObject("config"); + _haSendMagnitude(index, config); - jsonBuffer.clear(); - printer(output); - output = ""; + if (index == 0) output += "\n\nsensor:"; + output += "\n"; + bool first = true; + for (auto kv : config) { + if (first) { + output += " - "; + first = false; + } else { + output += " "; } + String value = kv.value.as(); + value.replace("%", "'%'"); + output += kv.key; + output += ": "; + output += value; + output += "\n"; + } + output += " "; + + root.remove("config"); + root["haConfig"] = output; - #endif } +#endif // SENSOR_SUPPORT + void _haGetDeviceConfig(JsonObject& config) { - String identifier = getIdentifier(); - - config.createNestedArray("identifiers").add(identifier); + config.createNestedArray("identifiers").add(getIdentifier()); config["name"] = getSetting("desc", getSetting("hostname")); - config["manufacturer"] = String(MANUFACTURER); - config["model"] = String(DEVICE); - config["sw_version"] = String(APP_NAME) + " " + String(APP_VERSION) + " (" + getCoreVersion() + ")"; + config["manufacturer"] = MANUFACTURER; + config["model"] = DEVICE; + config["sw_version"] = String(APP_NAME) + " " + APP_VERSION + " (" + getCoreVersion() + ")"; } void _haSend() { @@ -315,8 +318,6 @@ void _haConfigure() { #if WEB_SUPPORT -std::queue _ha_send_config; - bool _haWebSocketOnKeyCheck(const char * key, JsonVariant& value) { return (strncmp(key, "ha", 2) == 0); } @@ -332,20 +333,53 @@ void _haWebSocketOnConnected(JsonObject& root) { void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { if (strcmp(action, "haconfig") == 0) { - _ha_send_config.push(client_id); + ws_on_send_callback_list_t callbacks; + #if SENSOR_SUPPORT + callbacks.reserve(magnitudeCount() + relayCount()); + #else + callbacks.reserve(relayCount()); + #endif // SENSOR_SUPPORT + { + for (unsigned char idx=0; idx().c_str()); + } + #if SENSOR_SUPPORT + for (unsigned char idx=0; idx().c_str()); + } + #endif // SENSOR_SUPPORT DEBUG_MSG("\n"); terminalOK(); }); @@ -374,23 +408,6 @@ void _haInitCommands() { // ----------------------------------------------------------------------------- -#if WEB_SUPPORT -void _haLoop() { - if (_ha_send_config.empty()) return; - - uint32_t client_id = _ha_send_config.front(); - _ha_send_config.pop(); - - if (!wsConnected(client_id)) return; - - // TODO check wsConnected after each "printer" call? - _haDumpConfig([client_id](String& output) { - wsSend(client_id, output.c_str()); - yield(); - }, true); -} -#endif - void haSetup() { _haConfigure(); @@ -401,7 +418,6 @@ void haSetup() { .onConnected(_haWebSocketOnConnected) .onAction(_haWebSocketOnAction) .onKeyCheck(_haWebSocketOnKeyCheck); - espurnaRegisterLoop(_haLoop); #endif #if TERMINAL_SUPPORT diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index 9025d6e4..4f876581 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -74,6 +74,9 @@ String _mqtt_server; uint16_t _mqtt_port; String _mqtt_clientid; +String _mqtt_payload_online; +String _mqtt_payload_offline; + std::vector _mqtt_callbacks; struct mqtt_message_t { @@ -133,7 +136,7 @@ void _mqttSetupAsyncClient(bool secure = false) { _mqtt.setClientId(_mqtt_clientid.c_str()); _mqtt.setKeepAlive(_mqtt_keepalive); _mqtt.setCleanSession(false); - _mqtt.setWill(_mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + _mqtt.setWill(_mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str()); if (_mqtt_user.length() && _mqtt_pass.length()) { DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); @@ -172,7 +175,7 @@ bool _mqttConnectSyncClient(bool secure = false) { #if MQTT_LIBRARY == MQTT_LIBRARY_ARDUINOMQTT _mqtt.begin(_mqtt_server.c_str(), _mqtt_port, (secure ? _mqtt_client_secure->get() : _mqtt_client)); - _mqtt.setWill(_mqtt_will.c_str(), MQTT_STATUS_OFFLINE, _mqtt_qos, _mqtt_retain); + _mqtt.setWill(_mqtt_will.c_str(), _mqtt_payload_offline.c_str(), _mqtt_qos, _mqtt_retain); result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str()); #elif MQTT_LIBRARY == MQTT_LIBRARY_PUBSUBCLIENT _mqtt.setClient(secure ? _mqtt_client_secure->get() : _mqtt_client); @@ -180,9 +183,9 @@ bool _mqttConnectSyncClient(bool secure = false) { if (_mqtt_user.length() && _mqtt_pass.length()) { DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user.c_str()); - result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_user.c_str(), _mqtt_pass.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str()); } else { - result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, MQTT_STATUS_OFFLINE); + result = _mqtt.connect(_mqtt_clientid.c_str(), _mqtt_will.c_str(), _mqtt_qos, _mqtt_retain, _mqtt_payload_offline.c_str()); } #endif @@ -357,6 +360,13 @@ void _mqttConfigure() { _mqttApplyTopic(_mqtt_topic_json, MQTT_TOPIC_JSON); } + // Custom payload strings + settingsProcessConfig({ + {_mqtt_payload_online, "mqttPayloadOnline", MQTT_STATUS_ONLINE}, + {_mqtt_payload_offline, "mqttPayloadOffline", MQTT_STATUS_OFFLINE} + }); + + // Reset reconnect delay to reconnect sooner _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; } @@ -868,6 +878,22 @@ void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) { if (getSetting("mqttServer", MQTT_SERVER).length() == 0) mqttSetBroker(ip, port); } +const String& mqttPayloadOnline() { + return _mqtt_payload_online; +} + +const String& mqttPayloadOffline() { + return _mqtt_payload_offline; +} + +const char* mqttPayloadStatus(bool status) { + return status ? _mqtt_payload_online.c_str() : _mqtt_payload_offline.c_str(); +} + +void mqttSendStatus() { + mqttSend(MQTT_TOPIC_STATUS, _mqtt_payload_online.c_str(), true); +} + // ----------------------------------------------------------------------------- // Initialization // ----------------------------------------------------------------------------- diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index be477f0c..675f898e 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -44,6 +44,13 @@ std::vector _relays; bool _relayRecursive = false; Ticker _relaySaveTicker; +#if MQTT_SUPPORT + +String _relay_mqtt_payload_on; +String _relay_mqtt_payload_off; + +#endif // MQTT_SUPPORT + // ----------------------------------------------------------------------------- // RELAY PROVIDERS // ----------------------------------------------------------------------------- @@ -616,6 +623,13 @@ void _relayConfigure() { digitalWrite(_relays[i].pin, HIGH); } } + + #if MQTT_SUPPORT + settingsProcessConfig({ + {_relay_mqtt_payload_on, "relayPayloadON", RELAY_MQTT_ON}, + {_relay_mqtt_payload_off, "relayPayloadOFF", RELAY_MQTT_OFF} + }); + #endif // MQTT_SUPPORT } //------------------------------------------------------------------------------ @@ -857,6 +871,18 @@ void relaySetupAPI() { #if MQTT_SUPPORT +const String& relayPayloadOn() { + return _relay_mqtt_payload_on; +} + +const String& relayPayloadOff() { + return _relay_mqtt_payload_off; +} + +const char* relayPayload(bool status) { + return status ? _relay_mqtt_payload_on.c_str() : _relay_mqtt_payload_off.c_str(); +} + void _relayMQTTGroup(unsigned char id) { String topic = getSetting("mqttGroup", id, ""); if (!topic.length()) return; @@ -866,7 +892,7 @@ void _relayMQTTGroup(unsigned char id) { bool status = relayStatus(id); if (mode == RELAY_GROUP_SYNC_INVERSE) status = !status; - mqttSendRaw(topic.c_str(), status ? RELAY_MQTT_ON : RELAY_MQTT_OFF); + mqttSendRaw(topic.c_str(), relayPayload(status)); } void relayMQTT(unsigned char id) { @@ -876,7 +902,7 @@ void relayMQTT(unsigned char id) { // Send state topic if (_relays[id].report) { _relays[id].report = false; - mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? RELAY_MQTT_ON : RELAY_MQTT_OFF); + mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relays[id].current_status)); } // Check group topic @@ -896,7 +922,7 @@ void relayMQTT(unsigned char id) { void relayMQTT() { for (unsigned int id=0; id < _relays.size(); id++) { - mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? RELAY_MQTT_ON : RELAY_MQTT_OFF); + mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relays[id].current_status)); } } diff --git a/code/espurna/settings.ino b/code/espurna/settings.ino index 4cbbc5e0..0e82b4d0 100644 --- a/code/espurna/settings.ino +++ b/code/espurna/settings.ino @@ -245,6 +245,17 @@ void settingsGetJson(JsonObject& root) { } +void settingsProcessConfig(const settings_cfg_list_t& config, settings_filter_t filter) { + for (auto& entry : config) { + String value = getSetting(entry.key, entry.default_value); + if (filter) { + value = filter(value); + } + if (value.equals(entry.setting)) continue; + entry.setting = std::move(value); + } +} + // ----------------------------------------------------------------------------- // Initialization // ----------------------------------------------------------------------------- diff --git a/code/espurna/utils.ino b/code/espurna/utils.ino index efbe9ce9..e95d7d75 100644 --- a/code/espurna/utils.ino +++ b/code/espurna/utils.ino @@ -272,7 +272,7 @@ void heartbeat() { mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str()); if (hb_cfg & Heartbeat::Status) - mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); + mqttSendStatus(); if (hb_cfg & Heartbeat::Loadavg) mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str()); @@ -291,7 +291,7 @@ void heartbeat() { #endif } else if (!serial && _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) { - mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); + mqttSendStatus(); } #endif diff --git a/code/espurna/ws.ino b/code/espurna/ws.ino index 790ed31a..b9780c80 100644 --- a/code/espurna/ws.ino +++ b/code/espurna/ws.ino @@ -91,6 +91,14 @@ struct ws_data_t { counter(0, 1) {} + ws_data_t(const uint32_t client_id, ws_on_send_callback_list_t&& callbacks, mode_t mode = SEQUENCE) : + storage(new ws_on_send_callback_list_t(std::move(callbacks))), + client_id(client_id), + mode(mode), + callbacks(*storage.get()), + counter(0, (storage.get())->size()) + {} + 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), @@ -764,6 +772,10 @@ 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(uint32_t client_id, ws_on_send_callback_list_t&& cbs) { + _ws_client_data.emplace(client_id, std::forward(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); }