kakopappa / sinric

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

Possible to Set up a remote button? #32

Open mrlightsman opened 6 years ago

mrlightsman commented 6 years ago

I have several ESP-01s set up with sinric. All work terrific! I am wondering if it is possible to set up a separate ESP-01 with momentary N/O pushbuttons connected to the GPIO pins which when pressed will cause it to send an Alexa command to Sinric to toggle a device on an ESP that is programmed with a sinric device. (Essentially, acting like an Amazon Echo sending a command to sinric, but in an area where an Echo is not located.)

Think of it as setting up wireless 3Way switches where the main ESP (device programed) is replacing a standard toggle switch and the remote ESP (the device I'm trying to describe) is acting like the 3Way switch but it does not have access to the hardwired light circuit (like a traditional 3way lighting circuit would) or an echo device.

My thought is that a single ESP-01, using RX, TX, GPIO_0 and GPIO_2 as inputs could act as manual remote control to toggle (on/off depending on current state of the device) up to four sinric devices (programmed to other ESPs) when the (selected) pin is driven low (via pushbutton) and using a pullup resistor.

I haven't any idea what the action code would look like for this, but it seems the schematic would be pretty simple. I would, most preferably, use the WiFi Manager to establish the WiFi, API, and device_id parameters. Also, pressing two buttons at once would trigger a WiFi reset and spiffs format so the device could be reconfigured on demand.

I know this seems like I am trying to replace voice control with pushbutton control and that it is counter intuitive, but in my circumstance, having the flexibility to voice, phone via Alexa app, or manually trigger light switches is necessary as my family is hesitant to transition from old technology.

Any and all help and suggestions is appreciated. Thanks.

BoriKing commented 6 years ago

@mrlightsman

Give this a try: (the alexa app will not update the state of the device but you can still control it via alexa app/voce command or via momentary switch) Link deleted: (not what mrlightsman was looking for)

kakopappa may compile a better code in the future.

mrlightsman commented 6 years ago

@BoriKing Thanks for the quick response, but not quite what I am looking for. Let me try to explain again.

My concept involves using multiple separate ESPs.

The first type of ESP, which I will call "Sinric" ESPs are set up as a traditional sinric two device switches. They use the WiFi Manager code to get parameters for WiFi, API and device_ID (thanks @BoriKing). They work as alexa switches very much as @kakopappa has designed.

The second type of ESP, which I will call "Remote" ESPs have four momentary N/O pushbuttons connected to pins: RX, TX, GPIO_0 and GPIO_2. All pins are pulled high and will be driven low if their button is pressed. These would use WiFi Manager to set parameters for Wifi, API, and associated device_IDs to control.

The concept is that by pushing a button on the Remote ESP, it will signal a Sinric ESP to toggle its associated device_ID to the opposite state. Pressing two buttons simultaneously on the Remote will cause an on-demand reset of the WiFi manager of the Remote ESP.

As an example: Pressing button _ on Remote causes ____ to toggle on/off: TX == Device_ID 1 on Sinric 1 RX == Device_ID 2 on Sinric 1 GPIO_0 == Device_ID 1 on Sinric 2 GPIO_2 == Device_ID 2 on Sinric 3 TX & RX == Causes WiFi Manager Reset on Remote

Since the associated controlled device_IDs are configured in WiFi Manager on the Remote ESP, they can be reconfigured using the on-demand WiFi manager reset.

So, in summary, it seems the Remote ESP must mimic an alexa command either to the Alexa server, or directly to sinric.com (I really don't know how the backend of Sinric.com works) so the Sinric ESPs will receive the command sent by the Remote ESP and respond to it.

Again, thanks to all for helping.

kakopappa commented 6 years ago

Why not send a UDP broadcast in the wifi network rather than sending to sinric and getting it back on the other ESP ?

mrlightsman commented 6 years ago

UDP would probably work and I would be interested in learning more about it. I have even less knowledge about how that protocol works than I do with the Alexa. Would sending a UDP broadcast cause traffic problems on my LAN/WLAN? I already have several consumer product security cameras and media devices sending broadcast packets. What might integration of UDP look like when adding it to current sinric_switch code? Or for the remote ESP code?

The only reason I was looking to stay "alexa" is to keep everything operating in one code environment and as flexible and configurable as possible. I like the idea that I can pack these up and move to a new location and with some quick adjustment in the WiFi Manager be up and running in no time. My thinking is pretty simplistic, although I'm sure implementation is not. I found this page on the Alexa developer site.

https://developer.amazon.com/docs/device-apis/alexa-powercontroller.html

What I don't understand is:

  1. Are the command codes what the Alexa server is sending when it hears "turn on..."? If so, how does sinric.com interact with these? Can it interact with a Remote ESP in the same way?
  2. If these code samples are what is needed to make a Remote ESP send commands to a sinric switch, how does it translate into Arduino IDE?
  3. How do I make the Sinric ESPs listen for commands sent from the Remote ESP? Does the remote somehow send it to Sinric.com, or do I need to have the Sinric ESP listening to two locations?
  4. Can I make the Remote ESP probe the status (on/off) of the Sinric ESP device so it knows which way to toggle it?

As always, I am very appreciative of the help and hope these discussions help others too.

kakopappa commented 6 years ago

This is the current flow

Your voice > Alexa > Amazon Cloud > Sinric.com > Your ESP. Sinric just forward the commands sent by Amazon to the correct ESP and that's it. ESPs do not send commands to Sinric.

if you want to send a command from ESP to ESP look for a UDP Server, client code in google. That's the easy way. If you turn it off manually, status on Alexa app will go out of sync.

sending a UDP broadcasts does not cause traffic problems unless you sent often

mrlightsman commented 6 years ago

Thanks for the reply. Knowing how Sinric works helps. I'll do some research on UDP code and let you know what I've learned.

mrlightsman commented 6 years ago

OK, I know this is way off topic from Sinric, but following the thread you'll understand why I'm asking. I've managed to get a UDP transmitter working using a multicast ip and port. I can see the messages using a port listener on my phone. The transmitter sends a deviceId to the multicast.

However, I cannot get the receiver (client) to listen for and hear messages. This is code example I started with for the receiver.

#include <ESP8266WiFi.h>
#include <WiFiUDP.h>

int status = WL_IDLE_STATUS;
const char* ssid = "mySSID";  //  your network SSID (name)
const char* pass = "myPASSWORD";       // your network password

unsigned int localPort = 3300;      // local port to listen for UDP packets

byte packetBuffer[512]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP Udp;

// Multicast declarations
IPAddress ipMulti(161, 57, 37, 1);
unsigned int portMulti = 3300;      // local port to listen on

void setup()
{
  // Open serial communications and wait for port to open:
  Serial.begin(115200);

  // setting up Station AP
  WiFi.begin(ssid, pass);

  // Wait for connect to AP
  Serial.print("[Connecting]");
  Serial.print(ssid);
  int tries=0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    tries++;
    if (tries > 30){
      break;
    }
  }
  Serial.println();

printWifiStatus();

  Serial.println("Connected to wifi");
  Serial.print("Udp Multicast server started at : ");
  Serial.print(ipMulti);
  Serial.print(":");
  Serial.println(portMulti);
  Udp.beginMulticast(WiFi.localIP(),  ipMulti, portMulti);
}

void loop()
{
  int noBytes = Udp.parsePacket();
  if ( noBytes ) {
    Serial.print(millis() / 1000);
    Serial.print(":Packet of ");
    Serial.print(noBytes);
    Serial.print(" received from ");
    Serial.print(Udp.remoteIP());
    Serial.print(":");
    Serial.println(Udp.remotePort());
    // We've received a packet, read the data from it
    Udp.read(packetBuffer,noBytes); // read the packet into the buffer

    // display the packet contents in HEX
    for (int i=1;i<=noBytes;i++){
      Serial.print(packetBuffer[i-1],HEX);
      if (i % 32 == 0){
        Serial.println();
      }
      else Serial.print(' ');
    } // end for
    Serial.println();

    // send a reply, to the IP address and port that sent us the packet we received
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write("UDP packet received");
    Udp.endPacket();    
  } // end if

}

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);
}

