kakopappa / sinric

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

Lose Sinric Control with WiFi Manager w/parameters & custom IP #47

Open mrlightsman opened 6 years ago

mrlightsman commented 6 years ago

Probably not a Sinric issue, but it is manifesting itself here...

Because I am using a combination of UDP and Alexa, I need to be able to set the IP address with the WiFi manager. I am using the standard code found on Tzapu GitHub... WiFi Manager with parameters and custom IP.

The first time the ESP connects to wifi, it uses DHCP to set the IP. Then with reset, it shows the custom IP in the WiFi manager and can be changed, if desired. I am not changing the IP. I just want to see it.

The sinric code works fine on first ESP connect to wifi, but after reset, it no longer receives the sinric commands... possible conflict with websockets???

The UDP connections all work fine and physical buttons work also.

I'm puzzled and confused. I'm not sure what is happening in the SPIFFS or websockets upon reset of the ESP. I don't change the IP address or any parameters. If I comment out:


  //set static ip
  IPAddress _ip,_gw,_sn;
  _ip.fromString(static_ip);
  _gw.fromString(static_gw);
  _sn.fromString(static_sn);

  wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);

Everything works as expected after formatting the SPIFFS and reentering the parameters. I just can't identify the IP address.

Thoughts and suggestions are appreciated. The full code is listed here:

#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 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.
 * All other ESP pins are connected per usual ESP set up.
 */

 /*ESP8266-01 Flash settings:
  * Board - Generic ESP8266
  * Flash Mode - DIO
  * Flash Size - 1M (512K SPIFFS)
  * Debug Port - Disabled
  * Debug Level - None
  * lwIP Variant - V2 prebuilt (MSS=536)
  * Reset Method - ck
  * Crystal Frequency - 26M
  * Flash Frequency - 40MHz
  * CPU Frequency - 80MHz
  * Upload Speed - 115200
  * Port - TBD by your setup
  * Programmer - Arduino as ISP
  */

WebSocketsClient webSocket;
WiFiClient client;
WiFiUDP Udp;

//Swithc Contols variables
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 UDP server
char udp_port[6]; //used to store in json the udp port from user input on WiFi Manager page
unsigned int udpPort;  // used by udp.begin()command (it is udpPort converted for use)
char pktBuf[25]; //buffer for UDP packets

 //Define the local IP variables
char ap_SSID[25] = "NewBox"; //SSID for ESP when in AP mode
char static_ip[16]; // ip address for ESP when in client mode
char static_gw[16]; // gateway address for ESP when in client mode
char static_sn[16]; // subnet address for ESP when in client mode

//Define the Alexa variables
char api_key[37]; //Sinric API Key
char first_deviceId[25]; //alexa device 1
char second_deviceId[25]; //alexa device 2

//Define the heartbeat timer variables
uint64_t heartbeatTimestamp = 0; //heartbeat timer reset
bool isConnected = false;

//flag for saving data
bool shouldSaveConfig = false;

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

  //if a udp command is received, toggle the appropriate device
