erkr
Published © LGPL

Doorbell v1.2 (a Working Captive Portal + OTA Use Case)

A Doorbell showcase demonstrating wireless configuration and upgrading of Arduino boards by using a Captive portal, Hotspot and Arduino OTA

IntermediateShowcase (no instructions)2,379
Doorbell v1.2 (a Working Captive Portal + OTA Use Case)

Things used in this project

Hardware components

Wemos D1 R2
This is the Arduino+WiFi board I have used
×1
Keyes Relay board
This is the relay board I used. It has the option to split the ground forthe input circuit and relay part. That enables wiring according the alternative schematics
×1
NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
Alternative for Wemos board
×1

Story

Read more

Schematics

Manual

Manual for setting up the whole project (arduino, wiring, push services, apps...)

Basic Wiring

Basic shematics for both a Doorbell Sender or a Receiver

Schematics2

Alternative shematics for a Doorbell Sender

The Doorbell Device

Code

Doorbell

C/C++
Arduino code developed and tested on a ESP8266 (WemosD1R2)
/*
   Doorbell 
     Copyright: the GNU General Public License version 3 (GPL-3.0) by Eric Kreuwels, 2017
     Credits: 
      - inspired by i.e the Captive Portal example of M. Ray Burnette 20150831)
        https://www.hackster.io/rayburne/esp8266-captive-portal-5798ff
      - added push notifications inspired by Clement Storck:
        http://makezine.com/projects/notifying-doorbell-with-pushingbox/
      
   Main Highlights:
     - Supports dyncamical switching between two modes of Wi-Fi operation (SoftAp - STA): The usecase to make a wireless arduino devioces configurable via a web page
     - When connected with the PC it can be controlled via the serial terminal; inc memory testing
     - Fancy Webpage for configuration; example is easy extendable for other configuration options
     - All configuration settings are persisted in EEProm
     - Working Doorbell example:
         Broadcast rang events to another arduino board (same software, but configured as receiver) or mobile phones (by the coresponding Doorbell android app)
         Option to trigger a push notofication to mobilte devices. There are plenty free servers and mobile apps on the various platforms (android, Windows, IOS) 
         available for that. I.e. pushingbox.com for the service, and pushbullet for the apps

   Some details:
     Program was extensively but solely tested on a Wemos D1 R2 board. 
     There is a beta version of the Android Doorbell app to notify when a Doorbell rang event is received. 
     Added push notifications as alternative for the android app. These will also work for other platforms (iOS, Windows Phones), but is not restricted to the local Wlan
     With push notofications you will get these doorbell notifications as well when you are not at home
     
     The two modes of operation
     - SoftAP - Configuration mode; as long the board in not configured, this is the default mode 
                           It then runs as a captive hotspot with a configuration web page and a DNS server. 
                           When the configuration is saved, it dynamically switches to the operational mode
                           Supports monitoring a physical 'reconfiguration' switch. When triggred device returns back to this mode
                           Configuration settings are persisted in the EEProm
     - STA - operational mode (default): ounce configured, this is the default mode of the board
                           It connects to the configured WLAN (SSID, PWD)
                           When configured as Sender it monitors if the doorbell switch is triggered and broadcast that event on the WLAN (see device types)
                           When configured as Receiver it monitors the WLAN for broadcasts (see device types) to switch a relay
     The doorbell application supports two device types;
     - Sender (default); monitors the doorbell button. If triggered both a UDP message is broadcasted on the WLAN and the local DoorRelay is activated
     - Receiver; monitors the WLAN for broadcast of the Sender and activates the DoorRelay 
     - Note: Typically there is just 1 Sender device and a number of optional receiver devices (as your mobile phones can be Receivers as well)


    Changelog:
     - v1.0 initial version
     - v1.1 Functional changes;
            - added support for push notifications; 
            - added small example of upgrading the configuration settings from v1.0 to v1.1
            - added clear command in serial termininal to return the configuration to the factory defaults (i.e. ready for shipment)
            Non-Functional Changes;
            - Refactored main ino for readability (more into smaller functions) 
            - Moved Serial monitor support and registration to separate files
     - v1.2 Bug fixes:
            - Push notification could delay the local broadcast; changed the order of handling broadcast vs pus notifications
            Functional changes;
            - OTA via Arduino IDE support
*/

#include <ESP8266WiFi.h>                  // istall board manager sw via http://arduino.esp8266.com/stable/package_esp8266com_index.json
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>

#include "./DNSServer.h"                  // Patched lib
#include "./configuration.h"
#include "./HTTPHandlers.h"
#include "./GPIOSupport.h"
#include "./SomeUtils.h"
#include "./Registration.h"
#include "./SerialMonitor.h"
#include "./OTASupport.h"

#define DEBUG
//#define DEBUG_ESP

const byte        DNS_PORT = 53;          // Capture DNS requests on port 53
IPAddress         apIP(10, 10, 10, 1);    // Private network for server
DNSServer         dnsServer;              // Create the DNS object
ESP8266WebServer  webServer(80);          // HTTP server
String            strAPName;
WiFiUDP           broadcastUdp;
WiFiClient        pushClient;
bool              doOTA;


// don't change these broadcast strings if you want to use the mobile apps:
const char        BROADCAST_DOOR[]  = "DoorBell: Ring";
const char        BROADCAST_ALIVE[] = "DoorBell: Alive";
const char        BROADCAST_REGISTER[] = "DoorBell: Reg"; // android blocks broadcasts, so there is the option to register a destination MAC:IP
const char        BROADCAST_REG_ACK[] = "DoorBell: R_ACK"; // Confirm registration
const char        BROADCAST_REG_NACK[] = "DoorBell: R_NACK"; // Failed, registration table full

CInterval         intervalAlive, intervalTransmit; // classes to check passed intervals in time


// PIN assignments. Carefull, On a Wemos D1-R2 using D3, D4 and D8 will interfere with WDT reset induced reboots 
Manipulator doorRelay(D0,      "D0: DoorRelay");
InputSwitch doorBellButton(D1, "D1: DoorBellButton");
InputSwitch reconfigButton(D2, "D2: ReconfigButton");
Manipulator signalLed(D5, "D5: SignalLED", false); // On in configuration mode, short flash every 5 seconds in operational mode