What am I doing wrong? Why won't it listen for a UDP communication? By the way, the message I need to send is a deviceId from my sinric account. Thanks.

mrlightsman commented 6 years ago

@BoriKing @kakopappa

I've done a lot of work on this project over the past two weeks and appreciate the tips you have offered. Here is what I've come up with:

  1. Transmitter: Used as a remote switch to control up to four Sinric devices built on an ESP01 --Uses WiFi Manager to set four device IDs, multicast IP, Multicast port, and SSID for the ESP when in AP mode. --Has four physical buttons, which when one is pressed will trigger a Udp multicast of a device_ID for receivers the listen for and react to.

  2. Receiver: A two device Sinric Switch built on an ESP01 --Uses WiFi Manager to set two device IDs, Sinric API Key, Multicast IP, Multicast port, and SSID for the ESP when in AP mode. --Has two physical buttons, which when one is pressed will manually trigger one of the devices --Uses Sinric to connect to the Alexa server and respond to voice commands --Uses Udp Multicast to receive and react to commands from a transmitter

Here is my problem. Multicast is not working. Unicast works great, but because I am getting IP addresses via DHCP, unicast is not practical. Also, it is likely that one transmitter will be associated to multiple receivers... since transmitter has four buttons and receivers only have two devices.

I need help with two issues specifically:

  1. Why isn't multicast working? What do I misunderstand or have coded wrong? I thought the idea was that you broadcast a Udp packet to the multicast IP and multicast port and any receiver that is listening to that IP address and port (despite its own, actual IP address) will hear it and react. I can only get it to work if I broadcast to the ESPs actual IP address.
  2. In the codes I am posting, I still have to hard code the multicast port because I don't know how to get char multiPort (a user input from the captive portal) to convert to unsigned int portMulti (for use in Udp.beginMulticast() command).

