Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
My project purposes:
Read more- Enable smooth wireless configuration of Wi-Fi based Arduino boards via a HotSpot with a Configuration Web page
- Usage of Arduino OTA to upgrade the board over Wi-Fi (without the need to hook-up the board to USB)
- Functionally the example broadcasts a doorbell event on my local Wi-Fi, so receivers like smartphones can notify me (as long I'm in reach of my home Wi-Fi), and/or notify me everywhere with the use of Push Notifications
- Supports dynamical switching between two modes of Wi-Fi operation (SoftAp - STA): The use-case to make wireless Arduino devices configurable via an on board web server.
- Offers an OTA mode to upgrade the board over Wi-Fi (with the Arduino IDE)
- 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 example for sending Doorbell rang events to another Arduino board (same software, but configured as receiver) or Android phones (with the corresponding Doorbell Android app on my personal website).
- Option to enable Push Notifications to mobile devices (Android, iOS, Windows)
/*
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.");
}
}
#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
}
#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
#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();
}
// 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)
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;
}
};
#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> <input type=\"submit\" name=\"Sbutton\" value=\"Save\" /> <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();
}
}
////////////////////////////////////////////////
// HTTP response handlers
////////////////////////////////////////////////
void HandleConfigure();
void handleNotFound();
void HandleSettings();
////////////////////////////////////////////////
// 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();
};
#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
/*
* 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();
}
}
#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
/*
* 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");
}
#ifndef OTA_SUPPORT_h
#define OTA_SUPPORT_h
void SetupOTAMode();
void OTAHandler();
#endif
#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();
}
Thanks to M. Ray Burnette and Clement Storck.
Comments
Please log in or sign up to comment.