From fe3fb622ee61e0d70dc08e39d2164802dbd60159 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 1 Nov 2020 16:27:23 +0100 Subject: [PATCH 1/2] New usermod: MQTT switches. This user mod adds a function to toggle output pins via MQTT. --- usermods/mqtt_switch_v2/README.md | 46 ++++++ usermods/mqtt_switch_v2/usermod_mqtt_switch.h | 140 ++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 usermods/mqtt_switch_v2/README.md create mode 100644 usermods/mqtt_switch_v2/usermod_mqtt_switch.h diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md new file mode 100644 index 00000000..ec64898d --- /dev/null +++ b/usermods/mqtt_switch_v2/README.md @@ -0,0 +1,46 @@ +# MQTT controllable switches +This usermod allows controlling switches (e.g. relays) via MQTT. + +## Usermod installation + +1. Copy the file `usermod_mqtt_switch.h` to the `wled00` directory. +2. Register the usermod by adding `#include "usermod_mqtt_switch.h"` in the top and `registerUsermod(new UsermodMqttSwitch());` in the bottom of `usermods_list.cpp`. + + +Example `usermods_list.cpp`: + +``` +#include "wled.h" +#include "usermod_mqtt_switch.h" + +void registerUsermods() +{ + usermods.add(new UsermodMqttSwitch()); +} +``` + +## Define pins +Add a define for MQTTSWITCHPINS to platformio_override.ini. +The following example defines 3 switches connected to the GPIO pins 13, 0 and 2: + +``` +[env:livingroom] +board = esp12e +platform = ${common.platform_wled_default} +board_build.ldscript = ${common.ldscript_4m1m} +build_flags = ${common.build_flags_esp8266} + -D LEDPIN=3 + -D BTNPIN=4 + -D RLYPIN=12 + -D RLYMDE=1 + -D STATUSPIN=15 + -D MQTTSWITCHPINS="13, 0, 2" +``` + +## MQTT topics +This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced with the index of the switch) for commands. Anything starting with `ON` turns on the switch, everything else turns it off. +Feedback about the current state is provided at `[mqttDeviceTopic]/switch/0/state`. + +### Home Assistant auto-discovery +Auto-discovery information is automatically published and you shoudn't have to do anything to register the switches in Home Assistant. + diff --git a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h new file mode 100644 index 00000000..32b3c1be --- /dev/null +++ b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h @@ -0,0 +1,140 @@ +#pragma once + +#include "wled.h" +#ifndef WLED_ENABLE_MQTT +#error "This user mod requires MQTT to be enabled." +#endif + +#ifndef MQTTSWITCHPINS +#define MQTTSWITCHPINS 12, 0, 2 +//#error "Please define MQTTSWITCHPINS in platformio_override.ini. e.g. -D MQTTSWITCHPINS="12, 0, 2" " +#endif + + +static const uint8_t switchPins[] = {MQTTSWITCHPINS}; +//This is a hack to get the number of pins defined by the user +#define NUM_SWITCH_PINS (sizeof(switchPins)) + +class UsermodMqttSwitch: public Usermod +{ +private: + bool mqttInitialized; + bool switchState[NUM_SWITCH_PINS]; + +public: + UsermodMqttSwitch() : + mqttInitialized(false) + { + } + + void setup() + { + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + pinMode(switchPins[pinNr], OUTPUT); + setState(pinNr, false); + } + } + + void loop() + { + if (!mqttInitialized) { + mqttInit(); + return; // Try again in next loop iteration + } + } + + void mqttInit() + { + if (!mqtt) + return; + mqtt->onMessage( + std::bind(&UsermodMqttSwitch::onMqttMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, + std::placeholders::_5, std::placeholders::_6)); + mqtt->onConnect(std::bind(&UsermodMqttSwitch::onMqttConnect, this, std::placeholders::_1)); + mqttInitialized = true; + } + + void onMqttConnect(bool sessionPresent); + + void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); + void updateState(uint8_t pinNr); + + void setState(uint8_t pinNr, bool active) + { + if (pinNr > NUM_SWITCH_PINS) + return; + switchState[pinNr] = active; + digitalWrite((char) switchPins[pinNr], (char) active); + updateState(pinNr); + } +}; + +inline void UsermodMqttSwitch::onMqttConnect(bool sessionPresent) +{ + if (mqttDeviceTopic[0] == 0) + return; + + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[128]; + StaticJsonDocument<1024> json; + sprintf(buf, "%s Switch %d", serverDescription, pinNr + 1); + json[F("name")] = buf; + + sprintf(buf, "%s/switch/%d", mqttDeviceTopic, pinNr); + json["~"] = buf; + strcat(buf, "/set"); + mqtt->subscribe(buf, 0); + + json[F("stat_t")] = "~/state"; + json[F("cmd_t")] = "~/set"; + json[F("pl_off")] = F("OFF"); + json[F("pl_on")] = F("ON"); + + char uid[16]; + sprintf(uid, "%s_sw%d", escapedMac.c_str(), pinNr); + json[F("unique_id")] = uid; + + strcpy(buf, mqttDeviceTopic); + strcat(buf, "/status"); + json[F("avty_t")] = buf; + json[F("pl_avail")] = F("online"); + json[F("pl_not_avail")] = F("offline"); + //TODO: dev + sprintf(buf, "homeassistant/switch/%s/config", uid); + char json_str[1024]; + size_t payload_size = serializeJson(json, json_str); + mqtt->publish(buf, 0, true, json_str, payload_size); + updateState(pinNr); + } +} + +inline void UsermodMqttSwitch::onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) +{ + //Note: Payload is not necessarily null terminated. Check "len" instead. + for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + char buf[64]; + sprintf(buf, "%s/switch/%d/set", mqttDeviceTopic, pinNr); + if (strcmp(topic, buf) == 0) { + //Any string starting with "ON" is interpreted as ON, everything else as OFF + setState(pinNr, len >= 2 && payload[0] == 'O' && payload[1] == 'N'); + break; + } + } +} + +inline void UsermodMqttSwitch::updateState(uint8_t pinNr) +{ + if (!mqttInitialized) + return; + + if (pinNr > NUM_SWITCH_PINS) + return; + + char buf[64]; + sprintf(buf, "%s/switch/%d/state", mqttDeviceTopic, pinNr); + if (switchState[pinNr]) { + mqtt->publish(buf, 0, false, "ON"); + } else { + mqtt->publish(buf, 0, false, "OFF"); + } +} From b725d66ee3bf6656537b2216c37a0d5c5e36c5f0 Mon Sep 17 00:00:00 2001 From: Hermann Kraus Date: Sun, 1 Nov 2020 18:08:30 +0100 Subject: [PATCH 2/2] Add options for inverted switches and default values. --- usermods/mqtt_switch_v2/README.md | 8 ++++-- usermods/mqtt_switch_v2/usermod_mqtt_switch.h | 25 ++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/usermods/mqtt_switch_v2/README.md b/usermods/mqtt_switch_v2/README.md index ec64898d..dc0e259f 100644 --- a/usermods/mqtt_switch_v2/README.md +++ b/usermods/mqtt_switch_v2/README.md @@ -21,7 +21,7 @@ void registerUsermods() ## Define pins Add a define for MQTTSWITCHPINS to platformio_override.ini. -The following example defines 3 switches connected to the GPIO pins 13, 0 and 2: +The following example defines 3 switches connected to the GPIO pins 13, 5 and 2: ``` [env:livingroom] @@ -34,8 +34,12 @@ build_flags = ${common.build_flags_esp8266} -D RLYPIN=12 -D RLYMDE=1 -D STATUSPIN=15 - -D MQTTSWITCHPINS="13, 0, 2" + -D MQTTSWITCHPINS="13, 5, 2" ``` + +Pins can be inverted by setting `MQTTSWITCHINVERT`. For example `-D MQTTSWITCHINVERT="false, false, true"` would invert the switch on pin 2 in the previous example. + +The default state after booting before any MQTT message can be set by `MQTTSWITCHDEFAULTS`. For example `-D MQTTSWITCHDEFAULTS="ON, OFF, OFF"` would power on the switch on pin 13 and power off switches on pins 5 and 2. ## MQTT topics This usermod listens on `[mqttDeviceTopic]/switch/0/set` (where 0 is replaced with the index of the switch) for commands. Anything starting with `ON` turns on the switch, everything else turns it off. diff --git a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h index 32b3c1be..40241206 100644 --- a/usermods/mqtt_switch_v2/usermod_mqtt_switch.h +++ b/usermods/mqtt_switch_v2/usermod_mqtt_switch.h @@ -6,14 +6,31 @@ #endif #ifndef MQTTSWITCHPINS +#error "Please define MQTTSWITCHPINS in platformio_override.ini. e.g. -D MQTTSWITCHPINS="12, 0, 2" " +// The following define helps Eclipse's C++ parser but is never used in production due to the #error statement on the line before #define MQTTSWITCHPINS 12, 0, 2 -//#error "Please define MQTTSWITCHPINS in platformio_override.ini. e.g. -D MQTTSWITCHPINS="12, 0, 2" " #endif +// Default behavior: All outputs active high +#ifndef MQTTSWITCHINVERT +#define MQTTSWITCHINVERT +#endif -static const uint8_t switchPins[] = {MQTTSWITCHPINS}; +// Default behavior: All outputs off +#ifndef MQTTSWITCHDEFAULTS +#define MQTTSWITCHDEFAULTS +#endif + +static const uint8_t switchPins[] = { MQTTSWITCHPINS }; //This is a hack to get the number of pins defined by the user #define NUM_SWITCH_PINS (sizeof(switchPins)) +static const bool switchInvert[NUM_SWITCH_PINS] = { MQTTSWITCHINVERT}; +//Make settings in config file more readable +#define ON 1 +#define OFF 0 +static const bool switchDefaults[NUM_SWITCH_PINS] = { MQTTSWITCHDEFAULTS}; +#undef ON +#undef OFF class UsermodMqttSwitch: public Usermod { @@ -30,8 +47,8 @@ public: void setup() { for (int pinNr = 0; pinNr < NUM_SWITCH_PINS; pinNr++) { + setState(pinNr, switchDefaults[pinNr]); pinMode(switchPins[pinNr], OUTPUT); - setState(pinNr, false); } } @@ -64,7 +81,7 @@ public: if (pinNr > NUM_SWITCH_PINS) return; switchState[pinNr] = active; - digitalWrite((char) switchPins[pinNr], (char) active); + digitalWrite((char) switchPins[pinNr], (char) (switchInvert[pinNr] ? !active : active)); updateState(pinNr); } };