Is there anything I can do to either fix multicast, or substitute another form of communication? Thank you for your help and comments.

Here are my two sets of code. In both cases, references to RX & TX pins are commented out so I can use serial debug.

CODE FOR RECEIVER

#include <FS.h>  // this needs to be first, or it all crashes and burns...

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WebSocketsClient.h>   // get it from https://github.com/Links2004/arduinoWebSockets/releases 
#include <ArduinoJson.h>        // get it from https://arduinojson.org/ or install via Arduino library manager
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>        // get it from https://github.com/the-real-orca/WiFiManager
#include <WiFiUDP.h>  //get it from https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi/src

/* This version of the sinric switch uses the multi wifi manager to input a multicast ip address, multicast port, wifi, API, and two device ids.
 * It is designed to work with an ESP8266-01 with the following pin use:
 * GPIO_2 = device 1. LOW is ON.  HIGH is OFF.  Intended to sink to an optoisolator with pin 1 connected to Vcc and pin 2 to GPIO_2.
 * GPIO_0 = device 2. LOW is ON.  HIGH is OFF.  Intended to sink to an optoisolator with pin 1 connected to Vcc and pin 2 to GPIO-0.
 * RX (pin 3) = Used to manually toggle device_ID 1. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * TX (pin 1) = Used to manually toggle device_ID 2. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * Pressing both TX and RX buttons (to low) will cause the ESP to reset and go into AP WiFiManager mode.
 * The ESP also listens for a UDP packet from a remote ESP to toggle the devices.
 * All other ESP pins are connected per usual ESP set up.
 */

WebSocketsClient webSocket;
WiFiClient client;
WiFiUDP Udp;

int GPIO_2 = 2; //To control Device 1
int GPIO_0 = 0; //To control Device 2
//int RX = 3;  //To manually toggle device 1
//int TX = 1;  //To manually toggle device 2
//int RXState; //State of RX pin
//int TXState; //State of TX pin
int GPIO_2State; //State of device 1
int GPIO_0State; //State of device 2