void setup() {
  doOTA=false;
  delay(1000);
  Serial.begin(115200);
  delay(2000);
#ifdef DEBUG_ESP
  Serial.setDebugOutput(true); // include lower level network diagnostics
#endif
// pre-caution needed to keep reboots working after a WDT reset 
// Configure D3,D4 and D8 as inputs . Don't use them (explained below)
  pinMode(D3, INPUT); 
  pinMode(D4, INPUT); 
  pinMode(D8, INPUT); 
/*  Explanation why these pins should not be used. When a WDT reset kicks in the reboot behavior depends on the inputs of
    pins D3, D4 and D8. These pins are connect with GPIO 0,2 and 15 of the ESP6288 chip and have on board pull up/down resistors  
    These resitors determine the voltage in these pins to be D3/GPIO0=High, D4/GPIO2=High, D8/GPIO15=low (when not connected to external sources). 
    The default reboot behavior of the chip will boot the sketch after a crash:
      GPIO0  GPIO2  GPIO15 Mode
       0V    3.3V    0V    Uart Bootloader <== selected while downloading a sketch
      3.3V   3.3V    0V    Boot sketch  <=== default for the Wemos board when pins not used / connected
       x      x     3.3V   SDIO mode (not used for Arduino)
 */
  printProgramVerion();
  ShowSerialCommands();
  WiFi.mode(WIFI_OFF);
#ifdef DEBUG_ESP
  WiFi.printDiag(Serial); 
#endif
  // Construct the Hotspot Access point name with MAC address :
  byte mac[6];
  WiFi.macAddress(mac);
  strAPName  = "DoorBell HotSpot ";
  for (int i=0; i<6; i++) {
    strAPName += String(mac[i], HEX);
  }
  Serial.print("Hotspot name: ");
  Serial.println(strAPName);

  LoadConfiguration();
  printCurrentMode();
  if (ConfigurationData.Configured) {
    printBoardType();
  }
  
  Serial.println("Board pin assignments:");
  Serial.println(doorRelay.Name());
  Serial.println(doorBellButton.Name());
  Serial.println(reconfigButton.Name());
  Serial.println(signalLed.Name());
  
  // Handlers to reply on HTML requests 
  webServer.onNotFound(HandleConfigure);
  webServer.on( "/hotspot-detect.html", HandleConfigure); // apple works akward with captive pages
  webServer.on( "/generate_204", HandleConfigure); // android newer versions
  webServer.on( "/configure", HandleConfigure); 
  webServer.on( "/settings", HTTP_POST, HandleSettings); 
  intervalAlive.Start(1000); // time till first alive signal
  if (ConfigurationData.Configured==false) {
    signalLed.On();
  } else {
    signalLed.Off();
  }
}

void checkReConfigButton() {
  // 1-3 second press toggles the flag for operational versus configuration mode, or reboot the device when in OTA mode
  // 10+ seconds press sets the flag for OTA mode
  static bool reconfigRequest=false, OTARequest=false;
  
  // Monitor the duration of button pressed
  if ( reconfigButton.timePressed() > 9000  ) { // at least 10 seconds pressed
    OTARequest=true;
    reconfigRequest=false; // overules reconfig request
  } else if (reconfigButton.timePressed() > 4000 ) { // pressed too long for reconfig 
    reconfigRequest=false; // clear reconfig request
  } else if (reconfigButton.timePressed() > 900 ) { // at least 1 seconds pressed
    reconfigRequest=true;
  }
  // Evaluate the result once the button is released
  if (reconfigButton.IsOn() == false) {
    if( reconfigRequest ) { 
       if (doOTA) {
         Serial.println("\nRe-Config pressed 1-3 seconds; Abort OTA Mode");
         delay(1000);
         ESP.restart();
       }
       if (ConfigurationData.Configured) {
         Serial.println("\nRe-Config pressed 1-3 second; switch to Configuration Mode");
         ConfigurationData.Configured = false;
       }
       else {
         Serial.println("\nRe-Config pressed 1-3 second; switch to Operational Mode");
         ConfigurationData.Configured = true; 
       }
       reconfigRequest = false;
    } else if( OTARequest) { // was pressed long and just released
       Serial.println("\nRe-Config pressed 10 seconds or more; switch to OTA Mode");
       doOTA = true; // flag used in loop();
       // clear current network connections
       if ( WiFi.getMode()== WIFI_AP ) {
          // still in a different mode -> needs to be switched -> disable previous AP, DNS and Web settings
          dnsServer.stop();
          webServer.stop();
          signalLed.Off();
          WiFi.softAPdisconnect(true);
          delay(1000); 
       }
       if ( WiFi.getMode()== WIFI_STA ) {  
          // still in a different mode -> needs to be switched -> disable previous STA settings
          broadcastUdp.stop();
          signalLed.Off();
          WiFi.disconnect(true);   
          delay(1000); 
       }
       intervalAlive.Start(100);
       OTARequest = false;
    }
  }
}

void loop() {
  UpdateHeapInfo();
  HandleSerialMonitorCommunication();
  
  checkReConfigButton();

  if (doOTA) {
    handleOTAMode();
  }
  else if (ConfigurationData.Configured) { 
    // operational mode for monitoring the doorbell button (Sender) or broadcasts (Receiver)
    handleOperationalMode();
  }
  else {  
    // Captive Hotspot mode with a configuration web page
    handleConfigurationMode();
  }
}

////////////////////////////////////////////////
// Three modes; their handlers:
////////////////////////////////////////////////
void handleOTAMode() {
    if ( WiFi.status() != WL_CONNECTED ) {
      // (re-)connect if needed
       if (ConnectToNetwork(ConfigurationData.wlanSSID, ConfigurationData.wlanPWD) == false) {
          Serial.println("Failed to switch to OTA Mode; reboot in 5 seconds");
          delay(5000);
          ESP.restart();
       }
       SetupOTAMode();
    }    
    
    // actions to be done in OTA mode:
    if ( intervalAlive.checkDone() ) { // fast alternating led
       intervalAlive.Start(100);
       if (signalLed.IsOn()) {
        signalLed.Off();
       } else {
        signalLed.On();
       }
    }
    OTAHandler();
    delay(20);
}


void handleConfigurationMode() {
    /////////////////////////////////////////////////////// 
    // Captive Hotspot mode with a configuration web page
    ///////////////////////////////////////////////////////
    if ( WiFi.getMode()== WIFI_STA ) {  
      // still in a different mode -> needs to be switched -> disable previous STA settings
      broadcastUdp.stop();
      signalLed.On();
      WiFi.disconnect(true);   
      delay(1000); 
      printCurrentMode();
    }
    if ( WiFi.getMode()== WIFI_OFF ) {
      // Not initialized yet -> enable AP, DNS Server and WebServer
      dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
      dnsServer.start(DNS_PORT, "*", apIP); // if DNSServer is started with "*" for domain name, it will reply with provided IP to all DNS request
      Serial.println("DNS server started");
      StartAccessPoint();
      webServer.begin();
      Serial.println("HTTP server started");
    }
    // actions to be done in configuration (AP+Web) mode:
    dnsServer.processNextRequest(); // needed for being a captive portal; all requests go to this board
    webServer.handleClient(); // the configuration web pages
}

void handleOperationalMode() {
    /////////////////////////////////////////////////////// 
    // operational mode for Conecting to the configured NetWork 
    /////////////////////////////////////////////////////// 
    if ( WiFi.getMode()== WIFI_AP ) {
      // still in a different mode -> needs to be switched -> disable previous AP, DNS and Web settings
      dnsServer.stop();
      webServer.stop();
      signalLed.Off();
      WiFi.softAPdisconnect(true);
      delay(1000); 
      printCurrentMode();
      printBoardType();
    }
    if ( WiFi.getMode()== WIFI_OFF ) {
      // Not initialized yet -> initialize UPD
       broadcastUdp.begin(ConfigurationData.BrdCstUDPPort);
    }    
    if ( WiFi.status() != WL_CONNECTED ) {
      // (re-)connect if needed
       ConnectToNetwork(ConfigurationData.wlanSSID, ConfigurationData.wlanPWD);
    }    
    // actions to be done in connected (STA) mode:


    // Till this point the code was generic for any type of configurable Arduino board.
    
    // Below Doorbell specific use case actions will be done. These actions depend on the configured board type:
    if (ConfigurationData.isServer) { 
      // Sender type: monitoring the doorbell button
      handleSenderActions();
    }
    else {  
      // Receiver type: monitor doorbell broadcasts: 
      handleReceiverActions();
    }  
}

