kakopappa / sinric

Amazon Alexa Smart home skill / Google Home Action for ESP8266 / ESP32 / Arduino
https://sinric.com
285 stars 166 forks source link

Integration of Sinric Device with Home Assistant #332

Open kanavkapoor7 opened 4 years ago

kanavkapoor7 commented 4 years ago

Hi, Thank you for your code @kakopappa However I need a little more help. Currently I am using the example 'switch_example.ino' of yours on ESP8266 (Attached to 2 Relay board) and the device is working perfectly with Alexa, But I also want to integrate it with Home Assistant, Is it possible to achieve that because as of now home assistant is unable to discover the device.

What would be changes required in the code in order to achieve that ?

It would be extremely helpful if you could share the code for the same if you have already done it.

Your help would be highly appreciable.

kakopappa commented 4 years ago

hi there.

sorry i am not familair with Home Assistant. Sinric / Alexa app already does almost everything.

After taking a quick look, I assume you have to use the mqtt interface to update the Home Assistant to keep the two system in sync https://www.home-assistant.io/docs/mqtt/service/

kanavkapoor7 commented 4 years ago

Thank you for your comment @kakopappa I went through the entire MQTT setup, but the problem is how can I modify the existing Sinric fimware that I am feeding into the ESP8266 to make it to connect to MQTT broker on Home Assistant. It would be amazing if you could share some code to merge both these systems (MQTT & Sinric)

adukale commented 4 years ago

Thank you @kakopappa for amazing sinric library.

Hope someone finds this helpful. Using mqtt, the sinric program can retain the state.When device restarts the program requests status fom mqtt broker and sets the device state.

Integrating mqtt into sinric code: Following code needs to be merged into any sinric example.

Include and definitions:

#include <PubSubClient.h>
void callback(char* topic, byte* payload, unsigned int length);
#define MQTT_SERVER "192.168.x.xxx"  //you mqtt IP Address

char const* switchTopic1 = "/house/switch1/";
char const* switchTopic2 = "/house/switch2/";
char const* switchTopic3 = "/house/switch3/";
char const* switchTopic4 = "/house/switch4/";

WiFiClient wifiClient;
PubSubClient client(MQTT_SERVER, 1883, callback, wifiClient);

Add this to setup function:

reconnect(); Add this to loop():

//Attempt to connect if not connected
  if (!client.connected() && WiFi.status() == 3) 
{
reconnect();
}
  //maintain MQTT connection
  client.loop();

Add these functions:

void reconnect() {

  //attempt to connect to the wifi if connection is lost
  if(WiFi.status() != WL_CONNECTED){
    //debug printing
    Serial.print("Connecting to ");
    Serial.println(ssid);

    //loop while we wait for connection
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }

    //print out some more debug once connected
    Serial.println("");
    Serial.println("WiFi connected");  
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
  }

  //make sure we are connected to WIFI before attemping to reconnect to MQTT
  if(WiFi.status() == WL_CONNECTED){
  // Loop until we're reconnected to the MQTT server
    while (!client.connected()) {
      Serial.print("Attempting MQTT connection...");

      String clientName;
      clientName += "BedRoomSwitch"; // change as per your choice this will be your device name

      if (client.connect((char*) clientName.c_str(),"mqttuser", "mqttpass")) {  //EJ: Update accordingly with your MQTT account 
        Serial.print("\tMQTT Connected");
        client.subscribe(switchTopic1);
        client.subscribe(switchTopic2);
        client.subscribe(switchTopic3);
        client.subscribe(switchTopic4);

      }

      //otherwise print failed for debugging
      else{Serial.println("\tFailed."); abort();}
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {

  String topicStr = topic; 

  //Print out some debugging info
  Serial.println("Callback update.");
  Serial.print("Topic: ");
  Serial.println(topicStr);

   if (topicStr == "/house/switch1/") 
    {

     //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
     if(payload[0] == '1'){
       digitalWrite(switchPin1, HIGH);
       client.publish("/house/switchConfirm1/", "1");
       }

      //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
     else if (payload[0] == '0'){
       digitalWrite(switchPin1, LOW);
       client.publish("/house/switchConfirm1/", "0");
       }
     }

     else if (topicStr == "/house/switch2/") 
     {
     //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
     if(payload[0] == '1'){
       digitalWrite(switchPin2, HIGH);
       client.publish("/house/switchConfirm2/", "1");
       }

      //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
     else if (payload[0] == '0'){
       digitalWrite(switchPin2, LOW);
       client.publish("/house/switchConfirm2/", "0");
       }
     }
     else if (topicStr == "/house/switch3/") 
     {
     //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
     if(payload[0] == '1'){
       digitalWrite(switchPin3, HIGH);
       client.publish("/house/switchConfirm3/", "1");
       }

      //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
     else if (payload[0] == '0'){
       digitalWrite(switchPin3, LOW);
       client.publish("/house/switchConfirm3/", "0");
       }
     }
     else if (topicStr == "/house/switch4/") 
     {
     //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
     if(payload[0] == '1'){
       digitalWrite(switchPin4, HIGH);
       client.publish("/house/switchConfirm4/", "1");
       }

      //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
     else if (payload[0] == '0'){
       digitalWrite(switchPin4, LOW);
       client.publish("/house/switchConfirm4/", "0");
       }
     }
}

Add to configuration.yaml in homeassistant:

switch 1:
  platform: mqtt
  name: "MQTT Switch 1"
  state_topic: "/house/switchConfirm1/"
  command_topic: "/house/switch1/"
  payload_on: "1"
  payload_off: "0"
  qos: 0
  retain: true    

switch 2:
  platform: mqtt
  name: "MQTT Switch 2"
  state_topic: "/house/switchConfirm2/"
  command_topic: "/house/switch2/"
  payload_on: "1"
  payload_off: "0"
  qos: 0
  retain: true    

switch 3:
  platform: mqtt
  name: "MQTT Switch 3"
  state_topic: "/house/switchConfirm3/"
  command_topic: "/house/switch3/"
  payload_on: "1"
  payload_off: "0"
  qos: 0
  retain: true    

switch 4:
  platform: mqtt
  name: "MQTT Switch 4"
  state_topic: "/house/switchConfirm4/"
  command_topic: "/house/switch4/"
  payload_on: "1"
  payload_off: "0"
  qos: 0
  retain: true

This code is not my creation, this was copied and used from https://community.home-assistant.io/t/arduino-relay-switches-over-wifi-controlled-via-hass-mqtt-comes-with-ota-support/13439

Possible addon:

With every mqtt msg when switch state gets changed, we can call setPowerStateOnServer(DEVICE1, "ON/OFF"); so that device state is updated across services e.g. alexa, google home.

kanavkapoor7 commented 4 years ago

Hi @adukale Thank you soo much for sharing the above code, This was extremely helpful. Really appreciate you taking out the time to respond to my query. @adukale Just needed a little more help from you.

So when I am running my code it working perfectly fine with Alexa and google home mini, but the issue is that:

  1. When I change the state of the relay(NodeMcu Pin) from ON to OFF or Visa Versa through home assistant and then remove the power supply of the MCU board and then connect back, in this case I am able to get the last state of the switch (before poweroff) even after I power back on the MCU Board by using Retain: True in configaration.yaml.

  2. But In case if the switch state change is made by Alexa or Google home, I am not able to retain the last state that was there before power off, in this case the relays go back to the state which was last set by Home assistant after powering back on the Node MCU board

Could you please help me modify the code so that I am able to restore the last relay state which was there before powering off the Node MCU board irrespective of the fact that the state change was made by Alexa or Home Assistant.

Please find the below code that I am currently using for reference:

include

include

include

include

include

include

include

include

void callback(char topic, byte payload, unsigned int length);

define MQTT_SERVER "192.168.*****"

char const switchTopic1 = "/house/Geyser/"; char const switchTopic2 = "/house/AC/";

WiFiClient wifiClient; PubSubClient client(MQTT_SERVER, 1883, callback, wifiClient);

ESP8266WiFiMulti WiFiMulti; WebSocketsClient webSocket;

define MyApiKey "****"

define MySSID "****"

define MyWifiPassword "*****"

define HEARTBEAT_INTERVAL 300000 // 5 Minutes

uint64_t heartbeatTimestamp = 0; bool isConnected = false;

void setPowerStateOnServer(String deviceId, String value); void setTargetTemperatureOnServer(String deviceId, String value, String scale);

// deviceId is the ID assgined to your smart-home-device in sinric.com dashboard. Copy it from dashboard and paste it here

void turnOn(String deviceId) { bool ShouldCommit = true;

if (deviceId == "***") // Device ID of first device {
Serial.print("Turn on device Geyser"); Serial.println(deviceId);
digitalWrite(D1, LOW); // turn on relay with voltage HIGH client.publish("/house/switchConfirm1/", "1"); client.publish("/house/Geyser/", "1"); EEPROM.write(1,1);
}

else if (deviceId == "*****") // Device ID of first device {
Serial.print("Turn on device AC"); Serial.println(deviceId);
digitalWrite(D2, LOW); // turn on relay with voltage HIGH client.publish("/house/switchConfirm2/", "1"); client.publish("/house/AC/", "1"); EEPROM.write(2,1);
}

else { Serial.print("Turn on for unknown device id: "); Serial.println(deviceId); ShouldCommit = false;
} if (ShouldCommit) EEPROM.commit(); }

