Posted on

ESP8266 NodeMCU – Toggle Button & Slider – Remote Node.js Server Example

After looking all over for bits and pieces of how to properly get a Remote Node.js Server to communicate with a ESP8266 NodeMCU, I was finally able to get everything working properly. With so many libraries of the same variant of implementing a Socket.io client on a NodeMCU, it was very confusing when functions had the same or very similar names and arguments.

I wanted a way to remotely control a device with a button toggle switch (LED, Relay etc) and a range slider to control brightness of an LED, Triac etc with a web based interface on a NodeMCU.

Here is an example of how I was able to achieve it with fairly great results. Hopefully this helps others in trying to achieve the same results. For more buttons and sliders their values can be stored in 2 arrays one for switch state values and another for dimmer pwm values.

Below is the Server.js file running on the remote Node.js Web Server :


var http = require('http');

var express = require('express');

var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);

app.get('/', function (req, res) {

res.sendFile(__dirname + '/index.html');

});
app.use(express.static(__dirname + '/'));

var server = http.createServer(app);

var io = require('socket.io').listen(server);

server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});

// Define/initialize our global vars
var socketCount = 0

var brightness = 0; //static variable to hold the current brightness
var swstate = 0;

io.sockets.on('connection', function(socket){
//console.log(socket.id);
// Socket has connected, increase socket count
socketCount++
// Let all sockets know how many are connected
io.sockets.emit('users connected', socketCount)

socket.emit('swstate', swstate);

socket.on('swstate',function(){
swstate = 1 - swstate;
//console.log(swstate);
io.emit('swstate', swstate);
});

socket.emit('led', {value: brightness}); //send the new client the current brightness

socket.on('led', function (data) { //makes the socket react to 'led' packets by calling this function
brightness = data.value; //updates brightness from the data object
io.sockets.emit('led', {value: brightness}); //sends the updated brightness to all connected clients
});

});

process.on('SIGINT', function() {
//Do things on Script Termination...
console.log('\n Bye!')
process.exit();
});

Start the node.js server script on the remote server using the command in SSH:
forever start -o out.log -e err.log Server.js

Below is the index.html file running in the same directory as the Node.js Script (This page is served by the Node.js http Server) :

<html>
<head>
<title>Node.js Socket.io Testing</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-switch/3.3.2/css/bootstrap2/bootstrap-switch.min.css">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22http%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fjquery%2F1.11.3%2Fjquery.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Frangeslider.js%2F2.3.2%2Frangeslider.min.css%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<!--[if IE]>
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fjson3%2F3.3.2%2Fjson3.min.js%22%3E%0A%3Cscript%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fsocket.io%2F2.1.1%2Fsocket.io.dev.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<![endif]-->
<!--[if !IE]><!-->
<!--<![endif]-->
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22%2Fsocket.io%2Fsocket.io.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.5%2Fjs%2Fbootstrap.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fbootstrap-switch%2F3.3.2%2Fjs%2Fbootstrap-switch.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fgitcdn.github.io%2Fbootstrap-toggle%2F2.2.2%2Fjs%2Fbootstrap-toggle.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Frangeslider.js%2F2.3.2%2Frangeslider.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cstyle%3E%0Abody%20%7B%0Amargin-top%3A%2020px%3B%0Amargin-left%3A%2030px%3B%0A%7D%0A%0Ainput%5Btype%3Drange%5D%7B%0A-webkit-appearance%3A%20none%3B%0Awidth%3A%2080%25%3B%0A%7D%0A%0Ainput%5Btype%3Drange%5D%3A%3A-webkit-slider-runnable-track%20%7B%0Aheight%3A%2010px%3B%0Abackground%3A%20%230c75b3%3B%0Aborder%3A%20none%3B%0Aborder-radius%3A%203px%3B%0A%7D%0A%0Ainput%5Btype%3Drange%5D%3A%3A-webkit-slider-thumb%20%7B%0A-webkit-appearance%3A%20none%3B%0Aborder%3A%20none%3B%0Aheight%3A%2032px%3B%0Awidth%3A%2032px%3B%0Aborder-radius%3A%2050%25%3B%0Abackground%3A%20%2314c0dc%3B%0Amargin-top%3A%20-12px%3B%0A%7D%0A%0Ainput%5Btype%3Drange%5D%3Afocus%20%7B%0Aoutline%3A%20none%3B%0A%7D%0A%0Ainput%5Btype%3Drange%5D%3Afocus%3A%3A-webkit-slider-runnable-track%20%7B%0Abackground%3A%20%230c75b3%3B%0A%7D%0A%3C%2Fstyle%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<style>" title="<style>" />