void handleSenderActions() {
      // send broadcasts when bell switch is pressed
      if (doorBellButton.IsOn()) {
        doorBellButton.reset();
        Serial.println("Broadcast Doorbell UDP"); 
        doorRelay.On();
        for (int i=0; i<3; i++)  { // send a few times for potential packet loss
          intervalTransmit.Start(200); // send UDP's with a 200 msec interval to each client
          BroadcastUDP(BROADCAST_DOOR);
          for (int n=0; n<numRegisteredClients; n++) {
            // handle direct UDP messages to the registered clients
            PostUDP(BROADCAST_DOOR, getClientIP(n));
          }
          if (i==0) { // only once
            sendPushNotification(); // Send one round of UDP's first as this can take some time to connect
          }
          intervalTransmit.Wait(); // Wait till the 200 msecs passed
        }
        doorRelay.Off();
      }
      else {
        BroadCastReceived(); // have to consume all incoming UPD's to prevent ESP Wifi chocking with error LmacRxBlk:1
        if (intervalAlive.checkDone()) {
          signalLed.On();
          BroadcastUDP(BROADCAST_ALIVE); // show device is alive for whom whats to know
          for (int n=0; n<numRegisteredClients; n++) {
            // handle direct UDP messages to the registered clients
            PostUDP(BROADCAST_ALIVE, getClientIP(n));
          }
          signalLed.Off();
          intervalAlive.Start(5000); // time till next alive signal
        }
        delay(5);
      } 
}

void handleReceiverActions() {
      if (BroadCastReceived()) {
        // activate the relay and consume extra broadcasts for ca 1 second
        Serial.println("Received UDP broadcast");
        doorRelay.On();
        for (int i=0; i<3; i++) {
          delay(250);
          BroadCastReceived(); //just ignore these results (for reliability a few UDPs are tramsmited)
        }
        doorRelay.Off();
      } 
      if (intervalAlive.checkDone()) {
        signalLed.On();
        delay(20);
        signalLed.Off();
        intervalAlive.Start(5000);
      }  
}

////////////////////////////////////////////////
// Two Operational modes; their initializers:
////////////////////////////////////////////////

bool ConnectToNetwork(char *ssid, char *pass )  {
  int timeout=0;
  bool succes=false;
  WiFi.mode(WIFI_STA);
  Serial.print("Connecting SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED && timeout < 10)
  {
      delay(1000);
      timeout++;
  }
  if (WiFi.status() == WL_CONNECTED) {
     Serial.println();
     Serial.print("Connected, IP address: ");
     Serial.println(WiFi.localIP());
     succes=true;
  }
  else {
    Serial.println("Connecting to SSID Failed ");
  }
  // printWifiMode();
  return succes;
}

void StartAccessPoint()  {
  WiFi.mode(WIFI_AP);
  Serial.print("Setting soft-AP configuration ... ");
  Serial.println(WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)) ? "Ready" : "Failed!");

  Serial.print("Setting soft-AP ... ");
  Serial.println(WiFi.softAP(strAPName.c_str()) ? "Ready" : "Failed!");
  Serial.print("Soft-AP IP address = ");
  Serial.println(WiFi.softAPIP());
  // printWifiMode();
}

////////////////////////////////////////////////
// Functions to Send/Receive UDP's:
////////////////////////////////////////////////

void BroadcastUDP( const char *BroadcastPacket ) {
    IPAddress broadcastIP = WiFi.localIP() | (~WiFi.subnetMask());
#if 0
    // Not tried yet
    broadcastUdp.beginPacketMulticast( broadcastIP, (unsigned int)ConfigurationData.BrdCstUDPPort, WiFi.localIP());
    broadcastUdp.write(BroadcastPacket);
    broadcastUdp.endPacket();
#else
    // could be alternative for this working solution:
    PostUDP(BroadcastPacket, broadcastIP);
#endif
}

void PostUDP( const char *Packet, IPAddress ip) {
    broadcastUdp.beginPacket( ip, (unsigned int)ConfigurationData.BrdCstUDPPort);
    broadcastUdp.write(Packet);
    broadcastUdp.endPacket();
}


char packetBuffer[256]; //buffer to hold packets
bool BroadCastReceived() {
  // if there's data available, read a packet
  int packetSize = broadcastUdp.parsePacket();
  if (packetSize)
  {
    //Serial.print("Received UDP packet from [");
    //Serial.print(broadcastUdp.remoteIP());
    //Serial.print("] : ");
    // read the packet into packetBufffer
    int len = broadcastUdp.read(packetBuffer, 255);
    packetBuffer[len] = 0;
    //Serial.println(packetBuffer);
    String check(packetBuffer);

    if ( (check == BROADCAST_REGISTER) && ConfigurationData.isServer ) {
       char *pMAC = &(packetBuffer[sizeof(BROADCAST_REGISTER)]);
       if (processRegistrationRequest(pMAC, broadcastUdp.remoteIP()))
          PostUDP(BROADCAST_REG_ACK, broadcastUdp.remoteIP());
       else
          PostUDP(BROADCAST_REG_NACK, broadcastUdp.remoteIP());
    }
    if ( check == BROADCAST_DOOR )  {
       return true;
    }
    // ignore Alive's   
  }
  return false;
}

////////////////////////////////////////////////
// Optionally Trigger Push Notifications
////////////////////////////////////////////////


void sendPushNotification() {  //Function for sending the request to i.e. PushingBox
  pushClient.stop(); 
  if (ConfigurationData.pushMode == DISABLED) {
    return;
  }
  Serial.print("connecting push server...");
  if(pushClient.connect(ConfigurationData.pushSvr, 80)) { 
    Serial.println("send the request.");
    if (ConfigurationData.pushMode == GET) {
      pushClient.print("GET ");
    } else {
      pushClient.print("POST ");
    }
    pushClient.print(ConfigurationData.pushCmd);
    pushClient.println(" HTTP/1.1");
    pushClient.print("Host: ");
    pushClient.println(ConfigurationData.pushSvr);
    pushClient.println("User-Agent: Arduino");
    pushClient.println();
  } 
  else { 
    Serial.println("connection failed."); 
  } 
}

DNSServer.cpp

C/C++
#include "./DNSServer.h"
#include <lwip/def.h>
#include <Arduino.h>

#define DEBUG
#define DEBUG_OUTPUT Serial

DNSServer::DNSServer()
{
  _ttl = htonl(60);
  _errorReplyCode = DNSReplyCode::NonExistentDomain;
}

bool DNSServer::start(const uint16_t &port, const String &domainName,
                     const IPAddress &resolvedIP)
{
  _port = port;
  _domainName = domainName;
  _resolvedIP[0] = resolvedIP[0];
  _resolvedIP[1] = resolvedIP[1];
  _resolvedIP[2] = resolvedIP[2];
  _resolvedIP[3] = resolvedIP[3];
  downcaseAndRemoveWwwPrefix(_domainName);
  return _udp.begin(_port) == 1;
}

void DNSServer::setErrorReplyCode(const DNSReplyCode &replyCode)
{
  _errorReplyCode = replyCode;
}

void DNSServer::setTTL(const uint32_t &ttl)
{
  _ttl = htonl(ttl);
}

void DNSServer::stop()
{
  _udp.stop();
}

void DNSServer::downcaseAndRemoveWwwPrefix(String &domainName)
{
  domainName.toLowerCase();
  domainName.replace("www.", "");
}