#define API_ENDPOINT "http://sinric.com"
#define HEARTBEAT_INTERVAL 300000 // 5 Minutes 

//Set variables for multicast
//IPAddress ipMulti(239,100,100,100); //multicast IP Address
//unsigned int portMulti = 3300; // local port to listen on
IPAddress ipMulti;
char multiIP[16]; //used to store in json a Multicast IP address from user input on WiFiManager page
char multiPort[6]; //used to store in json a multicast port from user input on WiFi Manager page
unsigned int portMulti;  // used in multicast command below
char pktBuf[25]; //buffer for UDP packets

//Define your Box name and SSID
char ap_SSID[25] = "NewBox";

//Define variables for Alexa communication
char api_key[37]; //sinric API
char first_deviceId[25]; //Alexa id 1
char second_deviceId[25]; // Alexa id 2

//Define variables for heartbeat
uint64_t heartbeatTimestamp = 0;
bool isConnected = false;

bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  shouldSaveConfig = true;
}

void toggle(String deviceId) {
  if (deviceId == (first_deviceId)) {
    GPIO_2State = digitalRead(GPIO_2);
    digitalWrite(GPIO_2, !GPIO_2State);
       Serial.println("Device 1");
       Serial.println("Status:");
       Serial.println(GPIO_2State);
       Serial.println("Waiting");
  }
  else if (deviceId == (second_deviceId)) {
    GPIO_0State = digitalRead(GPIO_0);
    digitalWrite(GPIO_2, !GPIO_0State);
       Serial.println("Device 2:");
       Serial.println("Status:");
       Serial.println(GPIO_0State);
       Serial.println("Waiting");
  }  
}

void turnOn(String deviceId) {
  if (deviceId == (first_deviceId)) // Device ID of first device
  { 
    digitalWrite(GPIO_2, LOW);
    Serial.println("Device 1 On"); 
  } 
  else if (deviceId == (second_deviceId)) // Device ID of second device
  { 
    digitalWrite(GPIO_0, LOW);
    Serial.println("Device 2 On");
  }
  else {  // Ignore unknow device ID received
  }     
}

void turnOff(String deviceId) {
   if (deviceId == (first_deviceId)) // Device ID of first device
   {  
      digitalWrite(GPIO_2, HIGH);
      Serial.println("Device 1 Off");
   }
   else if (deviceId == (second_deviceId)) // Device ID of second device
   { 
      digitalWrite(GPIO_0, HIGH);
      Serial.println("Device 2 Off");
  }
  else {  // Ignore unknow device ID received
  }
}

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_DISCONNECTED:
      isConnected = false;    
      break;
    case WStype_CONNECTED: {
      isConnected = true;
      }
      break;
    case WStype_TEXT: {
        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 == "setBrightness") {
        }
        else if(action == "AdjustBrightness") {
        }
        else if (action == "test") {
        }
      }
      break;
    case WStype_BIN:
      break;
  }
}