void toggle(String device_Id) {
  if (device_Id == (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 (device_Id == (second_deviceId)) {
    GPIO_0State = digitalRead(GPIO_0);
    digitalWrite(GPIO_0, !GPIO_0State);
//       Serial.println("Device 2:");
//       Serial.println("Status:");
//       Serial.println(GPIO_0State);
//       Serial.println("Waiting");
  }  
}

  //Alexa "ON" response
void turnOn(String deviceId) {
  if (deviceId == (first_deviceId)) {  // Device ID of first device  
//    Serial.print("Turn on 1st-device id: ");
//    Serial.println(deviceId);
        digitalWrite(GPIO_2, LOW);
  } else if (deviceId == (second_deviceId)) {  // Device ID of second device 
//    Serial.print("Turn on 2nd-device id: ");
//    Serial.println(deviceId);
        digitalWrite(GPIO_0, LOW);
  } else { 
//    Serial.print("Turn on for unknown device id: ");
//    Serial.println(deviceId);
  }     
}

  //Alexa "OFF" response
void turnOff(String deviceId) {
   if (deviceId == (first_deviceId)) {  // Device ID of first device  
//     Serial.print("Turn off 1st-Device ID: ");
//     Serial.println(deviceId);
        digitalWrite(GPIO_2, HIGH);
   } else if (deviceId == (second_deviceId)) { // Device ID of second device
//     Serial.print("Turn off 2nd-Device ID: ");
//     Serial.println(deviceId);
        digitalWrite(GPIO_0, HIGH);
  } else {
//     Serial.print("Turn off for unknown device id: ");
//     Serial.println(deviceId);
  }
}

  //Receive and process an Alexa command
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: {
        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") {
//            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);
//  Serial.println();

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

//  SPIFFS.format();  //clean FS, for testing

  //read configuration from FS json
//  Serial.println("mounting FS...");

  if (SPIFFS.begin()) {
//    Serial.println("mounted file system");
    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(udp_port, json["udp_port"]);
          strcpy(api_key, json["api_key"]);
          strcpy(first_deviceId, json["first_deviceId"]);
          strcpy(second_deviceId, json["second_deviceId"]);

          if(json["ip"]) {
            Serial.println("setting custom ip from config");
            //static_ip = json["ip"];
            strcpy(static_ip, json["ip"]);
            strcpy(static_gw, json["gateway"]);
            strcpy(static_sn, json["subnet"]);
//            Serial.println(static_ip);
          } else { 
//            Serial.println("no custom ip in config");
          }
        } else {
//          Serial.println("failed to load json config");
        }
      }
    }
  } else {
//    Serial.println("failed to mount FS");
  }
  //end read
//  Serial.println(static_ip);
//  Serial.println(udp_port);
//  Serial.println(api_key);
//  Serial.println(first_deviceId);
//  Serial.println(second_deviceId);

  // The extra parameters to be configured (can be either global or just in the setup)
  // After connecting, parameter.getValue() will get you the configured value
  // id/name placeholder/prompt default length
  WiFiManagerParameter custom_ap_SSID("ap_SSID", "Box name and SSID", ap_SSID, 25);
  WiFiManagerParameter custom_udp_port("udp_port", "UDP Port", udp_port, 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. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;

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

  //set static ip
  IPAddress _ip,_gw,_sn;
  _ip.fromString(static_ip);
  _gw.fromString(static_gw);
  _sn.fromString(static_sn);

  wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);

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

  //reset settings - for testing
  //wifiManager.resetSettings();

  //set minimu quality of signal so it ignores AP's under that quality
  //defaults to 8%
  wifiManager.setMinimumSignalQuality();

  //sets timeout until configuration portal gets turned off
  //useful to make it all retry or go to sleep
  //in seconds
  //wifiManager.setTimeout(120);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name
  //here  "AutoConnectAP"
  //and goes into a blocking loop awaiting configuration
  if (!wifiManager.autoConnect(ap_SSID)) {
//    Serial.println("failed to connect and hit timeout");
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(3000);
  }

  //if you get here you have connected to the WiFi
//  Serial.println("connected to WiFi...");

  //read updated parameters
  strcpy(ap_SSID, custom_ap_SSID.getValue());
  strcpy(udp_port, custom_udp_port.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) {
    Serial.println("saving config");
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    json["ap_SSID"] = ap_SSID;
    json["udp_port"] = udp_port;
    json["api_key"] = api_key;
    json["first_deviceId"] = first_deviceId;
    json["second_deviceId"] = second_deviceId;

    json["ip"] = WiFi.localIP().toString();
    json["gateway"] = WiFi.gatewayIP().toString();
    json["subnet"] = WiFi.subnetMask().toString();

    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
//      Serial.println("failed to open config file for writing");
    }

    json.prettyPrintTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
  }