void DNSServer::processNextRequest()
{
  _currentPacketSize = _udp.parsePacket();
  if (_currentPacketSize)
  {
    _buffer = (unsigned char*)malloc(_currentPacketSize * sizeof(char));
    _udp.read(_buffer, _currentPacketSize);
    _dnsHeader = (DNSHeader*) _buffer;

   #ifdef DEBUG
    DEBUG_OUTPUT.println("DNS Request");
   #endif
   
    if (_dnsHeader->QR == DNS_QR_QUERY &&
        _dnsHeader->OPCode == DNS_OPCODE_QUERY &&
        requestIncludesOnlyOneQuestion() &&
        (_domainName == "*" || getDomainNameWithoutWwwPrefix() == _domainName)
       )
    {
      replyWithIP();
    }
    else if (_dnsHeader->QR == DNS_QR_QUERY)
    {
      replyWithCustomCode();
    }

    free(_buffer);
  }
}

bool DNSServer::requestIncludesOnlyOneQuestion()
{
  return ntohs(_dnsHeader->QDCount) == 1 &&
         _dnsHeader->ANCount == 0 &&
         _dnsHeader->NSCount == 0 &&
         _dnsHeader->ARCount == 0;
}

String DNSServer::getDomainNameWithoutWwwPrefix()
{
  String parsedDomainName = "";
  unsigned char *start = _buffer + 12;
  if (*start == 0)
  {
    return parsedDomainName;
  }
  int pos = 0;
  while(true)
  {
    unsigned char labelLength = *(start + pos);
    for(int i = 0; i < labelLength; i++)
    {
      pos++;
      parsedDomainName += (char)*(start + pos);
    }
    pos++;
    if (*(start + pos) == 0)
    {
      downcaseAndRemoveWwwPrefix(parsedDomainName);
      return parsedDomainName;
    }
    else
    {
      parsedDomainName += ".";
    }
  }
}

void DNSServer::replyWithIP()
{
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->ANCount = _dnsHeader->QDCount;
  _dnsHeader->QDCount = _dnsHeader->QDCount; 
  //_dnsHeader->RA = 1;  

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, _currentPacketSize);

  _udp.write((uint8_t)192); //  answer name is a pointer
  _udp.write((uint8_t)12);  // pointer to offset at 0x00c

  _udp.write((uint8_t)0);   // 0x0001  answer is type A query (host address)
  _udp.write((uint8_t)1);

  _udp.write((uint8_t)0);   //0x0001 answer is class IN (internet address)
  _udp.write((uint8_t)1);
 
  _udp.write((unsigned char*)&_ttl, 4);

  // Length of RData is 4 bytes (because, in this case, RData is IPv4)
  _udp.write((uint8_t)0);
  _udp.write((uint8_t)4);
  _udp.write(_resolvedIP, sizeof(_resolvedIP));
  _udp.endPacket();



  #ifdef DEBUG
    DEBUG_OUTPUT.print("DNS responds: ");
    DEBUG_OUTPUT.print(_resolvedIP[0]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[1]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[2]);
    DEBUG_OUTPUT.print(".");
    DEBUG_OUTPUT.print(_resolvedIP[3]);
    DEBUG_OUTPUT.print(" for ");
    DEBUG_OUTPUT.println(getDomainNameWithoutWwwPrefix());
  #endif
}

void DNSServer::replyWithCustomCode()
{
  _dnsHeader->QR = DNS_QR_RESPONSE;
  _dnsHeader->RCode = (unsigned char)_errorReplyCode;
  _dnsHeader->QDCount = 0;

  _udp.beginPacket(_udp.remoteIP(), _udp.remotePort());
  _udp.write(_buffer, sizeof(DNSHeader));
  _udp.endPacket();
  #ifdef DEBUG
    DEBUG_OUTPUT.println("DNSReplyCode::NonExistentDomain");
  #endif

}

DNSServer.h

C/C++
#ifndef DNSServer_h
#define DNSServer_h
#include <WiFiUdp.h>

#define DNS_QR_QUERY 0
#define DNS_QR_RESPONSE 1
#define DNS_OPCODE_QUERY 0

enum class DNSReplyCode
{
  NoError = 0,
  FormError = 1,
  ServerFailure = 2,
  NonExistentDomain = 3,
  NotImplemented = 4,
  Refused = 5,
  YXDomain = 6,
  YXRRSet = 7,
  NXRRSet = 8
};

struct DNSHeader
{
  uint16_t ID;               // identification number
  unsigned char RD : 1;      // recursion desired
  unsigned char TC : 1;      // truncated message
  unsigned char AA : 1;      // authoritive answer
  unsigned char OPCode : 4;  // message_type
  unsigned char QR : 1;      // query/response flag
  unsigned char RCode : 4;   // response code
  unsigned char Z : 3;       // its z! reserved
  unsigned char RA : 1;      // recursion available
  uint16_t QDCount;          // number of question entries
  uint16_t ANCount;          // number of answer entries
  uint16_t NSCount;          // number of authority entries
  uint16_t ARCount;          // number of resource entries
};

class DNSServer
{
  public:
    DNSServer();
    void processNextRequest();
    void setErrorReplyCode(const DNSReplyCode &replyCode);
    void setTTL(const uint32_t &ttl);

    // Returns true if successful, false if there are no sockets available
    bool start(const uint16_t &port,
              const String &domainName,
              const IPAddress &resolvedIP);
    // stops the DNS server
    void stop();

  private:
    WiFiUDP _udp;
    uint16_t _port;
    String _domainName;
    unsigned char _resolvedIP[4];
    int _currentPacketSize;
    unsigned char* _buffer;
    DNSHeader* _dnsHeader;
    uint32_t _ttl;
    DNSReplyCode _errorReplyCode;

    void downcaseAndRemoveWwwPrefix(String &domainName);
    String getDomainNameWithoutWwwPrefix();
    bool requestIncludesOnlyOneQuestion();
    void replyWithIP();
    void replyWithCustomCode();
};
#endif

Configuration.cpp

C/C++
Support for making configuration data persistant in eeprom of an ESP8266
#include "./configuration.h"
#include <Arduino.h>
#include <EEPROM.h>

// default configuration settings

STConfigurationData ConfigurationData = { DOORBELL_DATA_VERSION,// Increase number when STConfigurationData fields changed
                                          false, // Configured
                                          "",    // wlanSSID
                                          "",    // wlanPWD
                                          49152, // BrdCstUDPPort
                                          true,  // isServer
                                          DISABLED, // pushMode; disabled, get or post
                                          "api.pushingbox.com", // pushSvr; name of the push server, i.e the free pushingbox 
                                          "/pushingbox?devid=<yourDeviceID>" // push command
                                        };


////////////////////////////////////////////////
// Persistance support configuration in EEProm
////////////////////////////////////////////////
bool LoadConfiguration() 
{
   char StoredVersion[5];
   EEPROM.begin(512);
   for (int i=0; i<4; i++)
       StoredVersion[i] = EEPROM.read(i);
   StoredVersion[4] = '\0';
   Serial.print(F("Check data version stored in EEPROM: "));  
   
  if (strcmp(ConfigurationData.Version, StoredVersion) == 0) // right version
  {
     Serial.println(StoredVersion);
     // presume to find compatible configuration data in the eeprom
     Serial.println(F("Load compatible configuration from EEPROM"));
     byte *pByte = (byte *)&ConfigurationData;
     for (int i=0; i<sizeof(ConfigurationData); i++)
       *(pByte+i) = EEPROM.read(i);
  }
  else if (strcmp("v1.0", StoredVersion) == 0) {
       // simple example of migrating an old version to the new version of configuration settings
       Serial.println("Old v1.0 configuration found (migration of old settings)"); 
       struct STConfigurationData {
          char Version[5];
          bool Configured;
          char wlanSSID[SSID_MAXLEN+1];
          char wlanPWD[PWD_MAXLEN+1];
          long BrdCstUDPPort;
          bool isServer;
       } oldData;
       byte *pByte = (byte *)&oldData;
       for (int i=0; i<sizeof(oldData); i++)
          *(pByte+i) = EEPROM.read(i);
       // migrate the compatible settings
       strncpy(ConfigurationData.wlanSSID, oldData.wlanSSID, SSID_MAXLEN);
       strncpy(ConfigurationData.wlanPWD, oldData.wlanPWD, PWD_MAXLEN);
       ConfigurationData.BrdCstUDPPort = oldData.BrdCstUDPPort;
       ConfigurationData.isServer = oldData.isServer;
       ConfigurationData.Configured = false; // new settings probably still need attention
  }
  else {
       Serial.print("No configuration found (normal until board is configured)"); 
       ConfigurationData.Configured = false;
  }
  EEPROM.end();
  return  ConfigurationData.Configured; 
}