void setup() {
  Serial.begin(115200);

  // Setup pins
  pinMode(GPIO_2, OUTPUT);
  digitalWrite(GPIO_2, HIGH);
  pinMode(GPIO_0, OUTPUT);
  digitalWrite(GPIO_0, HIGH);
//  pinMode(RX, INPUT_PULLUP);
//  pinMode(TX, INPUT_PULLUP);

//  Initiate config file
  if (SPIFFS.begin()) {
    if (SPIFFS.exists("/config.json")) {
      File configFile = SPIFFS.open("/config.json", "r");

      if (configFile) {
        size_t size = configFile.size();

     // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);
        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");
          strcpy(ap_SSID, json["ap_SSID"]);
          strcpy(multiIP, json["multiIP"]);
          strcpy(multiPort, json["multiPort"]);
          strcpy(api_key, json["api_key"]);
          strcpy(first_deviceId, json["first_deviceId"]);
          strcpy(second_deviceId, json["second_deviceId"]);
        } else {
        }
      }
    }
  } else {
  }
  //end read

  // Parameters
  WiFiManagerParameter custom_ap_SSID("ap_SSID", "SSID Identifier", ap_SSID, 25);
  WiFiManagerParameter custom_multiIP("multiIP", "Multicast IP", multiIP, 16);
  WiFiManagerParameter custom_multiPort("multiPort", "Multicast Port", multiPort, 6);
  WiFiManagerParameter custom_api_key("api key", "api key", api_key, 37);
  WiFiManagerParameter custom_first_deviceId("1st-deviceId", "1st deviceId", first_deviceId, 25);
  WiFiManagerParameter custom_second_deviceId("2nd-deviceId", "2nd deviceId", second_deviceId, 25);

  //WiFiManager
  //Local intialization.

  WiFiManager wifiManager;
  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //add all your parameters here
  wifiManager.addParameter(&custom_ap_SSID);
  wifiManager.addParameter(&custom_multiIP);
  wifiManager.addParameter(&custom_multiPort);
  wifiManager.addParameter(&custom_api_key);
  wifiManager.addParameter(&custom_first_deviceId);
  wifiManager.addParameter(&custom_second_deviceId);

  if(!wifiManager.autoConnect(ap_SSID)) {
    delay(3000);
    ESP.reset();
    delay(5000);
  }

  //if you get here you have connected to the WiFi

  //read updated parameters
  strcpy(ap_SSID, custom_ap_SSID.getValue());
  strcpy(multiIP, custom_multiIP.getValue());
  strcpy(multiPort, custom_multiPort.getValue());
  strcpy(api_key, custom_api_key.getValue());
  strcpy(first_deviceId, custom_first_deviceId.getValue());
  strcpy(second_deviceId, custom_second_deviceId.getValue());

  //save the custom parameters to FS
  if (shouldSaveConfig) {
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    json["ap_SSID"] = ap_SSID;
    json["multiIP"] = multiIP;
    json["multiPort"] = multiPort;
    json["api_key"] = api_key;
    json["first_deviceId"] = first_deviceId;
    json["second_deviceId"] = second_deviceId;
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
    }
    json.printTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }

   delay(500);

  // server address, port and URL
  webSocket.begin("iot.sinric.com", 80, "/");

  // event handler
  webSocket.onEvent(webSocketEvent);
  webSocket.setAuthorization("apikey", api_key);
  // try again every 5000ms if connection has failed
  webSocket.setReconnectInterval(5000);   // If you see 'class WebSocketsClient' has no member named 'setReconnectInterval' error update arduinoWebSockets

///////////////////////////////////////////////////////////////////////////////////////////////
//Start Udp multicast server
IPAddress ipMulti;
ipMulti.fromString(multiIP);
//portMulti.fromString(multiPort);
//or
//portMulti.toInt(multiPort);
portMulti = 3300;
Udp.beginMulticast(WiFi.localIP(), ipMulti, portMulti);
Serial.println("Multicast IP");
Serial.println(ipMulti);
Serial.println("Multicast port");
Serial.println(portMulti);
//////////////////////////////////////////////////////////////////////////////////////////////
}