</head>
<body>


<div class="row">


<div class="alert alert-info col-md-3 col-md-3" role="alert" id="usersConnected"></div>

</div>





<strong>Slider Value: </strong><span id="outputText">50</span>

<input type="range" id= "inputSlider" min="0" max="1023" value="0" step="1" oninput="showValue(this.value)" />


<strong>SW State: </strong><span id="swval"></span>

<input type="checkbox" data-toggle="toggle" data-on="Off" data-off="On" id="toggle">

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fjavascript%22%3E%0A%0A%2F%2F%20Connect%20to%20our%20node%2Fwebsockets%20server%0Avar%20socket%20%3D%20io.connect('http%3A%2F%2Fwww.domain.com%3A3000')%3B%0A%0Avar%20websocket%20%3D%200%3B%0A%0A%24(%22%23toggle%22).change(function()%7B%0Aif(!websocket)%20%7B%0Aif(%24(%22%23toggle%22).prop(%22checked%22)%20%3D%3D%201)%7B%0Avar%20swstate%20%3D%201%3B%0Asocket.emit('swstate'%2C%20swstate)%3B%0A%7Delse%7B%0Avar%20swstate%20%3D%200%3B%0Asocket.emit('swstate'%2C%20swstate)%3B%0A%7D%0A%7D%20else%20%7Bwebsocket%20%3D%200%3B%7D%0A%7D)%3B%0A%0Asocket.on('swstate'%2C%20function(swstate)%20%7B%0AswValue(swstate)%3B%0Awebsocket%20%3D%201%3B%0Aif(swstate%20%3D%3D%3D%201)%7B%0A%24('%23toggle').bootstrapToggle('on')%3B%7D%0Aelse%7B%0A%24('%23toggle').bootstrapToggle('off')%3B%7D%0A%0A%7D)%3B%0A%2F%2F%20New%20socket%20connected%2C%20display%20new%20count%20on%20page%0Asocket.on('users%20connected'%2C%20function(data)%7B%0A%24('%23usersConnected').html('%3Cstrong%3EUsers%20Connected%3A%3C%2Fstrong%3E%20'%20%2B%20data)%0A%7D)%0A%0Asocket.on('led'%2C%20function%20(data)%20%7B%0Adocument.getElementById(%22inputSlider%22).value%20%3D%20data.value%3B%0Adocument.getElementById(%22outputText%22).innerHTML%20%3D%20data.value%3B%0A%7D)%3B%0A%0Afunction%20showValue(newValue)%0A%7B%0Adocument.getElementById(%22outputText%22).innerHTML%3DnewValue%3B%0Asocket.emit('led'%2C%20%7B%20value%3A%20newValue%20%7D)%3B%0A%7D%0Afunction%20swValue(swstate)%0A%7B%0Adocument.getElementById(%22swval%22).innerHTML%3Dswstate%3B%0A%7D%0A%2F%2F%7D)%0A%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
</body>
</html>

Below is the Code for the ESP8266 NodeMCU programmed on the Arduino IDE (v1.8.5) environment:

#include <ESP8266WiFi.h>          //ESP8266 Core WiFi Library (you most likely already have this in your sketch)</pre>