bool SaveConfiguration() // return true if there was something to save
{
     EEPROM.begin(512);
     boolean changed=false;
     byte *pByte = (byte *)&ConfigurationData;
     for (int i=0; i<sizeof(ConfigurationData); i++) {
        if (*(pByte+i) != EEPROM.read(i)) { // only write differences (lifetime of the EE cells)
           EEPROM.write(i,*(pByte+i));  
           changed=true;
        }
     }
     if (changed) {
       Serial.println(F("Saved configuration to EEPROM"));
       EEPROM.commit();
     } 
     EEPROM.end();
     return changed;
}

void clearConfiguration() {
     // just erasing version is sufficient
     EEPROM.begin(512);
     for (int i=0; i<5; i++) {
        EEPROM.write(i,'\0');  
     }
     Serial.println(F("Configuration in EEPROM erased"));
     EEPROM.commit();
     EEPROM.end();
}

Configuration.h

C/C++
// default configuration settings
#define DOORBELL_PROGRAM_VERSION "v1.2"  
#define DOORBELL_DATA_VERSION "v1.1" // this is the data version, not the program version!!!!
#define SSID_MAXLEN 32
#define PWD_MAXLEN 64
#define SVR_MAXLEN 127
#define CMD_MAXLEN 127

enum PUSH_MODE { DISABLED=0, GET=1, POST=2 };

typedef struct STConfigurationData
{
  char Version[5];
  bool Configured;
  char wlanSSID[SSID_MAXLEN+1];
  char wlanPWD[PWD_MAXLEN+1];
  long BrdCstUDPPort;
  bool isServer;
  PUSH_MODE pushMode;
  char pushSvr[SVR_MAXLEN+1]; 
  char pushCmd[CMD_MAXLEN+1];
} STConfigurationData_t;

extern STConfigurationData ConfigurationData;

bool LoadConfiguration(); // return true if there was something to read 
bool SaveConfiguration(); // return true if there was something to save
void clearConfiguration(); // erase configuration. Board will start in configuration mode next boot with factory defaults (ready for shipment)

GPIOSupport.h

C/C++
Classes for handling IO (switches and manipulators)
class InputSwitch
{
  //vars
  private:
  bool _IsOn, _bDebug;
  int _Pin;
  String _Name;
  int _Counter; // used to prevent intermitted switching (dender)
  unsigned long onMillis, timePrinted;

  //constructor
  public:
  InputSwitch(int pin, String name, bool doDebug=true)   {
    _IsOn = false;
    _Counter = 0;
    _Pin = pin;
    _Name= name;
    _bDebug=doDebug;
    pinMode(_Pin, INPUT_PULLUP); 
  }

  //methods
  String Name() {
    return _Name;
  }
  void DebugPrint()   {
    Serial.print(_Name); Serial.print(" on pin(");  Serial.print(_Pin); Serial.println( _IsOn ? ") = On" : ") = Off");
  }
  void reset() {
    _Counter=0;
    _IsOn = false;
  }
  
  bool IsOn()   {
    if (digitalRead(_Pin) == HIGH  && _IsOn == true) // open contact while on
    {
      if( _Counter++ > 2) // only act after 2 times the same read out
      {
         _IsOn = false;
         if (_bDebug) {
            DebugPrint();
         }
         _Counter = 0;
      }
    }
    else if (digitalRead(_Pin) == LOW  && _IsOn == false) // closed contact while off
    {
      if( _Counter++ > 5) // only act after  5 times the same read out
      {
         _IsOn = true;
         if (_bDebug) {
            DebugPrint();
         }
         onMillis = millis();
         timePrinted = 0;
         _Counter = 0;
      }
    }
    else   {
       _Counter = 0;
    }
    if (_IsOn && _bDebug ) { // print duration of pressed
       unsigned long durationOn = millis() - onMillis;
       durationOn /= 1000;
       if (durationOn >= timePrinted) {
          timePrinted++;
          Serial.printf("%u(s)", durationOn);
       }
    }
    return _IsOn;
  }
  // check long button press
  unsigned long timePressed() {
    if (IsOn() ) {
      unsigned long durationOn = millis() - onMillis;
      return durationOn;
    }
    return 0;
  }
};

class Manipulator 
{
  //vars
  private:
  bool _IsOn, _bDebug;
  int _Pin;
  String _Name;

  //constructor
  public:
  Manipulator(int pin, String name, bool doDebug=true )   {
    _IsOn = false;
    _Pin = pin;
    _Name = name;
    _bDebug=doDebug;
    pinMode(_Pin, OUTPUT);   
    digitalWrite(_Pin, LOW);
  }
  //methods
  String Name() {
    return _Name;
  }
  void DebugPrint()   {
    Serial.print(_Name);
    Serial.print(" on pin(");
    Serial.print(_Pin);
    if (_IsOn)
      Serial.println(") = On");
    else
      Serial.println(") = Off");
  }
  void On()    {
    if (_IsOn == false)
    {
      _IsOn = true;
      digitalWrite(_Pin, HIGH);
      if (_bDebug)
        DebugPrint();
    }
  }

  void Off()   {
    if (_IsOn == true)
    {
      _IsOn = false;
      digitalWrite(_Pin, LOW);
      if (_bDebug)
        DebugPrint();
    }
  }

  bool IsOn()   {
    return _IsOn;
  }
};

HTTPHandlers.cpp

C/C++
Support functions to create Web pages, used for a configuration Web Page
#include <ESP8266WebServer.h>
#include "./HTTPHandlers.h"
#include "./configuration.h"

////////////////////////////////////////////////
// Global dependencies
////////////////////////////////////////////////
extern ESP8266WebServer  webServer;

////////////////////////////////////////////////
// Local HTTP response support methods
////////////////////////////////////////////////

void ShowStyle(String &message, int width ) {
   message +=     " <style>\n"
                  "body{font-family:verdana;margin:20px;font-size:12px;}\n"
                  "h1{margin:0px;font-size:18px;}\n"
                  "h2{font-size:12px;margin-top:15px;color:grey;}\n"
  //                "select{ width:100%;}\n"
  //                "input[type=\"number\"]{width:50px;}\n"
  //                "input[type=\"text\"]{width:275px;}\n"
                  "a{text-decoration:none;font-size:18px;}\n"
                  "form{ background: #e8e8e8; width: ";
   message +=     width; 
   message +=     "px; padding: 20px; \n"
                  "-webkit-border-radius: 10px; -moz-border-radius: 10px; }\n"
                  "label{ display: inline-block; width: 180px; text-align: left; } </style>\n"
                  "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n";
}