void loop() {
  //Get the state of all four pins
//    RXState = digitalRead(RX); //Toggle button for first_deviceId (LOW is pressed)
//    TXState = digitalRead(TX); //Toggle button for second_deviceId (LOW is pressed)

//Receive incoming Udp packet and parse information
  int pktSize = Udp.parsePacket();
  if (pktSize) {
    Serial.print(Udp.remoteIP());
    Serial.print(":");
    Serial.println(Udp.remotePort());
    Udp.read(pktBuf, pktSize);
    Udp.stop();
    Udp.beginMulticast(WiFi.localIP(), ipMulti, portMulti);
   }

  String packetbuffer (pktBuf);
  //What to do with Udp packet
     //Toggle first_deviceId
     if (packetbuffer.startsWith(first_deviceId)) {
          String deviceId = (first_deviceId);
          toggle(deviceId);
        String packetbuffer = "";
        for (int i = 0; i<25; i++){
           pktBuf[i] = (char) 0;
           }
        delay(1000);
        }

     //Toggle second_deviceId
     else if (packetbuffer.startsWith(second_deviceId)) {
         String deviceId = (second_deviceId);
         toggle(deviceId);
       String packetbuffer = "";
       for (int i = 0; i<25; i++){
         pktBuf[i] = (char) 0;
         }
       delay(1000);
        }

  //What to do if physical buttons are pressed
    //If RX & TX are LOW then reset the ESP into AP mode
//    else if (RXState ==LOW && TXState == LOW) {
//     WiFiManager wifiManager;
//       SPIFFS.format(); // comment this out to keep data in SPIFFS
//       delay(1000);
//       wifiManager.resetSettings();
//       delay(1000);
        //reset and try again, or maybe put it to deep sleep    
//       {
//        ESP.reset();
//        delay(5000);
//       }
//     }

    //Else if RX is LOW and TX is HIGH flip first_deviceId
//    else if (RXState == LOW && TXState == HIGH) {
//      digitalWrite(GPIO_2, !GPIO_2State);
//      delay(1000);
//    }
    // Else if RX is HIGH and TX is LOW flip second_deviceId
//    else if (RXState == HIGH && TXState == LOW) {
//      digitalWrite(GPIO_0, !GPIO_0State);
//      delay(1000);
//    }
else {
}

  webSocket.loop();

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

      // Send heartbeat in order to avoid disconnections during ISP resetting IPs over night.
      if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) {
          heartbeatTimestamp = now;
          webSocket.sendTXT("H");          
      }
  }   
}

CODE FOR TRANSMITTER

#include <FS.h>  // this needs to be first, or it all crashes and burns...

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>        // get it from https://arduinojson.org/ or install via Arduino library manager
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>        // get it from https://github.com/the-real-orca/WiFiManager
#include <WiFiUdp.h>  //get it from https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266WiFi/src

/* This version of the sinric remote uses the multi wifi manager to input a multicast IP, multicast port, wifi, API, and two device ids.
 * It is designed to work with an ESP8266-01 with the following pin use:
 * GPIO_2 = Used to remotely toggle device_ID 1. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * GPIO_0 = Used to remotely toggle device_ID 2. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * RX (pin 3) = Used to remotely toggle device_ID 3. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * TX (pin 1) = Used to remotely toggle device_ID 4. Connect a momentary N/O switch to GROUND and RX with a 220 ohm pullup resistor to Vcc.
 * Pressing both TX and RX buttons (to low) will cause the ESP to reset and go into AP WiFiManager mode.
 * All other ESP pins are connected per usual ESP set up.
 *
 * This remote uses multicast UDP to communicate with sinric switches on the same WLAN.  Each button is set to control a single device and is set up using the WiFi manager.
 */

// Set ESP into WiFi client and turn on Udp
WiFiClient client;
WiFiUDP Udp;

//Set variables for multicast
//IPAddress ipMulti(239,100,100,100); //multicast IP Address
//unsigned int portMulti = 3300; // local port to listen on
IPAddress ipMulti;
char multiIP[16]; //used to store in json a Multicast IP address from user input on WiFiManager page
char multiPort[6]; //used to store in json a multicast port from user input on WiFi Manager page
unsigned int portMulti;  // used in multicast command below

//Define your Box name and SSID
char ap_SSID[25] = "NewBox";

// Set pin names and variables
int GPIO_2 = 2; //To manually toggle device 1
int GPIO_0 = 0; //To manually toggle device 2
int RX = 3;  //To manually toggle device 3
int TX = 1;  //To manually toggle device 4
int GPIO_2State; //State of device 1
int GPIO_0State; //State of device 2
int RXState; //State of device 3
int TXState; //State of device 4

//Set variables for use in WiFiManager
char deviceId[25];
char first_deviceId[25];
char second_deviceId[25];
char third_deviceId[25];
char fourth_deviceId[25];

#define API_ENDPOINT "http://sinric.com"
#define HEARTBEAT_INTERVAL 300000 // 5 Minutes
uint64_t heartbeatTimestamp = 0;
bool isConnected = false;
bool shouldSaveConfig = false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