//#include <DNSServer.h> //Local DNS Server used for redirecting all requests to the configuration portal
#include <ESP8266WebServer.h> //Local WebServer used to serve the configuration portal
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <ESP8266mDNS.h>
#include <SocketIoClient.h>
#include <ArduinoJson.h>

#define LED 13
// Pin D7 on NodeMCU
#define LEDPWM 12
// Pin D6 on NodeMCU

const char* mdnsName = "esp8266";

// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;

ESP8266WebServer webServer(80);

SocketIoClient socket;

void light(const char * payload, size_t length) {
String msg;
//String text = String((char *) &payload[0]);
msg = String((char*)payload);
if(msg=="1"){ digitalWrite(LED,HIGH); Serial.println("LED ON");}else{digitalWrite(LED,LOW); Serial.println("LED OFF");}
Serial.println(msg);
}

void lightpwm(const char * payload, size_t length) {

DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(4) + 100);

//String text = String((char *) &payload[0]);
//String msg2 = String((char*)payload);

JsonObject& root = jsonBuffer.parseObject(String((char*)payload));
int pwmval = root["value"];
//Serial.println("PWM LED");
analogWrite(LEDPWM, pwmval);
}

String responseHTML = ""
"<!DOCTYPE html><html><head><title>Hello World!</title></head><body>"
"
<h1>Hello World!</h1>

All requests are redirected here.

</body></html>";

//flag for saving data
bool shouldSaveConfig = false;

void configModeCallback (WiFiManager *myWiFiManager) {
Serial.println("Entered config mode");
Serial.println(WiFi.softAPIP());
Serial.println(myWiFiManager->getConfigPortalSSID());
}

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

void setup() {
// put your setup code here, to run once:
pinMode(LED, OUTPUT);
pinMode(LEDPWM, OUTPUT);
digitalWrite(LED, LOW);
Serial.begin(115200);
WiFiManager wifiManager;
//reset settings - for testing
//wifiManager.resetSettings();
wifiManager.setConfigPortalTimeout(360);
wifiManager.setDebugOutput(false);

wifiManager.setAPCallback(configModeCallback);
//WiFiManager.setHostname(mdnsName);
//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("Allen_LED", "1234pass")) {
Serial.println("failed to connect and hit timeout");
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(1000);
}
wifiManager.setSaveConfigCallback(saveConfigCallback);

//if you get here you have connected to the WiFi
Serial.println("Connected!... YES!");
Serial.println("Local IP is: ");
Serial.println(WiFi.localIP());
WiFi.hostname(mdnsName);
if (!MDNS.begin(mdnsName, WiFi.localIP())) {
// Start the mDNS responder for esp8266.local
Serial.println("Error setting up MDNS responder!");
}
Serial.println("mDNS responder started");
// Add service to MDNS-SD
MDNS.addService("http", "tcp", 80);
MDNS.addServiceTxt("http", "tcp", "url", "http://esp8266.local");

socket.begin("www.domain.com", 3000, "/socket.io/?transport=websocket");
//socket.on("event", event);
socket.on("swstate", light);
socket.on("led", lightpwm);

// HTTP Basic Authorization (optional)
//webSocket.setAuthorization("username", "password");

//socket.emit("plainString", "\"this is a plain string\"");
//socket.emit("swstate", "1");

// replay to all requests with same HTML
webServer.onNotFound([]() {
webServer.send(200, "text/html", responseHTML);
});
webServer.begin();
}

void loop() {
// put your main code here, to run repeatedly:
dnsServer.processNextRequest();
webServer.handleClient();
socket.loop();
}

The end result will look like the screenshot below where the Button Switch controls Pin D7 on the NodeMCU, and the Slider will create a PWM Dimmer on Pin D6, which you can connect an LED to see the brightness level correspond to the slider in just about real-time.

One side note: Make sure you replace domain.com in all the code above to reflect where your node.js server is running, localhost, ip, domain, hostname etc.