xoseperez / espurna

Home automation firmware for ESP8266-based devices
http://tinkerman.cat
GNU General Public License v3.0
3k stars 638 forks source link

Switch/button states not included in MQTT connect/keepalive status messages. #1413

Closed lbdroid closed 5 years ago

lbdroid commented 5 years ago

Using master branch at version 1.13.3.

My application is for use as a garage door controller. I'm working with a physically modified Sonoff Basic, in that the live A/C and neutral lines are cut off before the relay, and what was the neutral line to the output connector is jumped to the relay input. The result is that the output connector is now effectively just a normally open switch. This switch is connected to the garage door controller so that a 1-second pulse will activate it.

I have also connected the sonoff to the garage door limit switches via the GPIO header. There are 2 limit switches (fully open and fully closed), so it was necessary to reclaim the UART pins using "#define DEBUG_SERIAL_SUPPORT 0" in order to receive the state of both switches.

Now unfortunately, it seems that the state of the BUTTONs that I have connected to the limit switches is only sent to the MQTT server upon an actual state change. This leads to a potential loss of synchronization if the door position changes during a loss of communications between the sonoff and the MQTT server.

As an example; (1) door is closed, (2) network outage begins, (3) door is opened via car physical button, (4) network resumes. This would lead to a state where the door is open, but the software indicates that the door is CLOSED.