void turnOff(String deviceId) { bool ShouldCommit = true; if (deviceId == "***") // Device ID of first device {
Serial.print("Turn off Device Geyser"); Serial.println(deviceId);
digitalWrite(D1, HIGH); // turn off relay with voltage LOW client.publish("/house/switchConfirm1/", "0"); client.publish("/house/AC/", "0"); EEPROM.write(1,0); }

if (deviceId == "***") // Device ID of first device {
Serial.print("Turn off Device AC"); Serial.println(deviceId);
digitalWrite(D2, HIGH); // turn off relay with voltage LOW client.publish("/house/switchConfirm2/", "0"); client.publish("/house/AC/", "0");
EEPROM.write(2,0); }

else { Serial.print("Turn off for unknown device id: "); Serial.println(deviceId); ShouldCommit = false;
}

if (ShouldCommit) EEPROM.commit(); }

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) { switch(type) { case WStype_DISCONNECTED: isConnected = false;
Serial.printf("[WSc] Webservice disconnected from sinric.com!\n"); break; case WStype_CONNECTED: { isConnected = true; Serial.printf("[WSc] Service connected to sinric.com at url: %s\n", payload); Serial.printf("Waiting for commands from sinric.com ...\n");
} break; case WStype_TEXT: { Serial.printf("[WSc] get text: %s\n", payload);

    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.parseObject((char*)payload); 
    String deviceId = json ["deviceId"];     
    String action = json ["action"];

    if(action == "setPowerState") { // Switch or Light
        String value = json ["value"];
        if(value == "ON") {
            turnOn(deviceId);
        } else {
            turnOff(deviceId);
        }
    }
    else if (action == "action.devices.commands.OnOff") { // Switch
      String value = json["value"]["on"];
      Serial.println(value);

      if (value == "true") {
        turnOn(deviceId);
      } else {
        turnOff(deviceId);
      }}

    else if (action == "SetTargetTemperature") {
        String deviceId = json ["deviceId"];     
        String action = json ["action"];
        String value = json ["value"];
    }
    else if (action == "test") {
        Serial.println("[WSc] received test command from sinric.com");
    }
  }
  break;
case WStype_BIN:
  Serial.printf("[WSc] get binary length: %u\n", length);
  break;

} }

void setup() { Serial.begin(115200); EEPROM.begin(4); pinMode (D1, OUTPUT); pinMode (D2, OUTPUT);

if (EEPROM.read(1) == 0) digitalWrite (D1,HIGH); else digitalWrite (D1,LOW);

if (EEPROM.read(2) == 0) digitalWrite (D2,HIGH); else digitalWrite (D2,LOW);

WiFiMulti.addAP(MySSID, MyWifiPassword); Serial.println(); Serial.print("Connecting to Wifi: "); Serial.println(MySSID);

while(WiFiMulti.run() != WL_CONNECTED) { delay(500); Serial.print("."); } if(WiFiMulti.run() == WL_CONNECTED) { Serial.println(""); Serial.print("WiFi connected. "); Serial.print("IP address: "); Serial.println(WiFi.localIP()); }

webSocket.begin("iot.sinric.com", 80, "/");

webSocket.onEvent(webSocketEvent); webSocket.setAuthorization("apikey", MyApiKey);

webSocket.setReconnectInterval(5000); // If you see 'class WebSocketsClient' has no member named 'setReconnectInterval' error update arduinoWebSockets

reconnect();

}

void loop() { webSocket.loop();

if (!client.connected() && WiFi.status() == 3) { reconnect(); }

client.loop();

if(isConnected) { uint64_t now = millis();

  if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) {
      heartbeatTimestamp = now;
      webSocket.sendTXT("H");          
  }

}

}

