Harry Anderson
Published © GPL3+

Esp8266 Shop Door Assistant

A device that controls the customer population remotely, to reduce the spread of Covid-19.

IntermediateWork in progress3 hours499

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1
5V 2.5A Switching Power Supply
Digilent 5V 2.5A Switching Power Supply
×1
5 mm LED: Red
5 mm LED: Red
Choose how many LEDs you desire in the design.
×2
5 mm LED: Yellow
5 mm LED: Yellow
×2
5 mm LED: Green
5 mm LED: Green
×2
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×2
Power MOSFET N-Channel
Power MOSFET N-Channel
×3
Resistor 330 ohm
Resistor 330 ohm
1 for each LED.
×6

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

LED Circuits

This circuit shows how to implement 6 LEDs with switching MOSFETS, but in the prototype, I only use two.

Ultrasonic sensor pins

These require 5V power whereas the NodeMCU only uses 3V.

Code

JavaScript Code

JavaScript
Place this in the 'data' folder. The data folder should be next to the Arduino sketch
var currentCustomers = 0;
var maxCustomers = 0;

//When the script loads, set the variables in html and adjust the lights
window.onload = function(){
    document.getElementById("maxCustomers").innerHTML = maxCustomers;
    document.getElementById("currentCustomers").innerHTML = currentCustomers;
    
    adjustLights("Init");
};

//Setup websocket connection
var connection = new WebSocket('ws://' + location.hostname + ':81/', ['arduino']);//The name of our websocket is connection
connection.onopen = function () {
    connection.send('Connect ' + new Date());
    adjustActionLog("connectionMade");
};

//If a webSocket error occurs
connection.onerror = function(error){
    console.log('WebSocket error: ', error);
    adjustActionLog("connectionFailed")
};

//When a message arives from the webSocket connection
connection.onmessage = function(e){
    //We know data will be in JSON format
    var message = JSON.parse(e.data)
    console.log('Server: ', message);
    //Update the value
 document.getElementById("currentCustomers").innerHTML = message.currentCustomers;
    //If there has been an increase in customers, someone has entered.
    if (message.currentCustomers>currentCustomers){
        var source = "personEntered";  
    }
    //Otherwise a customer has exited
    else{
        var source = "personExited";
    }
    currentCustomers = message.currentCustomers;
    adjustActionLog(source);
    adjustLights(source);
};

//Informs us when webSocket conection disconnects
connection.onclose = function(){
    console.log('WebSocket connection closed');
};

//Formatting and sending data over webSocket connection
function sendVariables(max, current){
    var customers = max<<10|current;
    connection.send(customers);
}

//When the increase customer limit button has been pressed
function increaseMax(){
        //update value
        maxCustomers = maxCustomers + 1; document.getElementById("maxCustomers").innerHTML = maxCustomers;
    
    //send over new value to the ESP
    sendVariables(maxCustomers, currentCustomers);
    //update webPage log and lights
        adjustActionLog("increasedMax");
        adjustLights("increasedMax");
        return maxCustomers;
    }

//When the decrease customer limit button is pressed
function decreaseMax(){
    //Avoiding negative values
        if(maxCustomers > 0) {
            //decrease limit and update
           maxCustomers = maxCustomers - 1; document.getElementById("maxCustomers").innerHTML = maxCustomers;
            sendVariables(maxCustomers, currentCustomers);
            adjustLights("decreasedMax");
            adjustActionLog("decreasedMax");
        }
        
        return maxCustomers;
        
    }

//When the count reset button is pressed
function resetCurrent(){
    currentCustomers = 0;
 document.getElementById("currentCustomers").innerHTML = currentCustomers;
    sendVariables(maxCustomers,currentCustomers);
    adjustLights("resetCustomerCount");
    adjustActionLog("resetCustomerCount");
    return currentCustomers;
}

