From beff73ef8b7b22e6c170b72d11e4c2b51e6841cb Mon Sep 17 00:00:00 2001 From: Max Prokhorov Date: Tue, 3 Sep 2019 09:13:21 +0300 Subject: [PATCH] relay/mqtt: handle custom relay status payloads (#1889) - customize relay TOGGLE payload - match payload string when receiving mqtt status message - reference enum values instead of raw integers, spell out intended status - remove dead code amend #1885, capitalize `relayPayload...` suffix instead of using uppercase add `relayPayloadToggle` --- code/espurna/config/general.h | 10 +- code/espurna/config/prototypes.h | 13 ++- code/espurna/homeassistant.ino | 4 +- code/espurna/led.ino | 6 +- code/espurna/relay.ino | 180 +++++++++++++++++-------------- 5 files changed, 123 insertions(+), 90 deletions(-) diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index df05143b..41480d64 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -404,13 +404,17 @@ #define RELAY_REPORT_STATUS 1 #endif -// Configure the MQTT payload for ON/OFF +// Configure the MQTT payload for ON, OFF and TOGGLE +#ifndef RELAY_MQTT_OFF +#define RELAY_MQTT_OFF "0" +#endif + #ifndef RELAY_MQTT_ON #define RELAY_MQTT_ON "1" #endif -#ifndef RELAY_MQTT_OFF -#define RELAY_MQTT_OFF "0" +#ifndef RELAY_MQTT_TOGGLE +#define RELAY_MQTT_TOGGLE "2" #endif // TODO Only single EEPROM address is used to store state, which is 1 byte diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index 72b68ddf..d3d6b0d8 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -254,6 +254,15 @@ typedef struct { // ----------------------------------------------------------------------------- #include +enum class RelayStatus : unsigned char { + OFF = 0, + ON = 1, + TOGGLE = 2, + UNKNOWN = 0xFF +}; + +RelayStatus relayParsePayload(const char * payload); + bool relayStatus(unsigned char id, bool status, bool report, bool group_report); bool relayStatus(unsigned char id, bool status); bool relayStatus(unsigned char id); @@ -262,11 +271,11 @@ 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); +const String& relayPayloadToggle(); +const char* relayPayload(RelayStatus status); // ----------------------------------------------------------------------------- // Settings diff --git a/code/espurna/homeassistant.ino b/code/espurna/homeassistant.ino index d1f3bf45..bdcf5249 100644 --- a/code/espurna/homeassistant.ino +++ b/code/espurna/homeassistant.ino @@ -144,8 +144,8 @@ void _haSendSwitch(unsigned char i, JsonObject& config) { if (relayCount()) { config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false); config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true); - config["payload_on"] = relayPayload(true); - config["payload_off"] = relayPayload(false); + config["payload_on"] = relayPayload(RelayStatus::ON); + config["payload_off"] = relayPayload(RelayStatus::OFF); config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false); config["payload_available"] = mqttPayloadStatus(true); config["payload_not_available"] = mqttPayloadStatus(false); diff --git a/code/espurna/led.ino b/code/espurna/led.ino index 67a5658c..036e48ca 100644 --- a/code/espurna/led.ino +++ b/code/espurna/led.ino @@ -130,13 +130,13 @@ void _ledMQTTCallback(unsigned int type, const char * topic, const char * payloa if (_ledMode(ledID) != LED_MODE_MQTT) return; // get value - unsigned char value = relayParsePayload(payload); + const auto value = relayParsePayload(payload); // Action to perform - if (value == 2) { + if (value == RelayStatus::TOGGLE) { _ledToggle(ledID); } else { - _ledStatus(ledID, value == 1); + _ledStatus(ledID, (value == RelayStatus::ON)); } } diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 675f898e..1d0b0e05 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -48,9 +48,40 @@ Ticker _relaySaveTicker; String _relay_mqtt_payload_on; String _relay_mqtt_payload_off; +String _relay_mqtt_payload_toggle; #endif // MQTT_SUPPORT +// ----------------------------------------------------------------------------- +// UTILITY +// ----------------------------------------------------------------------------- + +bool _relayHandlePayload(unsigned char relayID, const char* payload) { + auto value = relayParsePayload(payload); + if (value == RelayStatus::UNKNOWN) return false; + + if (value == RelayStatus::OFF) { + relayStatus(relayID, false); + } else if (value == RelayStatus::ON) { + relayStatus(relayID, true); + } else if (value == RelayStatus::TOGGLE) { + relayToggle(relayID); + } + + return true; +} + +RelayStatus _relayStatusInvert(RelayStatus status) { + return (status == RelayStatus::ON) ? RelayStatus::OFF : status; +} + +RelayStatus _relayStatusTyped(unsigned char id) { + if (id >= _relays.size()) return RelayStatus::OFF; + + const bool status = _relays[id].current_status; + return (status) ? RelayStatus::ON : RelayStatus::OFF; +} + // ----------------------------------------------------------------------------- // RELAY PROVIDERS // ----------------------------------------------------------------------------- @@ -377,10 +408,10 @@ bool relayStatus(unsigned char id, bool status) { bool relayStatus(unsigned char id) { - // Check relay ID + // Check that relay ID is valid if (id >= _relays.size()) return false; - // Get status from storage + // Get status directly from storage return _relays[id].current_status; } @@ -484,37 +515,40 @@ unsigned char relayCount() { return _relays.size(); } -unsigned char relayParsePayload(const char * payload) { +RelayStatus relayParsePayload(const char * payload) { - // Payload could be "OFF", "ON", "TOGGLE" - // or its number equivalents: 0, 1 or 2 + // Don't parse empty strings + const auto len = strlen(payload); + if (!len) return RelayStatus::UNKNOWN; - if (payload[0] == '0') return 0; - if (payload[0] == '1') return 1; - if (payload[0] == '2') return 2; + // Check most commonly used payloads + if (len == 1) { + if (payload[0] == '0') return RelayStatus::OFF; + if (payload[0] == '1') return RelayStatus::ON; + if (payload[0] == '2') return RelayStatus::TOGGLE; + return RelayStatus::UNKNOWN; + } - // trim payload - char * p = ltrim((char *)payload); + // If possible, compare to locally configured payload strings + #if MQTT_SUPPORT + if (_relay_mqtt_payload_off.equals(payload)) return RelayStatus::OFF; + if (_relay_mqtt_payload_on.equals(payload)) return RelayStatus::ON; + if (_relay_mqtt_payload_toggle.equals(payload)) return RelayStatus::TOGGLE; + #endif // MQTT_SUPPORT - // to lower - unsigned int l = strlen(p); - if (l>6) l=6; - for (unsigned char i=0; i("id")) { + relayID = data["id"]; } + _relayHandlePayload(relayID, data["status"]); + } } @@ -807,21 +824,11 @@ void relaySetupAPI() { }, [relayID](const char * payload) { - unsigned char value = relayParsePayload(payload); - - if (value == 0xFF) { + if (_relayHandlePayload(relayID, payload)) { DEBUG_MSG_P(PSTR("[RELAY] Wrong payload (%s)\n"), payload); return; } - if (value == 0) { - relayStatus(relayID, false); - } else if (value == 1) { - relayStatus(relayID, true); - } else if (value == 2) { - relayToggle(relayID); - } - } ); @@ -879,8 +886,21 @@ 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(); +const String& relayPayloadToggle() { + return _relay_mqtt_payload_toggle; +} + +const char* relayPayload(RelayStatus status) { + + if (status == RelayStatus::ON) { + return _relay_mqtt_payload_off.c_str(); + } else if (status == RelayStatus::OFF) { + return _relay_mqtt_payload_on.c_str(); + } else if (status == RelayStatus::TOGGLE) { + return _relay_mqtt_payload_toggle.c_str(); + } + + return ""; } void _relayMQTTGroup(unsigned char id) { @@ -890,8 +910,8 @@ void _relayMQTTGroup(unsigned char id) { unsigned char mode = getSetting("mqttGroupSync", id, RELAY_GROUP_SYNC_NORMAL).toInt(); if (mode == RELAY_GROUP_SYNC_RECEIVEONLY) return; - bool status = relayStatus(id); - if (mode == RELAY_GROUP_SYNC_INVERSE) status = !status; + auto status = _relayStatusTyped(id); + if (mode == RELAY_GROUP_SYNC_INVERSE) status = _relayStatusInvert(status); mqttSendRaw(topic.c_str(), relayPayload(status)); } @@ -902,7 +922,7 @@ void relayMQTT(unsigned char id) { // Send state topic if (_relays[id].report) { _relays[id].report = false; - mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relays[id].current_status)); + mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relayStatusTyped(_relays[id].current_status))); } // Check group topic @@ -922,19 +942,19 @@ void relayMQTT(unsigned char id) { void relayMQTT() { for (unsigned int id=0; id < _relays.size(); id++) { - mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relays[id].current_status)); + mqttSend(MQTT_TOPIC_RELAY, id, relayPayload(_relayStatusTyped(_relays[id].current_status))); } } -void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) { +void relayStatusWrap(unsigned char id, RelayStatus value, bool is_group_topic) { switch (value) { - case 0: + case RelayStatus::OFF: relayStatus(id, false, mqttForward(), !is_group_topic); break; - case 1: + case RelayStatus::ON: relayStatus(id, true, mqttForward(), !is_group_topic); break; - case 2: + case RelayStatus::TOGGLE: relayToggle(id, true, true); break; default: @@ -1015,8 +1035,8 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo } // Get value - unsigned char value = relayParsePayload(payload); - if (value == 0xFF) return; + auto value = relayParsePayload(payload); + if (value == RelayStatus::UNKNOWN) return; relayStatusWrap(id, value, false); @@ -1031,12 +1051,12 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo if ((t.length() > 0) && t.equals(topic)) { - unsigned char value = relayParsePayload(payload); - if (value == 0xFF) return; + auto value = relayParsePayload(payload); + if (value == RelayStatus::UNKNOWN) return; - if (value < 2) { + if ((value == RelayStatus::ON) || (value == RelayStatus::OFF)) { if (getSetting("mqttGroupSync", i, RELAY_GROUP_SYNC_NORMAL).toInt() == RELAY_GROUP_SYNC_INVERSE) { - value = 1 - value; + value = _relayStatusInvert(value); } } @@ -1060,10 +1080,10 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo int reaction = getSetting("relayOnDisc", i, 0).toInt(); if (1 == reaction) { // switch relay OFF DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i); - relayStatusWrap(i, false, false); + relayStatusWrap(i, RelayStatus::OFF, false); } else if(2 == reaction) { // switch relay ON DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i); - relayStatusWrap(i, true, false); + relayStatusWrap(i, RelayStatus::ON, false); } }