So you're a Noob? Post your questions here until you graduate! Don't be shy.

User avatar
By Narfel
#71767 Bashing my head against another wall here. The below code is my current relayswitch implementation by using the websockets example from this thread. It works rather well, except that the handlePhysicalButton() in the main loop is causing problems by continuously firing at the websocket server no matter if it's running or not. Or, just to make it complicated for giggles, sometimes it refuses to fire at all until i reload the page. That's what i want eventually, but i don't know why and when it happens.

I'm looking for
a) an quick assessment if the concept of how i coded this makes sense
b) a way to have the physical button routine only send a message when pressed and checking if the websocket connection is up. I was able to do within the javascript with if (websock.readyState === WebSocket.OPEN) .

Code: Select all#include <Arduino.h>
#include <Hash.h>

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFiMulti.h>
#include <WebSocketsServer.h>
#include <ESP8266mDNS.h>

const char *website =
R"0(
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width = device-width, initial-scale = 1.0, maximum-scale = 1.0, user-scalable=0">
  <title>Switcheroo</title>

  <body>

<script>
  var websock;

  function start() {
    websock = new WebSocket('ws://' + window.location.hostname + ':81/');
    websock.onopen = function(evt) {
      console.log('websock open');
    };
    websock.onclose = function(evt) {
      console.log('websock close');
    };
    websock.onerror = function(evt) {
      console.log(evt);
    };
    websock.onmessage = function(evt) {
      console.log(evt);
      var a = document.getElementById('cbStyle');
      var e = document.getElementById('ledstatus');
      if (evt.data === 'ledon') {
        e.style.color = 'red';
        a.checked = true;
      } else if (evt.data === 'ledoff') {
        e.style.color = 'black';
        a.checked = false;
      } else {
        console.log('unknown event');
      }
    };
  }

  function checkboxClick(e) {
    if (e.checked) {
        if (websock.readyState === WebSocket.OPEN) {
          websock.send('ledon');
      }
    } else {
      if (websock.readyState === WebSocket.OPEN) {
        websock.send('ledoff');
      }
    }
    e.checked = !e.checked;
  }

  function buttonClick(e) {
    if (websock.readyState === WebSocket.OPEN) {
      websock.send(e.id);
    }
  }

  window.addEventListener("load", start, false);
</script>
</head>

<body>
  <h1>Switcheroo111</h1>
  <div id="ledstatus"><b>LED</b></div>
  <button id="ledon" type="button" onclick="buttonClick(this);">On</button>
  <button id="ledoff" type="button" onclick="buttonClick(this);">Off</button>
  <div id="switchCB" class="switch">
    <input id="cbStyle" class="cbStyle cbStyle-round" type="checkbox" onclick="checkboxClick(this)">
    <label for="cbStyle"></label>
  </div>
</body>

</html>
)0";

void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, unsigned int length), handleRoot(), handleNotFound(), handlePhysicalButton();
static void writeLED(bool LEDon);

#define RELAY 5  //D1
#define BUTTON 4 //D2

MDNSResponder mdns;
ESP8266WiFiMulti WiFiMulti;
ESP8266WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(81);

const char* ssid = "xxxxxxxxxxx";
const char* password = "xxxxxxxxxxx";

// Commands sent through Web Socket
const char LEDON[] = "ledon";
const char LEDOFF[] = "ledoff";

bool LEDStatus = false;
bool currentState = false;
bool lastButtonState = false;
bool pressedState = false;

//----------------------------------------------------------------------
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, unsigned int length)
{
        Serial.printf("webSocketEvent(%d, %d, ...)\r\n", num, type);
        switch(type) {
        case WStype_DISCONNECTED:
                Serial.printf("[%u] Disconnected!\r\n", num);
                break;

        case WStype_CONNECTED:
        {
                IPAddress ip = webSocket.remoteIP(num);
                Serial.printf("[%u] Connected from %d.%d.%d.%d url: %s\r\n", num, ip[0], ip[1], ip[2], ip[3], payload);
                // Send the current LED status
                if (LEDStatus) { webSocket.sendTXT(num, LEDON, strlen(LEDON)); }
                else { webSocket.sendTXT(num, LEDOFF, strlen(LEDOFF)); }
        }
        break;

        case WStype_TEXT:
                Serial.printf("[%u] get Text: %s\r\n", num, payload);
                if (strcmp(LEDON, (const char *)payload) == 0) {
                        writeLED(true);
                } else if (strcmp(LEDOFF, (const char *)payload) == 0) {
                        writeLED(false);
                }
                else { Serial.println("Unknown command"); }
                // send data to all connected clients
                webSocket.broadcastTXT(payload, length);
                break;

        case WStype_BIN:
                Serial.printf("[%u] get binary length: %u\r\n", num, length);
                hexdump(payload, length);
                // echo data back to browser
                webSocket.sendBIN(num, payload, length);
                break;

        default:
                Serial.printf("Invalid WStype [%d]\r\n", type);
                break;
        }
}

void handleRoot() {
  server.send ( 200, "text/html", website );
}