Since RELAY states are included in the connect/keepalive updates, as a workaround, I am using the following; (note that GPIO's 4, 5, and 16 are not connected on the Sonoff Basic)

    #define BUTTON2_PIN         14
    #define BUTTON2_MODE        BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
    #define BUTTON2_PRESS   BUTTON_MODE_ON
    #define BUTTON2_CLICK   BUTTON_MODE_OFF
    #define BUTTON2_DBLCLICK    BUTTON_MODE_OFF
    #define BUTTON2_LNGCLICK    BUTTON_MODE_OFF
    #define BUTTON2_LNGLNGCLICK BUTTON_MODE_OFF
    #define BUTTON2_RELAY   2
    #define BUTTON3_PIN     1
    #define BUTTON3_MODE    BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
    #define BUTTON3_PRESS   BUTTON_MODE_ON
    #define BUTTON3_CLICK   BUTTON_MODE_OFF
    #define BUTTON3_DBLCLICK    BUTTON_MODE_OFF
    #define BUTTON3_LNGCLICK    BUTTON_MODE_OFF
    #define BUTTON3_LNGLNGCLICK BUTTON_MODE_OFF
    #define BUTTON3_RELAY   3
    #define BUTTON4_PIN     3
    #define BUTTON4_MODE    BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
    #define BUTTON4_PRESS   BUTTON_MODE_ON
    #define BUTTON4_CLICK   BUTTON_MODE_OFF
    #define BUTTON4_DBLCLICK    BUTTON_MODE_OFF
    #define BUTTON4_LNGCLICK    BUTTON_MODE_OFF
    #define BUTTON4_LNGLNGCLICK BUTTON_MODE_OFF
    #define BUTTON4_RELAY   4

    #define RELAY2_PIN      4
    #define RELAY2_TYPE     RELAY_TYPE_NORMAL
    #define RELAY3_PIN      5
    #define RELAY3_TYPE     RELAY_TYPE_NORMAL
    #define RELAY4_PIN      16
    #define RELAY4_TYPE     RELAY_TYPE_NORMAL

It would be very useful if the MQTT updates would include status for all defined button GPIOs.

xoseperez commented 5 years ago

First of all, congratulations. It's obvious you put a lot of work on the project. Then, wouldn't it be a better option to use a DigitalSensor that would report the state of a pin every X seconds?

lbdroid commented 5 years ago

Thank you. I actually did look at that as an option earlier, but there doesn't appear to be any possibility of defining more than a single pin as a digital sensor. In a minimum case, I need to have 2 inputs. In an ideal case, up to 4 inputs (additionally one for running/stopped, and one for up/down). Although I suppose that having the fully-closed input alone read reliably would cover the security aspect of it.

Hmm, couple of other issues with DigitalSensor for this application; very long reporting interval of every minute, and it doesn't seem to actually be reading the state of the pin I have it assigned to, even though it works fine when the pin is defined as a button.

lbdroid commented 5 years ago

I've been able to achieve desired functionality by defining a new type of relay;

diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h
index a984bb7e..aabebf82 100644
--- a/code/espurna/config/types.h
+++ b/code/espurna/config/types.h
@@ -68,6 +68,7 @@
 #define RELAY_TYPE_INVERSE          1
 #define RELAY_TYPE_LATCHED          2
 #define RELAY_TYPE_LATCHED_INVERSE  3
+#define RELAY_TYPE_FAKE             4

 #define RELAY_SYNC_ANY              0
 #define RELAY_SYNC_NONE_OR_ONE      1

And making it not to appear in the web UI, and NOT respond to changes over MQTT:

diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino
index df01fbc4..9bcd27be 100644
--- a/code/espurna/relay.ino
+++ b/code/espurna/relay.ino
@@ -558,7 +558,7 @@ void _relayBoot() {
 void _relayConfigure() {
     for (unsigned int i=0; i<_relays.size(); i++) {
         pinMode(_relays[i].pin, OUTPUT);
-        if (GPIO_NONE != _relays[i].reset_pin) {
+        if (GPIO_NONE != _relays[i].reset_pin && RELAY_TYPE_FAKE != _relays[i].type) {
             pinMode(_relays[i].reset_pin, OUTPUT);
         }
         if (_relays[i].type == RELAY_TYPE_INVERSE) {
@@ -583,7 +583,7 @@ bool _relayWebSocketOnReceive(const char * key, JsonVariant& value) {
 void _relayWebSocketUpdate(JsonObject& root) {
     JsonArray& relay = root.createNestedArray("relayStatus");
     for (unsigned char i=0; i<relayCount(); i++) {
-        relay.add(_relays[i].target_status);
+        if (_relays[i].type != RELAY_TYPE_FAKE) relay.add(_relays[i].target_status);
     }
 }

@@ -597,18 +597,20 @@ void _relayWebSocketOnStart(JsonObject& root) {
     // Configuration
     JsonArray& config = root.createNestedArray("relayConfig");
     for (unsigned char i=0; i<relayCount(); i++) {
-        JsonObject& line = config.createNestedObject();
-        line["gpio"] = _relays[i].pin;
-        line["type"] = _relays[i].type;
-        line["reset"] = _relays[i].reset_pin;
-        line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
-        line["pulse"] = _relays[i].pulse;
-        line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
-        #if MQTT_SUPPORT
-            line["group"] = getSetting("mqttGroup", i, "");
-            line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
-            line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
-        #endif
+        if (_relays[i].type != RELAY_TYPE_FAKE){
+            JsonObject& line = config.createNestedObject();
+            line["gpio"] = _relays[i].pin;
+            line["type"] = _relays[i].type;
+            line["reset"] = _relays[i].reset_pin;
+            line["boot"] = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
+            line["pulse"] = _relays[i].pulse;
+            line["pulse_ms"] = _relays[i].pulse_ms / 1000.0;
+            #if MQTT_SUPPORT
+                line["group"] = getSetting("mqttGroup", i, "");
+                line["group_inv"] = getSetting("mqttGroupInv", i, 0).toInt();
+                line["on_disc"] = getSetting("relayOnDisc", i, 0).toInt();
+            #endif
+        }
     }

     if (relayCount() > 1) {
@@ -841,7 +843,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo

             unsigned int id = t.substring(strlen(MQTT_TOPIC_PULSE)+1).toInt();

-            if (id >= relayCount()) {
+            if (id >= relayCount() || _relays[id].type==RELAY_TYPE_FAKE) {
                 DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
                 return;
             }
@@ -866,7 +868,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo

             // Get relay ID
             unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
-            if (id >= relayCount()) {
+            if (id >= relayCount() || _relays[id].type==RELAY_TYPE_FAKE) {
                 DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
                 return;
             }

And defining fake relays with the following form (along with button definitions as in first message);

    #define RELAYx_PIN      0
    #define RELAYx_TYPE     RELAY_TYPE_FAKE
    #define RELAYx_RESET_PIN    0
    #define RELAYx_DELAY_ON 0
    #define RELAYx_DELAY_OFF    0

In this way, the status of the relay can only follow the state of the associated button, which effectively feeds the button state out via MQTT both immediately upon state change, as well as upon connect/keepalive.

This feels like a bit of a kludge to me, and doesn't display the button status at all in the webUI.

Thoughts?

xoseperez commented 5 years ago

I still think a sensor is more appropriate for this application. You can change the report interval of the DigitalSensor down to 1 second or you can use an EventSensor which will trigger an MQTT message on pin change.

Any issues with the sensors not reading the pin correctly should be fixed. And yes, even thou you can only define one PIN at the moment in the hardware.h file, you can check the sensor.ino file on how to add more manually.

matthieu0 commented 5 years ago

Hello, I would like to understand better how concretely you modified physically Sonoff so the output connector is a Normally Open switch. I wish to do the same on Sonoff TH10 model (which I believe is similar to yours). Would you have a picture of your card showing the modifications (the two cuts and the jumper I guess)? For info, my application is a boiler controller over the internet.

lbdroid commented 5 years ago

@matthieu0 : I've looked at some pictures of TH1x versions, and their circuit board is extremely different than that of the basic. They're also hooking the relay up on that one in a really strange way (see schematics for it here; https://www.itead.cc/wiki/images/3/39/Sonoff_TH10A%2816A%29_schmatic.pdf)

You will note that it is an SPDT relay, and that they're hooking SUPPLY POWER ("L" to BOTH the common AND the normally connected (N_C) pins. That arrangement means that you need to keep in mind that you must disconnect it from both locations, otherwise it will tie your switched circuit into high voltage every time the switch turns off. There was no reason at all for them to hook anything up to the NC pin -- they should have left it disconnected.

The one advantage that you would have with this board, is that it looks like the ground connectors are NOT ACTUALLY USED on it. That means that you have free connectors available to use for your switch. Make sure that you do NOT connect the ground circuit! Just use one of the 2 ground connectors as the "supply" side of the switch.

Here are a couple of images of the board;

https://github.com/lbdroid/store/raw/master/Sonoff-TH16-Board.jpg https://github.com/lbdroid/store/raw/master/Sonoff-TH16-Board-ESP8266.jpg

The bottom side of the board will be quite easy. The top, not so easy because its a pretty tight space in which you would have to cut the trace. I guess they didn't think that there would be enough conductivity if they only added traces on one side of the board, so you have the same traces mirrored on both sides.

If I were modifying one of those units myself, I'd be inclined to remove the relay entirely to gain access to the top of the board.

Pay particular attention to the other components connected to that live trace on the top of the board; the fuse holder, and a resistor. The circuit (esp8266 and so on) is powered through the resistor, so it MUST remain connected. The trace, on BOTH sides, has to be cut BETWEEN the resistor and the relay.

When I'm cutting out traces, especially on high voltage circuits, I like to make 2 cuts through the trace with a utility knife, with as much space between the 2 cuts as possible, and then scrape the trace off the substrate between the two cuts. This will provide a much greater gap between the two sides and ensure that you won't get any arcing.

I also would NOT recommend this modification unless you are EXTREMELY confident about it, and experienced with working on high voltage circuits.

matthieu0 commented 5 years ago

@lbdroid : I was looking forward to praticing a bit a of hacking but it seems I had better pass on this one. I am not very experienced with high voltage circuits and the modifications are not straight forward. So I will opt for an alternative solution. This other model of ITEAD should do the job without any modification: https://www.itead.cc/smart-home/inching-self-locking-wifi-wireless-switch.html At least it will give me piece of mind when I use it from a thousand kilometers away.

I will just miss the Thermometer/Humidity function. Either I also get Sonoff TH1x or I go for the monitoring station (https://www.itead.cc/smart-home/sonoff-sc.html)

Thank you for your reply and for the tip of making 2 cuts and scrape the trace in between.

lbdroid commented 5 years ago

@matthieu0 : The downside I see with that "sc" version (aside from the sensors you've noted), is that it lacks a built in power supply.

The other option you have that could just attach an additional relay with an AC coil. Something like this; https://www.amazon.com/uxcell-Electromagnetic-Power-Relay-Socket/dp/B01G3R7TWK

With that, you would just take the sonoff output to power the coil on the relay. Its a little silly to use a relay to switch a relay, but at least you won't be modifying circuits you aren't comfortable with.

matthieu0 commented 5 years ago

@lbdroid : For the power supply, I will simply use the USB 5V port. I did not understand the other option you propose with 2 relays in cascade (sonoff "sc" + uxcell). By the way what do you mean by "sc"?

skorokithakis commented 5 years ago

I have the same problem (although I'm using a generic WeMos). What I would like is a type of sensor (or something else) that could send a retained MQTT message every time the pin state changed (not on an interval, on an interrupt). Is that possible, @xoseperez?

lbdroid commented 5 years ago

@matthieu0 : "sc" is from the link you provided. I think my eyes skipped a few lines, I meant "th" version with the temperature sensor.

I'm not sure where the confusion is with the sonoff + relay solution. AC power --> Sonoff --> Relay's coil <==> switched DC.

matthieu0 commented 5 years ago

@lbdroid : Ok now I understand the other option with the 2 relays in cascade. Thanks for clarifying.

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.

stale[bot] commented 5 years ago

This issue will be auto-closed because there hasn't been any activity for two months. Feel free to open a new one if you still experience this problem.