void setPowerStateOnServer(String deviceId, String value) { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["deviceId"] = deviceId; root["action"] = "setPowerState"; root["value"] = value; StreamString databuf; root.printTo(databuf);

webSocket.sendTXT(databuf); }

void setTargetTemperatureOnServer(String deviceId, String value, String scale) { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["action"] = "SetTargetTemperature"; root["deviceId"] = deviceId;

JsonObject& valueObj = root.createNestedObject("value"); JsonObject& targetSetpoint = valueObj.createNestedObject("targetSetpoint"); targetSetpoint["value"] = value; targetSetpoint["scale"] = scale;

StreamString databuf; root.printTo(databuf);

webSocket.sendTXT(databuf); }

void reconnect() {

//attempt to connect to the wifi if connection is lost if(WiFi.status() != WL_CONNECTED){ //debug printing Serial.print("Connecting to "); Serial.println(MySSID); delay(5000);

//loop while we wait for connection
while (WiFi.status() != WL_CONNECTED) {
  delay(5000);
  Serial.print(".");
}

//print out some more debug once connected
Serial.println("");
Serial.println("WiFi connected");  
Serial.println("IP address: ");
Serial.println(WiFi.localIP());

}

//make sure we are connected to WIFI before attemping to reconnect to MQTT if(WiFi.status() == WL_CONNECTED){ // Loop until we're reconnected to the MQTT server while (!client.connected()) { Serial.print("Attempting MQTT connection...");

  String clientName;
  clientName += "Sinric_Mqtt"; // change as per your choice this will be your device name

  if (client.connect((char*) clientName.c_str(),"******", "********")) {  
    Serial.print("\tMQTT Connected");
    client.subscribe(switchTopic1);
    client.subscribe(switchTopic2);        
  }

  //otherwise print failed for debugging
  else{Serial.println("\tFailed."); abort();}
}

} }

void callback(char topic, byte payload, unsigned int length) {

String topicStr = topic; bool ShouldCommit = true;

//Print out some debugging info Serial.println("Callback update."); Serial.print("Topic: "); Serial.println(topicStr);

if (topicStr == "/house/Geyser/") {

 //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
 if(payload[0] == '1'){
   digitalWrite(D1, LOW);
   client.publish("/house/switchConfirm1/", "1");
   setPowerStateOnServer("**********************", "ON");
   EEPROM.write(1,1);
   }

  //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
 else if (payload[0] == '0'){
   digitalWrite(D1, HIGH);
   client.publish("/house/switchConfirm1/", "0");
   setPowerStateOnServer("**********************", "OFF");
   EEPROM.write(1,0);
   }
 }

 else if (topicStr == "/house/AC/") 
 {
 //turn the switch on if the payload is '1' and publish to the MQTT server a confirmation message
 if(payload[0] == '1'){
   digitalWrite(D2, LOW);
   client.publish("/house/switchConfirm2/", "1");
   setPowerStateOnServer("***********************", "ON");
   EEPROM.write(2,1);     
   }

  //turn the switch off if the payload is '0' and publish to the MQTT server a confirmation message
 else if (payload[0] == '0'){
   digitalWrite(D2, HIGH);
   client.publish("/house/switchConfirm2/", "0");
   setPowerStateOnServer("************************", "OFF");
   EEPROM.write(2,0);
   }
 }
   if (ShouldCommit)
   EEPROM.commit();
 }

Configuartion.yaml

adukale commented 4 years ago

@kanavkapoor7 Try with just client.publish("/house/switchConfirm2/", "1"); in turnon and client.publish("/house/switchConfirm2/", "0"); in turn off functions.

I have used just the state topic in my turnon and off functions to send state to home assistant when devices are controlled by alexa & google assistant and this works flawlessly. After rebooting or power failure, esp retains the last state.

kanavkapoor7 commented 4 years ago

Hi @adukale Thanks for the Update. As suggested above I tried using the below code but still no luck

void turnOn(String deviceId) 
{
  bool ShouldCommit = true;

  if (deviceId == "") // Device ID of first device
  {  
    Serial.print("Turn on device Geyser");
    Serial.println(deviceId);    
    digitalWrite(D1, LOW); // turn on relay with voltage HIGH
    client.publish("/house/switchConfirm1/", "1");
    EEPROM.write(1,1);     
  } 

  else if (deviceId == "") // Device ID of first device
  {  
    Serial.print("Turn on device AC");
    Serial.println(deviceId);    
    digitalWrite(D2, LOW); // turn on relay with voltage HIGH
    client.publish("/house/switchConfirm2/", "1");
    EEPROM.write(2,1);     
  } 

  else {
    Serial.print("Turn on for unknown device id: ");
    Serial.println(deviceId);
    ShouldCommit = false;   
  } 
    if (ShouldCommit)
    EEPROM.commit();
}