//This function adjusts the traffic lights on the webpage so they mimic the ESP control.
function adjustLights(source){
    
    if (source == 'personEntered'){ 
               document.getElementById("redLight").style.background = "black"; document.getElementById("amberLight").style.background = "black"; document.getElementById("greenLight").style.background = "lightgreen";                         
        setTimeout(function (){
            adjustLights();
        }, 3000);
            
    }
    else if(source == 'personExited'){
        document.getElementById("redLight").style.background = "red"; document.getElementById("amberLight").style.background = "#FFC200"; document.getElementById("greenLight").style.background = "lightgreen";                         
        setTimeout(function (){
            adjustLights();
        }, 3000);
    }
    
    else if (currentCustomers>=maxCustomers){
        document.getElementById("redLight").style.background = "red"; document.getElementById("amberLight").style.background = "black"; document.getElementById("greenLight").style.background = "black";
    }
    else{
        document.getElementById("redLight").style.background = "black"; document.getElementById("amberLight").style.background = "#FFC200"; document.getElementById("greenLight").style.background = "black";
    }
}

//An action log to remember all that has occurred
function adjustActionLog(source){
    var date = new Date();
    var textArea = document.getElementById("actionLog");
    textArea.innerHTML+= "\n"+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds()+": ";
    if (source=="increasedMax"){
        textArea.innerHTML+="Customer limit increased: "+maxCustomers;
    }
    else if (source=="decreasedMax"){
        textArea.innerHTML+="Customer limit decreased: "+maxCustomers;
    }
    else if (source=="personExited"){
        textArea.innerHTML+="Customer exited, current customers: "+currentCustomers;
    }
    else if (source=="personEntered"){
        textArea.innerHTML+="Customer entered, current customers: "+currentCustomers;
    }
    else if (source=="connectionMade"){
        textArea.innerHTML+="Connection successful, system ready.";
    }
    else if (source=="connectionFailed"){
        textArea.innerHTML+="Connection failed, system not ready.";
    }
    else if (source=="resetCustomerCount"){
        textArea.innerHTML+="Customer count reset: 0";
    }
    else{
        textArea.innerHTML+="Unknown situation occurred.";
    }
    textArea.scrollTop = textArea.scrollHeight;
    
}

CSS Code

CSS
Place this in the 'data' folder. The data folder should be next to the Arduino sketch.
h1 {
    text-align: center;
    font-family: Georgia, serif;
    padding:10px;
    margin:10px;
    border-bottom: 1px, black;
}

html {
    background-color: #37392E;
    color:azure; 
    margin: 0px;
    width:100%;    
    padding:0 px;
    border-top: 1px black solid;
    border-bottom: 1px black solid;
}

.dataNumbers {
    font-size: 8em;
    text-align: center;
    margin: 0px;
    
}

.dataContainer {
    
    top:0;
    left:0;
    width: 100%;
    background-color: #28AFB0; 
    display:flex;
    border-spacing: 0 0 ;
    border-top: 1px black solid;
    border-bottom: 1px black solid;
}




#rightSection {
    float: right;
    width: 45%;  
    padding: 0px;
}

#leftSection {
    float:left;
    width: 45%;
    margin;
}

#sectionDataLabel {
    margin: 0px;
    background-color: #19647E; 
    text-align: center;
    border-top: 1px black solid;
    border-bottom: 1px white solid;
    padding: 0px;
    
}

#dataIncreaseButton {
    background-color: coral;
    border:none;
    color: white;
    padding:5px 10px;
    font-size: 2em;
    margin-right:10px;
    border-radius: 12px;
    margin-bottom: 8px;
}

#dataDecreaseButton {
    background-color:darkseagreen;
    border:none;
    color: white;
    padding:5px 10px;
    font-size: 2em;
    margin-left:10px;
    border-radius: 12px;
    margin-bottom: 8px;
}

*{
    margin: 0;
}