void setup() {
  Serial.begin(115200);
//  Serial.println();

  // Setup pins
  pinMode(GPIO_2, INPUT_PULLUP);
  pinMode(GPIO_0, INPUT_PULLUP);
//  pinMode(RX, INPUT_PULLUP);
//  pinMode(TX, INPUT_PULLUP);

  Serial.println("mounting FS...");
  if (SPIFFS.begin()) {
    if (SPIFFS.exists("/config.json")) {

      //file exists, reading and loading
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
      }
        size_t size = configFile.size();

        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);
        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        json.printTo(Serial);
        if (json.success()) {
          Serial.println("\nparsed json");
          strcpy(ap_SSID, json["ap_SSID"]);
          strcpy(multiIP, json["multiIP"]);
          strcpy(multiPort, json["multiPort"]);
          strcpy(first_deviceId, json["first_deviceId"]);
          strcpy(second_deviceId, json["second_deviceId"]);
          strcpy(third_deviceId, json["third_deviceId"]);
          strcpy(fourth_deviceId, json["fourth_deviceId"]);

        } else {
          Serial.println("failed to load json config");
        }
      }
 //   }
  } else {
  }
  //end read

  // Parameters
  WiFiManagerParameter custom_ap_SSID("ap_SSID", "SSID Identifier", ap_SSID, 25);
  WiFiManagerParameter custom_multiIP("multiIP", "Multicast IP", multiIP, 16);
  WiFiManagerParameter custom_multiPort("multiPort", "Multicast Port", multiPort, 6);
  WiFiManagerParameter custom_first_deviceId("1st-deviceId", "1st deviceId", first_deviceId, 25);
  WiFiManagerParameter custom_second_deviceId("2nd-deviceId", "2nd deviceId", second_deviceId, 25);
  WiFiManagerParameter custom_third_deviceId("3rd-deviceId", "3rd deviceId", third_deviceId, 25);
  WiFiManagerParameter custom_fourth_deviceId("4th-deviceId", "4th deviceId", fourth_deviceId, 25);

  //WiFiManager
  //Local intialization.

  WiFiManager wifiManager;
  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //add all your parameters here
  wifiManager.addParameter(&custom_ap_SSID);
  wifiManager.addParameter(&custom_multiIP);
  wifiManager.addParameter(&custom_multiPort);
  wifiManager.addParameter(&custom_first_deviceId);
  wifiManager.addParameter(&custom_second_deviceId);
  wifiManager.addParameter(&custom_third_deviceId);
  wifiManager.addParameter(&custom_fourth_deviceId);

  if(!wifiManager.autoConnect(ap_SSID)) {
    Serial.println("failed to connect and hit timeout");
    delay(3000);
    ESP.reset();
    delay(5000);
  }
 Serial.println("connected to wifi.");
  //if you get here you have connected to the WiFi

  //read updated parameters
  strcpy(ap_SSID, custom_ap_SSID.getValue());
  strcpy(multiIP, custom_multiIP.getValue());
  strcpy(multiPort, custom_multiPort.getValue());
  strcpy(first_deviceId, custom_first_deviceId.getValue());
  strcpy(second_deviceId, custom_second_deviceId.getValue());
  strcpy(third_deviceId, custom_third_deviceId.getValue());
  strcpy(fourth_deviceId, custom_fourth_deviceId.getValue());

  //save the custom parameters to FS
  if (shouldSaveConfig) {
    Serial.println("saving config");
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    json["ap_SSID"] = ap_SSID;
    json["multiIP"] = multiIP;
    json["multiPort"] = multiPort;
    json["first_deviceId"] = first_deviceId;
    json["second_deviceId"] = second_deviceId;
    json["third_deviceId"] = third_deviceId;
    json["fourth_deviceId"] = fourth_deviceId;
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
      Serial.println("failed to open config file for writing");
    }
    json.printTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
        delay(500);