//  Serial.println("local ip");
//  Serial.println(WiFi.localIP());
//  Serial.println(WiFi.gatewayIP());
//  Serial.println(WiFi.subnetMask());
    delay(500);

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

  // Alexa 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 server
  udpPort = atoi(udp_port);
  Udp.begin(udpPort);
//  Serial.println("UDP Target IP");
//  Serial.println(WiFi.localIP());
//  Serial.println("UDP port");
//  Serial.println(udpPort);

} //end void setup()

void loop() {

  //Get the state of all four pins
  RXState = digitalRead(RX); //Toggle button for device 1 (LOW is pressed)
  TXState = digitalRead(TX); //Toggle button for device 2 (LOW is pressed)
  GPIO_2State = digitalRead(GPIO_2); //State of device 1 (on/Off)
  GPIO_0State = digitalRead(GPIO_0); //State of device 2 (on/Off)

  //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);
   }

  String packetbuffer (pktBuf);

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

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

  if (RXState ==LOW && TXState == LOW) {  //If RX & TX are LOW then reset the ESP into AP mode
    WiFiManager wifiManager;
//    SPIFFS.format();
    delay(1000);
    wifiManager.resetSettings();
    delay(1000);
    {
      ESP.reset();  //reset and try again, or maybe put it to deep sleep
      delay(5000);
    }

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

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

  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");          
    }
  }   
}
BoriKing commented 6 years ago

Have you tried changing the flash size to a higher value?

mrlightsman commented 6 years ago

@BoriKing Do you mean the 1M(512SPIFFS) setting in Arduino ide? Does the ESP01 have more memory available? My next setting is 2M(1M SPIFFS).

When I compile it, ide says: Sketch uses 437781 bytes (87%) of program storage space. Max is 499696 bytes. Global variables use 39332 bytes (48%) of dynamic memory, leaving 42588 bytes for local variables. Max is 81920 bytes.

Is this what you mean? What is the highest setting I can use with an esp01? Thanks.

BoriKing commented 6 years ago

yes that's what i mean. as for the highest setting i'm not sure. this may help.

https://github.com/esp8266/Arduino/blob/master/libraries/esp8266/examples/CheckFlashConfig/CheckFlashConfig.ino

mrlightsman commented 6 years ago

Thanks. I’ll give the sketch a try after work tomorrow and report out.

For my learning... I assume dynamic memory is equated to ram in a pc. Is it possible the variables I am using really use up 52% of the memory?

I know I am getting close on the program storage. I’m not sure if there is anything I can do to make the code more lean. I’m clearly a novice, but have tried to keep my modifications as efficient as I know how.

Thanks again for always being willing to help.

mrlightsman commented 6 years ago

I ran the sketch @BoriKing shared. The ESP01 is a 1M chip. So, it seems 1M (512 SPIFFS) is my limit. Assuming size is the problem, and short of buying a different version of the ESP, is there anything I can do to reduce the size of my code and/or variables?

For example, are most DHCP servers "smart enough" that if I only have json handle the IP address, the DHCP will be able to fill in the gateway and subnet without me having to handle them in the sketch?

mrlightsman commented 6 years ago

After doing a bit more testing, I commented out the json handling for gateway (static_gw) and subnet (static_sn). Now everything works as expected. Except that the wifi manager now shows the gw and sn as 0.0.0.0 and even when I change the IP, it defaults back to the dhcp given IP. I suspect the latter is because of json["ip"] = WiFi.localIP().toString(); This doesn't really matter to me, as I will just do an address reservation in my router.

I just wonder why the gateway and subnet cause problems. Is it possible that wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn); is a bug and should be wifiManager.setSTAStaticIPConfig(_ip, _sn, _gw);?

I realize this is not a Sinric issue, but it is an interesting topic which I should probably migrate to the other git, but it doesn't seem to draw too many comments any more.