#Foot {
    background-color: #EEE5E5; 
    color: black;
    padding: 4px;
    margin: 0px;
    border: 0px;
}

#resetButton {
    background-color: coral;
    border:none;
    color: white;
    padding:5px 10px;
    font-size: 2em;
    margin-right:10px;
    border-radius: 12px;
    margin-bottom: 8px;
}

.buttonSection {
text-align: center;
}

#centralSection {
    flaot: left;
    width: 10%;
    text-align: center;
    border: 2px black solid;
    background: #474747;
}

.trafficLight {
    border: solid black 2px;
    border-radius: 50%;
    width:96%;
    height:auto;
    text-align: center;
    line-height: 4.4;
    font-family: Georgia, serif, bold;
}

#redLight {
    background: red;
}

#amberLight {
    background: #FFC200;
}

#greenLight {
    background: lightgreen;
}

#actionLog {
    height:  auto;
    min-width:500px; 
    max-width:99.7%;
    min-height:40px;
    height:100%;
    width:99.5%;
    border: 0;
    overflow-y: scroll;
    padding-bottom: 0;
    border-bottom: 0;
    
}

NodeMCU Code

C/C++
Please read the SPIFFS explanation section as a guide to use the nodemcu file system.
#include <WebSocketsServer.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>
#include <FS.h>   // Include the SPIFFS library

#ifndef STASSID
#define STASSID ""
#define STAPSK  ""
#endif


//LED sign pins
int redLedPin = D3;
int amberLedPin = D8;
int greenLedPin = D2;

//Entry ultrasonic sensor Pins
int entryPinOut = D5;
int entryPinIn = D6;

//Exit ultrasonic sensor Pins
int exitPinOut = D7;
int exitPinIn = D1;

//WiFi Credentials
const char* ssid = STASSID;
const char* password = STAPSK;

int maxCustomers = 0;
float currentCustomers = 0;

ESP8266WiFiMulti wifiMulti;     // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti'
ESP8266WebServer server(80);    // Create a webserver object that listens for HTTP request on port 80
WebSocketsServer webSocket(81); // Creat websockets object listening to port 81.


String getContentType(String filename); // convert the file extension to the MIME type
bool handleFileRead(String path);       // send the right file to the client (if it exists)

void setup() {
  Serial.begin(115200);         // Start the Serial communication to send messages to the computer
  delay(10);
  Serial.println('\n');

  startWifi();    //starts WiFi access point.

  SPIFFS.begin();  // Start the SPI Flash Files System

  startWebSocket(); //starts the webSockets server

  startServer(); //starts the HTTP server with a file read handler

  initialisePins();//initialises pins used with hardware

}

void loop(void) {
  webSocket.loop(); //Updates websocket
  MDNS.update();    //Allows mDNS to be used (doesn't work with android phones)
  server.handleClient(); //Handles http requests
  currentCustomers = trafficLight(); //Updates traffic light LEDs and takes sensor readings
  
}

//Fs formatting for http get requests
String getContentType(String filename) { // convert the file extension to the MIME type
  if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  return "text/plain";
}


bool handleFileRead(String path) { // send the right file to the client (if it exists)
  Serial.println("handleFileRead: " + path);
  if (path.endsWith("/")) path += "dashboard.html";     // If page not fully requested, append the correct ending
  String contentType = getContentType(path);            // Get the MIME type
  if (SPIFFS.exists(path)) {                            // If the file exists
    File file = SPIFFS.open(path, "r");                 // Open it
    size_t sent = server.streamFile(file, contentType); // And send it to the client
    file.close();                                       // Then close the file again
    return true;
  }
  Serial.println("\tFile Not Found");
  return false;                                         // If the file doesn't exist, return false
}