///////////////////////////////////////////////////////////////////////////////////////////////
//Start Udp multicast server
IPAddress ipMulti;
ipMulti.fromString(multiIP);
//portMulti.fromString(multiPort);
//or
//portMulti.toInt(multiPort);
portMulti = 3300;
Udp.beginMulticast(WiFi.localIP(), ipMulti, portMulti);
Serial.println("Multicast IP");
Serial.println(ipMulti);
Serial.println("Multicast port");
Serial.println(portMulti);
//////////////////////////////////////////////////////////////////////////////////////////////

}

void loop() {

  //Get the state of all four pins
  GPIO_2State = digitalRead(GPIO_2); //Toggle button for device 1 (LOW is pressed
  GPIO_0State = digitalRead(GPIO_0); //Toggle button for device 2 (LOW is pressed
//  RXState = digitalRead(RX); //Toggle button for device 3 (LOW is pressed)
//  TXState = digitalRead(TX); //Toggle button for device 4 (LOW is pressed)
                             //Pressing GPIO_2 & TX Button invokes WifiManager reset

 //IF GPIO_2, GPIO_0, TX & RX are LOW Reset ESP into AP mode and format SPIFFS
//    if (GPIO_2State == LOW && GPIO_0State == LOW && TXState == LOW && RXState == LOW) {
//      Serial.println("Reset invoked");
//        WiFiManager wifiManager;
//        SPIFFS.format();
//        Serial.println("config formatted");
//        delay(1000);
//        wifiManager.resetSettings();
//        delay(1000);
//     //reset and try again, or maybe put it to deep sleep    
//     {
//       ESP.reset();
//       delay(2000);
//        }
//   }

 //If GPIO_2 & GPIO_0 are LOW then reset the ESP into AP mode
//    else if (GPIO_2State ==LOW && GPIO_0State == LOW) {
    if (GPIO_2State ==LOW && GPIO_0State == LOW) { //REMOVE THIS LINE AFTER TESTING
      Serial.println("Reset invoked");
        WiFiManager wifiManager;
        Serial.println("config formatted");
        delay(1000);
        wifiManager.resetSettings();
        delay(1000);
     //reset and try again, or maybe put it to deep sleep    
     {
       ESP.reset();
       delay(2000);
        }
   }

  //if GPIO_2 is LOW toggle first_deviceId
//  if (GPIO_2State == LOW) { //REMOVE THIS LINE AFTER TESTING
    else if (GPIO_2State == LOW) {
      Serial.println("Device 1:");
      Serial.println(first_deviceId);
      Serial.println("local ip");
      Serial.println(WiFi.localIP());
      Serial.println("Multicast IP");
      Serial.println(ipMulti);
      Serial.println("Multicast port");
      Serial.println(portMulti);
      Serial.println();
      Udp.beginPacketMulticast(WiFi.localIP(), ipMulti, portMulti);
      Udp.write(first_deviceId);
      Udp.endPacket();
      delay(1000);
    }

 //if GPIO_0 is LOW toggle Second_deviceId
    else if (GPIO_0State== LOW) {
      Serial.println("Device 2:");
      Serial.println(second_deviceId);
      Serial.println("local ip");
      Serial.println(WiFi.localIP());
      Serial.println("Multicast IP");
      Serial.println(ipMulti);
      Serial.println("Multicast port");
      Serial.println(portMulti);
      Serial.println();
      Udp.beginPacketMulticast(WiFi.localIP(), ipMulti, portMulti);
      Udp.write(second_deviceId);
      Udp.endPacket();
      delay(1000);
    }

 //if RX is LOW toggle Third_deviceId
 //   else if (RXState== LOW) {
 //     Udp.beginPacketMulticast(WiFi.localIP(), ipMulti, portMulti);
 //     Udp.write(third_deviceId);
 //     Udp.endPacket();
 //     delay(1000);
 //   }

 //if TX is LOW toggle fourth_deviceId
 //   else if (TXState== LOW) {
 //     Udp.beginPacket(ipMulti, portMulti);
 //     Udp.write(fourth_deviceId);
 //     Udp.endPacket();
 //     delay(1000);
 //   }
}