void handleNotFound()
{
        String message = "File Not Found\n\n";
        message += "URI: ";
        message += server.uri();
        message += "\nMethod: ";
        message += (server.method() == HTTP_GET) ? "GET" : "POST";
        message += "\nArguments: ";
        message += server.args();
        message += "\n";
        for (uint8_t i=0; i<server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; }
        server.send(404, "text/plain", message);
}

void handlePhysicalButton() {
  delay(100); //just slow down a bit
  currentState = digitalRead(BUTTON);
  if (currentState != lastButtonState) {
    lastButtonState = currentState;
    if (currentState == pressedState) {
      LEDStatus = !LEDStatus;
    }
    digitalWrite(RELAY, LEDStatus);
  }
  delay(50); //poor mans debounce
  if (LEDStatus) { webSocket.sendTXT(0, LEDON, strlen(LEDON)); }
  else { webSocket.sendTXT(0, LEDOFF, strlen(LEDOFF)); }
}

static void writeLED(bool LEDon)
{
        LEDStatus = LEDon;
        if (LEDon) { digitalWrite(RELAY, 1); }
        else { digitalWrite(RELAY, 0); }
}
//----------------------------------------------------------------------
void setup() {
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(RELAY, OUTPUT);
  writeLED(false);
  char HostName[32];
  sprintf(HostName, "Narfel-%06X", ESP.getChipId()); //create hostname name+chip-id
  WiFi.mode(WIFI_STA);
  WiFi.hostname(HostName);
  Serial.begin(115200);
  //Serial.setDebugOutput(true);
  Serial.println();
  for(uint8_t t = 4; t > 0; t--) {
          Serial.printf("[SETUP] BOOT WAIT %d...\r\n", t);
          Serial.flush();
          delay(1000);
  }

  Serial.printf("%s, ChipID: ", HostName);
  Serial.println(ESP.getChipId());

  WiFiMulti.addAP(ssid, password);
  while(WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print(WiFi.SSID());
  Serial.print(" (");
  Serial.print(WiFi.RSSI());
  Serial.println(")");

  if (mdns.begin("esprelay", WiFi.localIP())) {
          Serial.println("MDNS responder started");
          mdns.addService("http", "tcp", 80);
          mdns.addService("ws", "tcp", 81);
  } else { Serial.println("MDNS.begin failed"); }

  Serial.print("Connect to http://esprelay.local or http://");
  Serial.println(WiFi.localIP());
  // webserver
  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);
  server.begin();
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);
}

void loop() {
  handlePhysicalButton();
  webSocket.loop();
  server.handleClient();
}
User avatar
By Narfel
#71774 Thanks, and here I was hoping for that code to be simple enough to not having to watch the traffic :/ The dev tools in chrome give me an idea, but well.

Just out of curiousity: Is it so esoteric to want a physical switch control the same thing? Nearly every tutorial or example is about reading data and displaying it in all forms. Or am i just oldfashioned and its totally normal that your off the shelf iot nuclear reactor of today can only be controlled by mobile? :? :lol:

About the volatile global. The button doesn't know anything about the socket because i don't want him to, he should be able to work even if the network is down. That's why i have him send the exact same message as the button on the website. And ... according to my theory ... that message in function buttonClick() gets tested for if (websock.readyState === WebSocket.OPEN). However that's up to chance if that happens or not.

Maybe i'm better off just having the button do whatever he pleases and put a on-demand status check in the website. But then i could have stopped at the simple GET request and be done with it for weeks. :shock: :|
User avatar
By rudy
#71777
Narfel wrote:Is it so esoteric to want a physical switch control the same thing?

I consider it to be a requirement in most cases.

The button doesn't know anything about the socket because i don't want him to, he should be able to work even if the network is down.

I think that makes a lot of sense. It is how I would do it. (so of course it makes sense to me)

Maybe i'm better off just having the button do whatever he pleases and put a on-demand status check in the website. But then i could have stopped at the simple GET request and be done with it for weeks. :shock: :|

But you wouldn't have learned anything. ;)

I tried your code and I had no problems with it so far. I had to do other things so I have not had time to work it much. I hope to later today.
User avatar
By Narfel
#71779 Yeah of course, if it weren't for the learning i would have stopped there anyway. I learned so many new concepts over the last days. I haven't felt like this since school :)

Some more about my thought process:

I added handlePhysicalButton() to the main loop, running outside of webSocket.loop() to be independent from it. That's naturally sending an endless stream of message events potentially leading to weird behaviour. As a preventive measure i added a (redundant) check for the readystate 1 in the javascript before sending a message. That at least got chrome to stop throwing an exception when the websocket connection was not yet established.

The key problem i do not understand is that after a while the websocket connection gets dropped without any message. Programmatically i never explicitly tell the connection to close. Yet there is websock.onclose that should log a "websocket close" to the console. It doesn't though.

The next problem is, when i then reload one of two things happen: either the start function starts normally, establishes connection and starts firing the handlePhysicalButton() messages. Or ... that's were i am totally without any clue ... it establishes the websocket, the website buttons work as planned, but no firing. As if handlePhysicalButton() never existed. If i reload again, 50:50 chance of either thing happening.

Now, i like the idea of the volatile global to stem the tide of messages, but wouldn't that make the button dependent on a websocket? Is there a way to have the button send a message without looping?