// helper method for next set of functions
void _showLabel(String &message, const char *tag, const char *label) {
   if (strlen(label) ) {
     message += "<label for=\""; 
     message += tag;
     message += "\">";
     message += label;
     message += "</label>\n";
   }
}

void ShowTextOption(String &message, const char *tag, const char *label, const char *value, const int length, const int size) {
  _showLabel(message, tag, label);
   message += "<input name=\""; 
   message += tag; 
   message += "\" id=\""; 
   message += tag; 
   message += "\" type=\"text\" maxlength=\""; 
   message += length; 
   message += "\" size=\""; 
   message += size; 
   message += "\" value=\""; 
   message += value; 
   message += "\" />\n";
}
void ShowNumberOption(String &message, const char *tag, const char *label, int value, int minV, int maxV, int Step) {
  _showLabel(message, tag, label);
   message += "<input name=\"";
   message += tag; 
   message += "\" id=\""; 
   message += tag;
   message += "\" type=\"number\" min=\"";
   message += minV; 
   message += "\" max=\""; 
   message += maxV; 
   message += "\" value=\""; 
   message += value; 
   message += "\" step=\""; 
   message += Step;
   message += "\" />\n";
}

void ShowOptionList(String &message, const char *tag, const char *label, const int idx, const char **pStr, const int number) {
  _showLabel(message, tag, label);
   message += " <SELECT NAME=\"";
   message += tag; 
   message += "\" id=\""; 
   message += tag;
   message += "\">\n";
   for (int i=0; i<number; i++)
   {
      message +=  "  <OPTION VALUE=\"";
      message += i;  
      if (i==idx) 
        message += "\" selected>";
      else
        message += "\">";
      String buf(*(pStr+i));
      message += buf;
     message += "</OPTION>\n"; 
   }
   message += " </SELECT>\n";
}


////////////////////////////////////////////////
// HTTP response handlers
////////////////////////////////////////////////

void HandleConfigure () {
    Serial.println("HandleConfigure");
    String message = "<html>"
                     "\n   <head> ";
                     "\n     <title>DoorBell "DOORBELL_PROGRAM_VERSION" Configure</title> ";
    ShowStyle(message, 300);                 
    message +=       "\n   </head>"
                     "\n   <body>"
                     "\n   <form class=\"\" method=\"POST\" action=\"http://";
    if (WiFi.getMode() == WIFI_AP) {
      message += WiFi.softAPIP().toString();                  
    } 
    else {
      message += WiFi.localIP().toString(); 
    }
    message +=       "/settings\">"
                     "\n   <h1>Doorbell "DOORBELL_PROGRAM_VERSION" configuration page</h1>"
                     "\n     <p>Enter the credentials for connecting the WLAN to be used for broadcasting Door Bell events:<BR><BR>";
    ShowTextOption(message, "SSID", "WLAN SSID", ConfigurationData.wlanSSID, SSID_MAXLEN, 32);
    message += "\n  <BR>" ;   
    ShowTextOption(message, "PWD",  "WLAN Password", ConfigurationData.wlanPWD, PWD_MAXLEN, 32);
    message += "\n  <BR>Enter the listen port used by the client apps (default=49152, advised range 49152..65535):<BR> ";   
    ShowNumberOption(message, "PORT", "UPD Listen port", ConfigurationData.BrdCstUDPPort, 1024, 65535, 1); // allowed range starts at 1024..65535
    message += "\n  <BR><BR>Define the device type: <br>A <b>Sender</b> monitors a bell button to broadcast such event<br><b>Receivers</b> monitor these broadcasts to trigger a relay<BR>";   
    const char *pStr[2] = { "Sender", "Receiver" }; // preserve index order!!!
    ShowOptionList(message, "TYPE", "Device type", ConfigurationData.isServer ? 0:1, pStr, 2);
    message += "\n  <BR><BR>Optionally configure a Push Server: <BR>The example values are for the free service PushingBox (use GET to enable). To enable a push servicer need to configure the HTTP command type into GET or POST. <BR>";   
    const char *pStr2[3] = { "Disabled", "GET", "POST" }; // preserve index order!!!
    ShowOptionList(message, "PMODE", "Push mode", ConfigurationData.pushMode, pStr2, 3);
    ShowTextOption(message, "PSVR", "Server name", ConfigurationData.pushSvr, SVR_MAXLEN, 32);
    ShowTextOption(message, "PCMD", "Push Command", ConfigurationData.pushCmd, CMD_MAXLEN, 32);
    
    message +=       "\n    <BR><BR></p> &nbsp;<input type=\"submit\" name=\"Sbutton\" value=\"Save\" /> &nbsp;&nbsp; <input type=\"reset\" value=\"Cancel\" />\n"
                     "\n   </form>"
                     "\n   </body>"
                     "\n</html>";
                    
    webServer.send(200, "text/html", message);
}

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

void HandleSettings () {
   String message="Handle Settings, Received Arguments:\n";
   bool changes = false;
   for (uint8_t i=0; i<webServer.args(); i++){
      String argument = webServer.arg(i);
      message += " " + webServer.argName(i) + ": " + argument + "\n";
      if (webServer.argName(i) == "SSID") {
         argument.toCharArray( ConfigurationData.wlanSSID, SSID_MAXLEN );
         changes = true;
      }
      else if (webServer.argName(i) == "PWD") {
         argument.toCharArray( ConfigurationData.wlanPWD, PWD_MAXLEN);
         changes = true;
      }
      else if (webServer.argName(i) == "PORT") {
         ConfigurationData.BrdCstUDPPort = atol( argument.c_str()); 
         changes = true;
      }     
      else if (webServer.argName(i) == "TYPE") {
         ConfigurationData.isServer = (argument == "0"); // option index based;  0=sender(server), 1=receiver(client)
         changes = true;
      }     
      else if (webServer.argName(i) == "PMODE") {
         ConfigurationData.pushMode = (PUSH_MODE)atol( argument.c_str()); 
         changes = true;
      }     
      else if (webServer.argName(i) == "PSVR") {
         argument.toCharArray( ConfigurationData.pushSvr, SVR_MAXLEN);
         changes = true;
      }
      else if (webServer.argName(i) == "PCMD") {
         argument.toCharArray( ConfigurationData.pushCmd, CMD_MAXLEN);
         changes = true;
      }
      else {
        message += "   Warning; parameter not recognized...\n";
      }
  }
 Serial.println(message);
 webServer.send(200, "text/html", 
                  "<html>\n"
                  " <head>\n"
                  "  <meta http-equiv=\"refresh\" content=\"3\">\n"
                  "  <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n"
                  " <title>Configuration Done</title>"
                  " </head>\n"
                  " <body>\n"
                  "   <h1>Processing changes, please wait...</h1>\n"            
                  " </body>\n"
                  "</html>\n");
  delay(1000); // give web page transmission some time
  // persist changes
  if (changes ) {
    ConfigurationData.Configured = true;
    SaveConfiguration();
  }
}

HTTPHandlers.h

C/C++
////////////////////////////////////////////////
// HTTP response handlers
////////////////////////////////////////////////

void HandleConfigure(); 
void handleNotFound();
void HandleSettings();

SomeUtils.h

C/C++
Left overs
////////////////////////////////////////////////
// Some Utils: check if an interval is passed
//  Create an instance
//  Start in interval with Start()
//  Check if inteval is expired with checkDone()
//  Max interval is ca 49 days
////////////////////////////////////////////////

#include <limits.h>

