How to Make a Two Way Switch with OpenHab2 & External Physical switch

I wanted to create a Mqtt switch to control an external Motor with Openhab "switch: Item. However I always wanted a Manually Override switch that mechanically alter the switch position. This seems overly simple until you get into te details who becomes the master and how to get a feedback from the external switch.

Here is How I Implemented the switch in OH2:

in the Items file:

Switch Relay7 "Solid State Relay" {mqtt=">[broker:/test/relay:command:ON:c],>[broker:/test/relay:command:OFF:d]"}

in the Sitemap file:

Frame label="SWITCHES"
  	{
  		Switch item=Modes label="Lights Mode" icon="pressure" mappings=[0="MANUAL", 1="AUTO"]
  		Switch item=Relay7 visibility=[Modes==0]
  		Text item=Motor_switch 
  		Selection item=Light_Time icon="heating" visibility=[Modes==1] mappings=[0 = "Off",15 = "Once- 9AM",30 = "Twice 9AM, 12PM",45 = "Thrice 9AM, 12PM, 6PM"]
  		
  	}

in the Rules:

rule "Motor Switch Notification"
when 
	Item Motor_switch received update
		then
			if(Motor_switch.state== OPEN){
				Relay7.postUpdate(ON)
				sendBroadcastNotification("MOTOR SWITCH CHANGED OUTSIIDE--OPEN!!")
			}
			else if (Motor_switch.state== CLOSED){
				Relay7.postUpdate(OFF)
				sendBroadcastNotification("MOTOR SWITCH CHANGED OUTSIIDE--CLOSED!!")
			}
end

in the MQTT node I connected one of the external Switch to a GPIO & and used that GPIO as an input Contact.
The tricky part is saving the states of the Relay & Switch when there is a power failure and restart of either the Node or the OH2 server. In my case a RPi.

Check out the Mqtt code below (Pardon me this is not fully cleaned as far as the comments go) This is meant for an ArmTronix SSR board:

/*
Revision: 21/01/2018 with External switch working as Two Switch
 It connects to an MQTT server then:
  - on 0 switches off relay
  - on 1 switches on relay
  - on 2 switches the state of the relay

  - sends 0 on off relay
  - sends 1 on on relay

 It will reconnect to the server if the connection is lost using a blocking
 reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
 achieve the same result without blocking the main loop.

 The current state is stored in EEPROM and restored on bootup
Copied from ARM TRONIX SITE
#define OUTPIN 13 //output pin
#define INPIN 0  // input pin (push button)
#define OUTLED 12 

*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Bounce2.h>
#include <EEPROM.h>
#include <sstream>
#include <string.h>
#include <base64.h>

//#define TRIGGER 5
//#define ECHO    4

const char* ssid = "**********";
const char* password = "**********";
const char* mqtt_server = "192.168.1.116";

WiFiClient espClientSSR;
PubSubClient client(espClientSSR);
long lastMsg = 0;
long freq=0;
char msg[50];
int value = 0;


const char* outTopic = "/test/status";
const char* inTopic = "/test/relay";
//const char* inTopic = "/test/poll";

int relay_pin_1 = 16;
int relay_pin_2 = 5;
int relay_pin_3 = 4;
int relay_pin_4 = 0;
int relay_pin_5 = 2;
int relay_pin_6 = 12;
int relay_pin_7 = 13;
int relay_pin_8 = 14;

//int button_pin = 0;
bool relayState = LOW;
bool SwitchState_before = HIGH;
bool SwitchState_after = HIGH;
// Instantiate a Bounce object :
Bounce debouncer = Bounce(); 


void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    extButton();
    for(int i = 0; i<500; i++){
      extButton();
      delay(1);
    }
    Serial.print(".");
  }
  //digitalWrite(13, LOW);
  //delay(500);
  //digitalWrite(13, HIGH);
  //delay(500);
  digitalWrite(13, LOW);
  delay(500);
  //digitalWrite(13, HIGH);
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
 // uncomment for just 1 relay 0
    if ((char)payload[0] == '8') {
     digitalWrite(relay_pin_5, LOW);   // Turn the LED OFF (Note that HIGH is the voltage level
    Serial.println("relay_pin_5 -> LOW RELAY ON");
    client.publish(outTopic, "ON");
   // relayState = LOW;
    //EEPROM.write(0, relayState);    // Write state to EEPROM
    //EEPROM.commit();
   } else if ((char)payload[0] == '9') {
    digitalWrite(relay_pin_5, HIGH);   // Turn the LED OFF (Note that LOW is the voltage level
    Serial.println("relay_pin_5 -> HIGH RELAY OFF");
   // relayState = HIGH;
   // EEPROM.write(0, relayState);    // Write state to EEPROM
   // EEPROM.commit();
  }  else if ((char)payload[0] == 'c') {
    digitalWrite(relay_pin_7, HIGH);  // Turn the LED ON by making the voltage HIGH
    Serial.println("relay_pin_7 -> RELAY ON");
    relayState = HIGH;
    EEPROM.write(0, relayState);    // Write state to EEPROM
    EEPROM.commit();
    } else if ((char)payload[0] == 'd') {
    digitalWrite(relay_pin_7, LOW);   // Turn the LED OFF (Note that LOW is the voltage level
    Serial.println("relay_pin_7 -> RELAY OFF");
    relayState = LOW;
    EEPROM.write(0, relayState);    // Write state to EEPROM
    EEPROM.commit(); 
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client2")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish(outTopic,"NULL");
      // ... and resubscribe
      client.subscribe(inTopic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      digitalWrite(relay_pin_7, LOW);
      digitalWrite(relay_pin_5, LOW); 
      // Wait 5 seconds before retrying
      for(int i = 0; i<5000; i++){
        extButton();
        delay(1);
      }
    }
  }
}

void extButton() {
  debouncer.update();
   
   // Call code if Bounce fell (transition from HIGH to LOW) :
   if ( debouncer.fell() ) {
     Serial.println("Debouncer fell");
     // Toggle relay state :
     relayState = !relayState;
     digitalWrite(relay_pin_7,relayState);
     EEPROM.write(0, relayState);    // Write state to EEPROM
     if (relayState == 1){
      client.publish(outTopic, "1");
     }
     else if (relayState == 0){
      client.publish(outTopic, "0");
     }
   }
}

void setup() {
  EEPROM.begin(512);              // Begin eeprom to store on/off state
  //pinMode(relay_pin_1, OUTPUT);     // Initialize the relay pin as an output
  //pinMode(relay_pin_2, OUTPUT);     // Initialize the relay pin as an output
  pinMode(relay_pin_3, INPUT);     // Initialize the relay pin as an output
  //pinMode(relay_pin_4, OUTPUT);     // Initialize the relay pin as an output
  pinMode(relay_pin_5, OUTPUT);     // Initialize the relay pin as an output
  //pinMode(relay_pin_6, OUTPUT);     // Initialize the relay pin as an output
  pinMode(relay_pin_7, OUTPUT);     // Initialize the relay pin as an output
 // pinMode(relay_pin_8, OUTPUT);     // Initialize the relay pin as an output
//  pinMode(button_pin, INPUT);     // Initialize the relay pin as an output
 // pinMode(13, OUTPUT);
//  pinMode(TRIGGER, OUTPUT);
//  pinMode(ECHO, INPUT);
  relayState = EEPROM.read(0);
  SwitchState_before=EEPROM.read(1);
 // digitalWrite(relay_pin_1,relayState);
 // digitalWrite(relay_pin_2,LOW);
 // digitalWrite(relay_pin_3,relayState);
 // digitalWrite(relay_pin_4,relayState);
  digitalWrite(relay_pin_5,LOW);
 // digitalWrite(relay_pin_6,relayState);
  digitalWrite(relay_pin_7,LOW);
 // digitalWrite(relay_pin_8,relayState);
  
  
//  debouncer.attach(button_pin);   // Use the bounce2 library to debounce the built in button
  debouncer.interval(50);         // Input must be low for 50 ms
  
  digitalWrite(2, LOW);          // Blink to indicate setup
  delay(500);
  digitalWrite(2, HIGH);
  delay(500);
  
  Serial.begin(115200);
  setup_wifi();                   // Connect to wifi 
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void loop() {

  SwitchState_after=digitalRead(relay_pin_3);
  delay(500);
 // Serial.print("Switch state after");
 // Serial.print(SwitchState_after);
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  extButton();
  
  if (SwitchState_after != SwitchState_before){
      relayState=!relayState;
      SwitchState_before=SwitchState_after;
      EEPROM.write(1, SwitchState_before); 
    digitalWrite(relay_pin_7, relayState); 
    if(relayState==HIGH){
    client.publish(outTopic, "OPEN");
    }
    else if(relayState==LOW){
      client.publish(outTopic, "CLOSED");
    }
  EEPROM.write(0, relayState); 
  EEPROM.commit();
  }
}

I think you’re over complicating things. If your switch sends status updates when physically switched, all you need to do is configure the inbound topic on your Item.

Take a look at the docs for an example of inbound messages. If your physical switch sends something other than ON and OFF as the status, you will have to use a transform, but it should still be pretty straight forward.

I agree. This is quiet Complicated!!
I would love to hear if there are any other ways to simplify this. However I’ve designed this for a system considering a few pre-requisites for users in India:
1. Most Important of all- POWER CUTS. In India its pretty Common that we would have power cuts during specified time of the day. There could be even Low-Voltage on the Power lines. So in terms of an Openhab design. There could a power failure at the Node or at the Server (Pi) In which a manual override of the switch would require a reaction from the Node without feedback from the OpenHab systesm
2. Wifi Interference: Considering we have a dense neighbourhood. There are times when there are something like 20/30 wifi signal inthe area. Sometimes casuing my Node to disconnect. In such a scenario I would like the Node to still be reactive.
3. Toggle Switches: Openhab Gui does not have a Toggle switch. A switch can only have a ON state or a OFF state. In this case How Can I depict to a user a two switch by simply using a ON/OFF status. If I were to use this a two way switch by toggling the switch I could have either turned it ON/OFF. I hope this is clear.
I looked around in the Openahab community and couldnt find any posts that spoke about an Implementation on 2 way switches. Perhaps I have unique Problem sets and if someone out there may have better Ideas. I would always love to hear more.

If you looks at what is perhaps the closest analog, Sonoff with Tasmota firmware:

  • Changes made outside openHAB get reported to the MQTT status topic
  • Commands are sent to the switch by posting messages to the command topic

If you manually trigger the switch, the Item in OH gets updated to match the state on the status topic.

This can all be done on the Item config.

If the connection to the Switch is not reliable, you can use the Expire Binding and some rules code to set the state of the Switch to NULL when the device disconnects. This way when there is no connection you know that OH doesn’t know what state the switch is in.

To have a toggle on the sitemap Just have a proxy Switch Item put on your sitemap using a MAPPINGS.

Switch item="Light_Proxy" MAPPINGS=[Toggle=ON]

This will put the Item with a button labeled Toggle that will send the command ON when it is pressed. Then use a simple rule to forward that command to your Lights over MQTT.

1 Like

Those are good suggestions!! Tried them and it works!!
THANKS!!