void startWifi(){
  wifiMulti.addAP(ssid, password);   // add Wi-Fi networks you want to connect to

  Serial.println("Connecting ...");
  int i = 0;
  while (wifiMulti.run() != WL_CONNECTED) { // Wait for the Wi-Fi to connect
    delay(250);
    Serial.print('.');
  }

  Serial.println('\n');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());              // Tell us what network we're connected to
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());           // Send the IP address of the ESP8266 to the computer

  if (MDNS.begin("doorMate")) {              // Start the mDNS responder for esp8266.local
    Serial.println("mDNS responder started");
  } else {
    Serial.println("Error setting up MDNS responder!");
  }
}

void startServer(){
  server.onNotFound([]() {                              // If the client requests any URI
    if (!handleFileRead(server.uri()))                  // send it if it exists
      server.send(404, "text/plain", "404: Not Found"); // otherwise, respond with a 404 (Not Found) error
  });

  server.begin();                           // Actually start the server
  Serial.println("HTTP server started");
}

void startWebSocket(){
  webSocket.begin();
  webSocket.onEvent(webSocketEvent);//if a webSocket message arrives, go to the webSocketEvent function
  Serial.println("WebSocket server started.");
}

//When a webSocket event is received
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length){
switch(type){
  case WStype_DISCONNECTED://When a server disconnects
  Serial.printf("[%u] Disconnected.\n",num);
  break;
  case WStype_CONNECTED:{//When new server is established
    IPAddress ip = webSocket.remoteIP(num);
    Serial.printf("[%u] connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
  }
  break;
  case WStype_TEXT:   //if new text data is received
  Serial.printf("[%u] get Text: %s\n", num, payload);
  uint32_t customers = (int)strtol((const char*) &payload[0], NULL, 10);
  maxCustomers  = customers >> 10 & 0x3FF; //Formatting the payload
  currentCustomers = customers &0x3FF;//Formatting the payload
  Serial.println(maxCustomers);
  Serial.println(currentCustomers);
  break;
} 
}

void initialisePins(){
  //Setting the sign LED pins
  
  pinMode(redLedPin, OUTPUT);
  pinMode(amberLedPin, OUTPUT);
  pinMode(greenLedPin, OUTPUT);


  //Setting the entry ultrasonic sesnor pins
  pinMode(entryPinOut, OUTPUT);
  pinMode(entryPinIn, INPUT);


  //Setting the exit ultrasonic sesnor pins
  pinMode(exitPinOut, OUTPUT);
  pinMode(exitPinIn, INPUT);
  
  Serial.println("Pin Initialisation Complete");
}

int trafficLight(){
 
  //variables to be used
  long entryDuration, entryCm, exitDuration, exitCm;

  //If the us sensor reading is below this value, it reads as a definite scan of a person
  int sensorLimit = 15;

  //If current customer count is greater or equal to max allowance, show the red sign.
  if (currentCustomers >= maxCustomers) {
    digitalWrite(redLedPin, HIGH);
    digitalWrite(amberLedPin, LOW);
    digitalWrite(greenLedPin, LOW);
  }

  //Otherwise show the amber sign and detect for an entry
  else {
    digitalWrite(redLedPin, LOW);
    digitalWrite(amberLedPin, HIGH);
    digitalWrite(greenLedPin, LOW);
    

    //Taking a distance sensor reading
    digitalWrite(entryPinOut, LOW);
    delayMicroseconds(2);
    digitalWrite(entryPinOut, HIGH);

    delayMicroseconds(10);
    digitalWrite(entryPinOut, LOW);
    entryDuration = pulseIn(entryPinIn, HIGH);

    //Converting to cm
    entryCm = entryDuration / 29 / 2;
    Serial.println("Entry Sensor:");
    Serial.println(entryCm);
  
    //If the distance is below the sensorLimit and there is space, the green light will turn on
    if (entryCm <= sensorLimit & currentCustomers < maxCustomers){
      digitalWrite(amberLedPin, LOW);
      digitalWrite(greenLedPin, HIGH);


      currentCustomers++; //Noting the entry of a person

      //Using json formatting to send over data
      String json = "{\"currentCustomers\":";
      json += currentCustomers;
      json += "}";
      Serial.println(json.c_str());

      //Send updated customer count over webSocket
      webSocket.broadcastTXT(json.c_str(), json.length());
      Serial.println("Green, enter, person count:");
      Serial.println(currentCustomers);
      delay(3000);
    }
  }
    //If there is more than 1 person in the shop, detect for departure
  if (currentCustomers > 0) {
    
    //Taking a distance sensor reading for exiting sensors
    digitalWrite(exitPinOut, LOW);
    delayMicroseconds(2);
    digitalWrite(exitPinOut, HIGH);
    delayMicroseconds(10);
    digitalWrite(exitPinOut, LOW);
    exitDuration = pulseIn(exitPinIn, HIGH);
    
    //Converting to cm
    exitCm = exitDuration / 29 / 2;
    Serial.println("Exit Sensor:");
    Serial.println(exitCm);

    if (exitCm <= sensorLimit) {
      currentCustomers--;
  //Putting all lights on to indicate the departure has been read.
      digitalWrite(amberLedPin, HIGH);
      digitalWrite(greenLedPin, HIGH);
      digitalWrite(redLedPin, HIGH);

      //format as json
      String json = "{\"currentCustomers\":";
      json += currentCustomers;
      json += "}";
      Serial.println(json.c_str());

      //Send over updated value
      webSocket.broadcastTXT(json.c_str(), json.length());

      Serial.println("Person left the room, person Count:");
      Serial.println(currentCustomers);
      delay(3000);
    }

  }
  
  return currentCustomers;
}

HTML Web Page Code

HTML
Place this in the 'data' folder. The data folder should be next to the Arduino sketch.
<!DOCTYPE html>
<html>

<!--  Header  -->
<head>
  <link rel="stylesheet" href="dashboard.css">
    <script type="text/javascript" src="dashboard.js"></script>
</head>
    
<!--Title-->
<h1>
    Customer Data
</h1>

    
<!--Main Page     -->
<section class = "dataContainer">
    <section id = "leftSection">
        <h3 id = sectionDataLabel>
            Current Number of Customers:
        </h3>
        
        <body>
            <p class = "dataNumbers" id = "currentCustomers">
                0
            </p>
        </body>
        <section class = "buttonSection">
            <button id = "resetButton" onclick="currentCustomers = resetCurrent()">
        Reset Counter
        </button>
        </section>
        
    </section>
    
    <section id = "centralSection">
    <section class = "trafficLight" id = "redLight">
        Queue</section>
    <section class="trafficLight" id = amberLight>
        Scan</section>
    <section class="trafficLight" id = greenLight>
        Enter</section>
    </section>
    <section id = "rightSection">
    <h3 id = sectionDataLabel>
        Customer Limit:
    </h3>
        <body>
            <p class = "dataNumbers" id = "maxCustomers">
                0
            </p>

        </body>
       <section class = "buttonSection">
        <button id = "dataIncreaseButton" onclick="maxCustomers = increaseMax()">Increase</button>
 
        <button id = "dataDecreaseButton" onclick = "maxCustomers = decreaseMax()">Decrease</button>   
        </section> 
    </section>
</section>
    <section>
    <textarea id = "actionLog" readonly rows="4">Action Log: System reset, please wait until system is ready.</textarea>
    </section>
    
<!-- Footer   -->
<p id = "Foot">
    Version: <b>0.70</b><br>
    Last website edit date: <b>03 July 2020</b><br>
A website to accompany a <a href = "https://www.hackster.io/HarryAnderson> Hackster.io Project</a> to reduce the spread of Covid-19 while enabling small shops to open during the release of lockdown.
</p>  
    
   
    
</html>

Credits

Harry Anderson

Harry Anderson

1 project • 1 follower
Thanks to Pieter P..

Comments