class CInterval
{
  public:
    CInterval() {
      _millisToWait = 1000;
    };
    void Start(unsigned long millisToWait) {
      // (re)start a new interval defined in miliseconds
       _millisToWait = millisToWait;
       _start = millis(); 
    };  
    bool checkDone() {      
      // return true when interval is passed
       return (millis() - _start >= _millisToWait );
    }
    void Wait() {
      // wait till interval is passed
      long timePassed = millis() - _start;
      if (timePassed < _millisToWait)
         delay(_millisToWait - timePassed);
    }
  private:
    unsigned long _millisToWait; 
    unsigned long _start = millis(); 
};

Registration.h

C/C++
#ifndef Registration_h
#define Registration_h
// For Android clientx there is a registration mechanism to send directed UPD's (instead of broadcasts)

#define MAX_NR_REGISTRATIONS  20
extern int numRegisteredClients;

void printRegistrations();
IPAddress getClientIP(int  idx); 
bool processRegistrationRequest(char *pMAC, IPAddress IPClient);

class MACAddress {
  char _mac[6];
  
  public:
  MACAddress() {
  };
  MACAddress(const char *mac) {
    for (int i=0; i<6; i++) {
      _mac[i] = mac[i];
    }
  };

  void setAddress( const char *mac) {
    for (int i=0; i<6; i++) {
      _mac[i] = mac[i];
    }
  };
  
  bool operator==(const MACAddress& addr) const { 
    bool isEqual=true;
    for (int i=0; i<6; i++) {
      isEqual = (isEqual && _mac[i]==addr._mac[i]);
    }
    return isEqual;
  };

  void Print() {
    Serial.print("MAC["); 
    for (int i=0; i<6; i++) {
        Serial.print(_mac[i], HEX);
    }
    Serial.print("]"); 
  };
};

class ClientRegInfo {
  IPAddress _IPClient;
  MACAddress _MAC;
  
  public:
  ClientRegInfo() {
  };
  ClientRegInfo( char *mac, IPAddress IPClient) {
    _IPClient = IPClient;
    _MAC.setAddress(mac);
  };
  
  bool operator==(const ClientRegInfo& addr) const { 
    return (_IPClient==addr._IPClient && _MAC==addr._MAC );
  };

  IPAddress getIP() {return _IPClient;};
  MACAddress getMAC() { return _MAC; };
  
  void Print() {
      _MAC.Print();
      Serial.print(" IP["); Serial.print(_IPClient); Serial.println("]");
  };
};

#endif

Registration.cpp

C/C++
/*
 * All stuff to interact with the Serial Monitor 
 */
#include <ESP8266WiFi.h>                  // istall board manager sw via http://arduino.esp8266.com/stable/package_esp8266com_index.json

#include "./Registration.h"

#define DEBUG

int numRegisteredClients=0;

ClientRegInfo RegisteredClients[MAX_NR_REGISTRATIONS];

IPAddress getClientIP(int  idx) {
  return RegisteredClients[idx].getIP();
}

bool processRegistrationRequest(char *pMAC, IPAddress IPClient) {
   Serial.println("Enter processRegistrationRequest()");
   ClientRegInfo info(pMAC, IPClient);
   
   int newIndex=numRegisteredClients;
   for (int index=0; index<numRegisteredClients; index++) {
      // check if there is already an entry for this client
      if ((RegisteredClients[index]).getMAC() == info.getMAC()) {
        newIndex = index; // existing entry
        break;
      }
   }
   if (newIndex<MAX_NR_REGISTRATIONS) {
     memcpy( &(RegisteredClients[newIndex]), &info, sizeof(info));
     if (newIndex == numRegisteredClients) {
        numRegisteredClients++; // new entry
     }
     Serial.print("RegEntry["); Serial.print(newIndex); Serial.print("] = "); info.Print();
     return true;
   } 
   Serial.println("Max number of registrations exceeded; reguest dropped...");
   return false;
}

void printRegistrations() {
   if (numRegisteredClients>0) {
    Serial.println("Registered Clients:");
   }
   for (int index=0; index<numRegisteredClients; index++) {
      // check if there is already an entry for this client
      Serial.print("- Entry["); Serial.print(index); Serial.print("] = "); 
      RegisteredClients[index].Print();
   }
}

SerialMonitor.h

C/C++
#ifndef SerialMonitor_h
#define SerialMonitor_h

// Support for interaction & testing via the Serial Monitor

// For Interaction
void HandleSerialMonitorCommunication();
void ShowSerialCommands(); 
// for getting info
void printProgramVerion();
void printStartupMode();
void printCurrentMode(); // configuration or Operational
void printBoardType(); // Sender or Receiver
void UpdateHeapInfo(); // free heap info
void printWifiMode();  // 4 modes:  Off, STA, AP or AP+STA

#endif

SerialMonitor.cpp

C/C++
/*
 * All stuff to interact with the Serial Monitor 
 */
#include <ESP8266WiFi.h>                  // istall board manager sw via http://arduino.esp8266.com/stable/package_esp8266com_index.json

#include "./SerialMonitor.h"
#include "./configuration.h"
#include "./SomeUtils.h"
#include "./Registration.h"
extern void sendPushNotification();
#define DEBUG

#ifdef DEBUG
////////////////////////////////////////////////
//  Keep track of heap usage on an ESP device
//  Call Update regularly; e.g every loop to track the minimal heap
//  Check the actual heap with getFreeHeap, the minimal with getMinFreeHeap
////////////////////////////////////////////////

class CFreeHeap {
  public:
    CFreeHeap() {
      _minFreeHeap = 0x7FFFFFFFL;
    };
    long lowest() {
      return _minFreeHeap;
    };
    int actual() {
      long curFreeHeap = ESP.getFreeHeap();
      if (curFreeHeap < _minFreeHeap)
         _minFreeHeap = curFreeHeap;
      return curFreeHeap;
    };
    void update() { // keep track of minimal heap size only
      this->actual();
    };
  private:
    long _minFreeHeap; 
};
CFreeHeap         freeHeap;

void UpdateHeapInfo() {
  freeHeap.update(); 
}
#else
void UpdateHeapInfo() {
  // do nothing
}
#endif

////////////////////////////////////////////////
// Debugging print support
////////////////////////////////////////////////

void printProgramVerion() {
  Serial.print("\n=====================================\n=========  DoorBell ");
  Serial.print(DOORBELL_PROGRAM_VERSION);
  Serial.println("  ===========");

}
void printStartupMode() {
   Serial.println(ConfigurationData.Configured ? "Startup Mode : Operational mode" : "Startup Mode : Configuration Mode" );  
}

extern bool doOTA;
void printCurrentMode() {
  Serial.print("Current mode: ");
  if (doOTA) {
     Serial.println("OTA Mode");
  } else {
     Serial.println(ConfigurationData.Configured ? "Operational mode (STA)" : "Configuration Mode (AP)" );  
  }
}


void printBoardType() {
   Serial.println(ConfigurationData.isServer ? "Board acts as a Sender (monitoring bell switch)" : "Board acts as a Receiver (monitoring broadcasts on the network)" );  
}

void printWifiMode() {
  WiFiMode_t  wifiMode; // WIFI_AP, WIFI_STA, WIFI_AP_STA or WIFI_OFF
  Serial.print("WiFi Mode: ");
  wifiMode = WiFi.getMode();
  Serial.println( (wifiMode==WIFI_OFF) ? "Off" : ((wifiMode==WIFI_STA) ? "Stand Alone" : ((wifiMode==WIFI_AP) ? "Access Point" : "Access Point+Stand Alone") ) );
  //WiFi.printDiag(Serial);
}