void turnOff(String deviceId) 
{
   bool ShouldCommit = true;
   if (deviceId == "") // Device ID of first device
   {  
     Serial.print("Turn off Device Geyser");
     Serial.println(deviceId);     
     digitalWrite(D1, HIGH);  // turn off relay with voltage LOW
     client.publish("/house/switchConfirm1/", "0");
     EEPROM.write(1,0);
   }

   if (deviceId == "") // Device ID of first device
   {  
     Serial.print("Turn off Device AC");
     Serial.println(deviceId);     
     digitalWrite(D2, HIGH);  // turn off relay with voltage LOW
     client.publish("/house/switchConfirm2/", "0");
     EEPROM.write(2,0); 
   }

   else {
     Serial.print("Turn off for unknown device id: ");
     Serial.println(deviceId);
     ShouldCommit = false;     
  }

  if (ShouldCommit)
    EEPROM.commit();
}

The Scenario that I am getting is as below:

  1. Switching AC ON via Home Assistant: Geyser: OFF AC: ON

  2. Then Switching AC OFF via Alexa Geyser: OFF AC: OFF

  3. Power Cutoff to ESP

  4. Power Restore to ESP

  5. State after power restore: Geyser: OFF AC: ON

So basically what is happening here is that irrespective of what state is set by Alexa, after power reset the states that get restored are the ones that were last set by HA

what is even more surprising is that the state changes made by Alexa are reflected correctly on HA but these changes made by alexa are not restored after power cut.

I have a feeling that somehow there could be an issue with the MQTT commands in the configuration.yaml file, could you please have a look at it and let me know if the below commands are as per what you have set in your system.

switch 1:
  - platform: mqtt
    name: "Geyser"
    state_topic: "/house/switchConfirm1/"
    command_topic: "/house/Geyser/"
    qos: 0
    payload_on: "1"
    payload_off: "0"
    retain: True

switch 2:
  - platform: mqtt
    name: "AC"
    state_topic: "/house/switchConfirm2/"
    command_topic: "/house/AC/"
    qos: 0
    payload_on: "1"
    payload_off: "0"
    retain: True

The other possibility could be that the changes made by the Alexa are not getting stored on the MQTT server. however the changes made by HA are gtting saved on MQTT Server that is why it always restores to the last change made by HA. Is there a way to make these state changes (made by Alexa) to get stored on MQTT server ?

adukale commented 4 years ago

@kanavkapoor7 You are right. There seems to be something with mqtt. Yesterday out of the blue my devices turned on or off automatically. Culprit was mqtt, i guess the issue relates to ghost switching. Retained mqtt msgs are getting relayed. Your issue seems to be related. Figuring this out, will let you know what i get out of it.

kakopappa commented 4 years ago

it seems really cumbersome to restore state after the power failer. To fix this problem, I will add a new method to resore the state state in Sinric v2.

I will release V2 for beta testing in two weeks 💯

adukale commented 4 years ago

@kakopappa Thanks for the update on sinric v2. Appreciate it.

Do you have something like providing minimal api in mind for v1? I was wondering if i can setup php script which will relay state and mqtt connections between Sinric and HomeAssistant. This will help many of us in the process.

kakopappa commented 4 years ago

https://github.com/kakopappa/sinric/wiki/API-Documentation

On Sat, Oct 12, 2019 at 1:21 AM Adwait Kale notifications@github.com wrote:

@kakopappa https://github.com/kakopappa Thanks for the update on sinric v2. Appreciate it.

Do you have something like providing minimal api in mind for v1? I was wondering if i can setup php script which will relay state and mqtt connections between Sinric and HomeAssistant. This will help many of us in the process.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/kakopappa/sinric/issues/332?email_source=notifications&email_token=ABZAZZVU46VOQE4BHYNJTS3QOC7ZTA5CNFSM4ITFFQOKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBAZ7RQ#issuecomment-541171654, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABZAZZWPX37EZUOJJRQO2STQOC7ZTANCNFSM4ITFFQOA .

adukale commented 4 years ago

@kakopappa Sorry, didnt look there. Will share my development here.

kanavkapoor7 commented 4 years ago

Really looking forward to support from you guys..!!