////////////////////////////////////////////////
// Serial Configuration & Debugging support
////////////////////////////////////////////////
void  HandleSerialMonitorCommunication() {
  if (Serial.available()) {
    // enable mode toggling and configure settings for debugging via serial terminal
    char command[100];
    command[Serial.readBytesUntil('\r', command, 99)] = '\0';
    String s(command);
    if (s == "config") {
      ConfigurationData.Configured = false; // back to configuration mode
      printCurrentMode();
    }
    else if (s == "connect") {
      ConfigurationData.Configured = true; // back to operational mode
      printCurrentMode();
    }
    else if (s.substring(0, 4) == "pwd=") {
      s.remove(0,4);
      s.toCharArray( ConfigurationData.wlanPWD, PWD_MAXLEN);
      Serial.print("PassWord changed to: ["); Serial.print(ConfigurationData.wlanPWD);  Serial.println("]");
    }
    else if (s.substring(0, 5) == "ssid=") {
      s.remove(0,5);
      s.toCharArray( ConfigurationData.wlanSSID, SSID_MAXLEN);
      Serial.print("SSID changed to: ["); Serial.print(ConfigurationData.wlanSSID);  Serial.println("]");
    }
    else if (s.substring(0, 5) == "port=") {
      s.remove(0,5);
      ConfigurationData.BrdCstUDPPort = atol( s.c_str()); 
      Serial.print("Port changed to: ["); Serial.print(ConfigurationData.BrdCstUDPPort);  Serial.println("]");
    }
    else if (s == "save") {
       ConfigurationData.Configured=true; // board will start up in operational mode next time
       SaveConfiguration();
       Serial.println("Configuration Saved to EEProm, (back to) operational mode");
    }
    else if (s == "sender") {
       ConfigurationData.isServer=true; // board will act as a sender (server) device
       Serial.println("Board acts as a sender... ");
    }
    else if (s == "receiver") {
       ConfigurationData.isServer=false; // board will act as a receiver (client) device
       Serial.println("Board acts as a receiver... ");
    }
    else if (s.substring(0, 6) == "pmode=") {
      s.remove(0,6);
      ConfigurationData.pushMode = (s == "G" ? GET : (s == "P" ? POST : DISABLED)); 
      Serial.print("Push Mode changed to: "); Serial.println(ConfigurationData.pushMode == GET ? "[GET]" : (ConfigurationData.pushMode == POST ? "[POST]" : "[Disabled]"));
    }
    else if (s.substring(0, 5) == "psvr=") {
      s.remove(0,5);
      s.toCharArray( ConfigurationData.pushSvr, SVR_MAXLEN);
      Serial.print("Push Server changed to: ["); Serial.print(ConfigurationData.pushSvr);  Serial.println("]");
    }
    else if (s.substring(0, 5) == "pcmd=") {
      s.remove(0,5);
      s.toCharArray( ConfigurationData.pushCmd, CMD_MAXLEN);
      Serial.print("Push Command changed to: ["); Serial.print(ConfigurationData.pushCmd);  Serial.println("]");
    }
    else if (s.substring(0,5) == "clear") {
      clearConfiguration();
    }
     else if (s.substring(0,5) == "ptest") {
       if( ConfigurationData.pushMode == DISABLED) {
          Serial.println("Push Service is not yet enabled (pmode)");
       }
       else {
          sendPushNotification();
       }
    }
    else if (s.substring(0,7) == "restart") {
      ESP.restart();
    }
    else if (s == "show") {
       Serial.println("DoorBell "DOORBELL_PROGRAM_VERSION" Configuration settings:");
       printStartupMode();
       Serial.print("SSID         : "); Serial.println(ConfigurationData.wlanSSID);
       Serial.print("Password     : "); Serial.println(ConfigurationData.wlanPWD);
       Serial.print("BrdCstUDPPort: "); Serial.println(ConfigurationData.BrdCstUDPPort);
       Serial.print("Device type  : "); printBoardType();
       Serial.print("Push Mode    : "); Serial.println(ConfigurationData.pushMode == GET ? "GET" : (ConfigurationData.pushMode == POST ? "POST" : "Disabled"));
       Serial.print("Push Server  : "); Serial.println(ConfigurationData.pushSvr);
       Serial.print("Push Command : "); Serial.println(ConfigurationData.pushCmd);
       printRegistrations();
       printCurrentMode();
    }   
    else if (s == "help") {
      ShowSerialCommands();
    }
    // for testing purpose only:
#ifdef DEBUG
    else if (s.substring(0, 5) == "leak=") { // leak 1000 bytes
      static char *buffer=NULL;
      Serial.print("FreeHeap(act): "); Serial.println(freeHeap.actual());
      s.remove(0,5);
      long leakage = atol( s.c_str()); 
      Serial.print("Leakage attempt: "); Serial.println(leakage);
      buffer = (char *)malloc(leakage);
      if (buffer == NULL)
       Serial.println("Malloc Failed (heap can be fragmented,try smaller chunks");
      else      
       Serial.print("Succeeded, remaining heap: "); Serial.println(freeHeap.actual());
    }
    else if (s == "mem") { 
       Serial.println("Memory usage :");
       Serial.print("FreeHeap(act): "); Serial.println(freeHeap.actual());
       Serial.print("FreeHeap(low): "); Serial.println(freeHeap.lowest());
    }
#endif

  }
}

void ShowSerialCommands() {
  Serial.println("=====================================\n"
                 "Supported serial input Commands (case sensitive; use the send bar):\n"
                 " help          : show this overview\n"
                 " show          : show the current settings\n"
                 " config        : switch board to configuration mode (hotspot+webserver)\n"
                 " connect       : switch board to operational mode (connect the WLAN)\n"
                 " ssid=<SSID>   : the WLAN SSID\n"
                 " pwd=<password>: the WLAN password\n"
                 " port=<number> : the destination UDP listen port of the clients\n"
                 " sender        : act as a sender (server) device (monitor bell switch to broadcast on WLAN)\n"
                 " receiver      : act as a receiver (client) device (monitor WLAN for broadcasts to active a relay)\n"
                 " pmode=<D,G,P> : Either Disable, or enable a GET or POST push command type\n"
                 " psvr=<server> : Enter a Notification server name\n"
                 " pcmd=<command>: Enter a push command\n"
                 " ptest         : Test the Push notification\n"
                 " clear         : Erase configuration (EEProm). Board starts in configuration mode next BOOT\n"
                 " save          : Persist new settings entered via Serial interface (and switches to operational)\n"
                 " restart       : Restart board\n"
#ifdef DEBUG
                 " --- For testing ---\n"
                 " mem           : returns the free heap(lowest and current); usable for checking memory leaks\n"
                 " leak=<size>   : create a leak with size in bytes; choose a smaller size if it fails\n"
#endif
                 "=====================================\n");
}

OTASupport.h

C/C++
#ifndef OTA_SUPPORT_h
#define OTA_SUPPORT_h

void SetupOTAMode();
void OTAHandler(); 


#endif

OTASupport.cpp

C/C++
#include <ESP8266WiFi.h>
//#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include "./OTASupport.h"

void SetupOTAMode() {  // reboot is connection fails
  
  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  ArduinoOTA.setHostname("DoorBell_in_OTA");

  // No authentication by default
  // ArduinoOTA.setPassword((const char *)"123");

  ArduinoOTA.onStart([]() {
    Serial.println("Start OTA download");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd OTA download");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("OTA preparation Ready. Waiting for upload...");
}

void OTAHandler() {
    ArduinoOTA.handle();
}

Credits

erkr

erkr

4 projects • 19 followers

Comments