|
/* |
|
Simple alternative ESP8266 or ESP8265 firmware for |
|
T4EU1C switches. |
|
|
|
Note: |
|
This firmware contains the following placeholders inside code to be |
|
filled by a shellscript / the Jenkins build file: |
|
- WiFi SSID and Password |
|
{FILLIN_WIFI_SSID} |
|
{FILLIN_WIFI_PWD} |
|
- OTA shared secret and device name |
|
{FILLIN_OTA_NAME} |
|
{FILLIN_OTA_KEY} |
|
- MQTT credentials and server configuration |
|
{FILLIN_MQTT_SERVER} |
|
{FILLIN_MQTT_PORT} |
|
{FILLIN_MQTT_USER} |
|
{FILLIN_MQTT_PWD} |
|
{FILLIN_MQTT_TOPIC} |
|
- Additional title |
|
{FILLIN_TITLE} |
|
An own firmware image has to be built for every device. Also make |
|
sure not to publish your credentials to any SCM system (for example |
|
by filling them in your CI/CD chain). |
|
*/ |
|
|
|
// Select which control mechanisms should be embedded into the firmware |
|
#define CTRL_HTTP_ENABLE 1 |
|
#define CTRL_MQTT_ENABLE 1 |
|
|
|
/* Credentials and connection information */ |
|
const char* ssid = "{FILLIN_WIFI_SSID}"; |
|
const char* password = "{FILLIN_WIFI_PWD}"; |
|
|
|
#define MQTT_SERVER "{FILLIN_MQTT_SERVER}" |
|
#define MQTT_PORT {FILLIN_MQTT_PORT} |
|
#define MQTT_USER "{FILLIN_MQTT_USER}" |
|
#define MQTT_PASSWORD "{FILLIN_MQTT_PWD}" |
|
|
|
#ifdef CTRL_HTTP_ENABLE |
|
static const char* htmlPart1 = "<!DOCTYPE HTML><html><head><title>Lichtschalter {FILLIN_TITLE}</title><style type=\"text/css\">h1{display:block;color:white;background-color:darkgreen;font-variant:small-caps;padding:0.5ex 1ex 0.5ex 1ex;margin-bottom:0;}div#m{margin-top:0;border:1px solid grey;padding:0.5ex 1ex 0.5ex 1ex;}</style></head><body><h1>Simpler Lichtschalter {FILLIN_TITLE}</h1><div id=\"m\"><p>Status: <strong>"; |
|
static const char* htmlPart2 = "</strong> (<a href=\"/on\">Ein</a>, <a href=\"/off\">Aus</a>)</p></div></body></html>"; |
|
|
|
static const char* htmlOn = "Ein"; |
|
static const char* htmlOff = "Aus"; |
|
#endif |
|
|
|
/* |
|
One should simply always enable OTA if there is not |
|
a really really good reason not to do so |
|
*/ |
|
#define OTA_ENABLE 1 |
|
|
|
/* |
|
Touch delays for special commands: |
|
1000-5000 ms Enable or disable manual mode |
|
*/ |
|
#define TOUCHDURATION_MANUAL__MIN 1000 |
|
#define TOUCHDURATION_MANUAL__MAX 5000 |
|
|
|
/* |
|
If the following option is set the pure connection to |
|
HTTP prevents local control like MQTT control would |
|
do. Note that HTTP normally doesnt provide event |
|
notification to your home automation system so this |
|
might be problematic ... |
|
|
|
Note that automatic control can be overriden using |
|
manual mode. |
|
*/ |
|
// #define HTTP_PREVENTS_MANUAL 1 |
|
|
|
/* |
|
LED2 (WiFi LED) configuration: |
|
NONET Blinking period in case no WiFi connection is available (default: 250 ms) |
|
NOMQTT Blinking period in case WiFi is available but MQTT is not connected (default: 1 second) |
|
NOCMD Blinking period after command timeout (default: 5 seconds) |
|
AUTO Period for automatic mode (default: no blinking, 0) |
|
LOCAL Period for manual mode (default: permanent on, ~0) |
|
*/ |
|
#ifndef DELAY_BLINK_NONET |
|
#define DELAY_BLINK_NONET 250 |
|
#endif |
|
#ifndef DELAY_BLINK_NOMQTT |
|
#define DELAY_BLINK_NOMQTT 1000 |
|
#endif |
|
#ifndef DELAY_BLINK_NOCMD |
|
#define DELAY_BLINK_NOCMD 10000 |
|
#endif |
|
#ifndef DELAY_BLINK_AUTO |
|
#define DELAY_BLINK_AUTO 0 |
|
#endif |
|
#ifndef DELAY_BLINK_LOCAL |
|
#define DELAY_BLINK_LOCAL (~0) |
|
#endif |
|
|
|
#ifndef TIMEOUT_MANUAL_MODE |
|
/* |
|
Set default manual mode timeout. This is the time after which a |
|
device switches back from forced manual modhe if nothing else happens. |
|
If a network connection is available it switches back into automatic |
|
mode, else into non-forced manual mode. |
|
|
|
If set to 0 this feature is disabled and the device never switches |
|
back to automatic mode on it's own (note that local intervention is |
|
required in this case to leave manual mode). |
|
|
|
Default: 4 hours |
|
*/ |
|
#define TIMEOUT_MANUAL_MODE (4*3600) |
|
#endif |
|
|
|
#ifndef TIMEOUT_AUTO_MODE |
|
/* |
|
This is the keepalive timeout. If no automatic control message is |
|
received during this period the device switches to manual mode |
|
to provide local control in case of system failure. |
|
|
|
If set to 0 this feature is disabled (but it's highly recommended |
|
to be used to prevent device disfunctioning in case of backend |
|
error) |
|
|
|
Default: 5 minutes |
|
*/ |
|
#define TIMEOUT_AUTO_MODE (5*60) |
|
#endif |
|
|
|
#ifndef TIMEOUT_POWERON |
|
/* |
|
Power on timeout. If power is enabled for longer than this period |
|
power is automatically disabled (switched off) to prevent to keep |
|
a device powered if forgotten. |
|
|
|
If set to 0 this feature is disabled. It's recommended to use this |
|
feature if disabling is not a strict requirement for the given |
|
use-case. |
|
*/ |
|
#define TIMEOUT_POWERON (24*3600) |
|
#endif |
|
|
|
#include <ESP8266WiFi.h> |
|
#include <ESP8266mDNS.h> |
|
#include <WiFiUdp.h> |
|
#include <ArduinoOTA.h> |
|
#include <ESP8266WebServer.h> |
|
#include <Adafruit_MQTT.h> |
|
#include <Adafruit_MQTT_Client.h> |
|
|
|
#include <wpa2_enterprise.h> |
|
|
|
/* |
|
MQTT reconnection interval. This interval is *not* related |
|
to the internal reconnection strategy by the MQTT library. It |
|
is an (seconds) interval after which the MQTT client gets |
|
reset locally |
|
*/ |
|
#ifndef MQTT_RECONNECT_INTERVAL |
|
#define MQTT_RECONNECT_INTERVAL 5 |
|
#endif |
|
/* |
|
MQTT report interval. This is the interval after which the |
|
switch sends one of his periodic status reports (this might |
|
even be used for monitoring). If undefined periodic reports |
|
do not happen. |
|
*/ |
|
#ifndef MQTT_REPORT_INTERVAL |
|
#define MQTT_REPORT_INTERVAL 60 |
|
#endif |
|
|
|
#ifndef MQTT_PROCESS_PACKETS_TIMEOUT |
|
#define MQTT_PROCESS_PACKETS_TIMEOUT 150 |
|
#endif |
|
|
|
/* |
|
Sonoff T1: |
|
GPIO Direction Function Level information |
|
00 INPUT Button/Touch sensor Active low |
|
12 OUTPUT Relays and main LED Active high |
|
13 OUTPUT WiFi LED (blue) Active low |
|
2 Gang Version: |
|
5 OUTPUT Channel 2 |
|
10 INPUT Button/Touch sensor Active low |
|
2 Gang Version: |
|
4 OUTPUT Channel 3 |
|
9 INPUT Button/Touch sensor Active low |
|
*/ |
|
#define LEDPORT 13 |
|
#define RELAISPORT 12 |
|
#define SWITCHPORT 0 |
|
|
|
// Webserver object (if HTTP control is enabled) |
|
#ifdef CTRL_HTTP_ENABLE |
|
ESP8266WebServer server(80); |
|
#endif |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
WiFiClient wclient; |
|
// WiFiClientSecure wclient; /* For SSL/TLS */ |
|
|
|
Adafruit_MQTT_Client mqtt(&wclient, MQTT_SERVER, MQTT_PORT, MQTT_USER, MQTT_PASSWORD); |
|
Adafruit_MQTT_Publish mqttTopicOut(&mqtt, "{FILLIN_MQTT_TOPIC}"); |
|
Adafruit_MQTT_Subscribe mqttTopicIn_On(&mqtt, "{FILLIN_MQTT_TOPIC}/on"); |
|
Adafruit_MQTT_Subscribe mqttTopicIn_Off(&mqtt, "{FILLIN_MQTT_TOPIC}/off"); |
|
Adafruit_MQTT_Subscribe mqttTopicIn_Status(&mqtt, "{FILLIN_MQTT_TOPIC}/status"); |
|
|
|
unsigned long int dwMQTTLastReport; |
|
#endif |
|
|
|
/* |
|
State block for local state machines: |
|
Mains state (switch status): |
|
statePower just keeps track of the relais / main LEDs status and can be true or false |
|
Network state: |
|
stateNet keeps track of overall status: |
|
not connected |
|
WiFi connected but no MQTT |
|
MQTT connected (if MQTT is enabled, else WiFi is same as MQTT) |
|
Control state: |
|
unsigned long int stateAutoKeepaliveTimeout |
|
counts down up to the timeout of the last automatic control message. If no |
|
message is received during this time the device switches to manual control |
|
mode automatically |
|
unsigned long int stateManualForceTimeout |
|
counts down till a timeout after which the device automatically switches back |
|
from manual mode to automatic mode if put into FORCED mode. |
|
unsigned long int dwPowerTimeout: |
|
Counts downwards in case of enabled power. This provides an auto-shutdown |
|
feature for devices that have been forgotten to be turned off. |
|
*/ |
|
static boolean statePower = false; |
|
static boolean led2State = false; |
|
static unsigned long int dwTimeoutManualMode; |
|
static unsigned long int dwTimeoutAutoKeepalive; |
|
static unsigned long int dwPowerTimeout; |
|
static unsigned long int dwDelayBlink; |
|
static unsigned long int dwWiFiCheckTimeout; /* This period is used to check if WiFi connectivity is available */ |
|
static unsigned long int oldMillis; /* Used for delay calculations */ |
|
static unsigned long int dwTimeoutTickLast; |
|
static unsigned long int dwMqttLastConnectTry; |
|
|
|
enum stateNetwork { |
|
stateNetwork_Disconnected = 0, |
|
stateNetwork_ConnectedWiFi = 1, |
|
stateNetwork_ConnectedMQTT = 2, |
|
}; |
|
static enum stateNetwork stateNet; |
|
|
|
static boolean isManualMode() { |
|
// If manual mode is forced then it's actiev till timeout |
|
if (dwTimeoutManualMode > 0) { |
|
return true; |
|
} |
|
|
|
/* |
|
If manual mode is not forced and MQTT connection is |
|
available (or WiFi in case MQTT is not used) the device |
|
is NOT in manual mode; if it's not connected properly |
|
it's in manual mode |
|
*/ |
|
#ifdef CTRL_MQTT_ENABLE |
|
if (stateNet != stateNetwork_ConnectedMQTT) { |
|
return true; |
|
} |
|
#else |
|
#ifdef HTTP_PREVENTS_MANUAL |
|
if (stateNet != stateNetwork_ConnectedWiFi) { |
|
return true; |
|
} |
|
#else |
|
return true; |
|
#endif |
|
#endif |
|
|
|
/* |
|
In case our keepalive timeout counted down to zero |
|
we switch into manual mode automatically |
|
*/ |
|
if (dwTimeoutAutoKeepalive == 0) { |
|
return true; |
|
} |
|
|
|
// We are in automatic mode (connected AND timeout not reached AND not forced) |
|
return false; |
|
} |
|
|
|
static unsigned long int getBlinkDelay() { |
|
/* |
|
Blink modes: |
|
DELAY_BLINK_NONET If no network connection is available; (and we accept local commands) |
|
DELAY_BLINK_NOMQTT If network is available but no MQTT connection is active (if compiled); (and we accept local commands) |
|
DELAY_BLINK_NOCMD If command timeout has reached (and we accept local commands) |
|
DELAY_BLINK_AUTO If everything is connected and we are in auto mode |
|
DELAY_BLINK_LOCAL If we are in forced local mode |
|
There are two "special" delays: |
|
0 Disable LED, no blinking at all |
|
~0 Enable LED, no blinking at all |
|
*/ |
|
// If manual mode is forced then it's active till timeout |
|
if (dwTimeoutManualMode > 0) { |
|
return DELAY_BLINK_LOCAL; |
|
} |
|
|
|
/* |
|
If manual mode is not forced and MQTT connection is |
|
available (or WiFi in case MQTT is not used) the device |
|
is NOT in manual mode; if it's not connected properly |
|
it's in manual mode |
|
*/ |
|
if ((stateNet != stateNetwork_ConnectedWiFi) && (stateNet != stateNetwork_ConnectedMQTT)) { |
|
return DELAY_BLINK_NONET; |
|
} |
|
#ifdef CTRL_MQTT_ENABLE |
|
if (stateNet != stateNetwork_ConnectedMQTT) { |
|
return DELAY_BLINK_NOMQTT; |
|
} |
|
#endif |
|
|
|
/* |
|
In case our keepalive timeout counted down to zero |
|
we switch into manual mode automatically |
|
*/ |
|
if (dwTimeoutAutoKeepalive == 0) { |
|
return DELAY_BLINK_NOCMD; |
|
} |
|
|
|
// We are in automatic mode (connected AND timeout not reached AND not forced) |
|
return DELAY_BLINK_AUTO; |
|
} |
|
|
|
static void switchOn() { |
|
digitalWrite(RELAISPORT, HIGH); |
|
statePower = true; |
|
#if TIMEOUT_POWERON > 0 |
|
dwPowerTimeout = TIMEOUT_POWERON; |
|
#else |
|
dwPowerTimeout = 1; // Any constant != 0 will do |
|
#endif |
|
|
|
#ifdef DEBUG |
|
Serial.println("Switching power on"); |
|
#endif |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
mqttRequestStatus(NULL, 0); |
|
#endif |
|
} |
|
|
|
static void switchOff() { |
|
digitalWrite(RELAISPORT, LOW); |
|
statePower = false; |
|
dwPowerTimeout = 0; |
|
|
|
#ifdef DEBUG |
|
Serial.println("Switching power off"); |
|
#endif |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
mqttRequestStatus(NULL, 0); |
|
#endif |
|
} |
|
|
|
#ifdef CTRL_HTTP_ENABLE |
|
static void sendStatusPage(int statusCode) { |
|
String resp; |
|
|
|
resp += htmlPart1; |
|
resp += statePower ? htmlOn : htmlOff; |
|
resp += htmlPart2; |
|
|
|
/* We set auto-refresh every 15 seconds */ |
|
server.sendHeader("Refresh", "15; url=/"); |
|
/* |
|
And disable client- and proxy side caching |
|
as well as reverse proxy caching |
|
*/ |
|
server.sendHeader("Cache-control", "no-cache, no-store, must-revalidate"); |
|
server.sendHeader("Pragma", "no-cache"); |
|
server.send(statusCode, "text/html", resp); |
|
} |
|
|
|
static void handleOn() { |
|
if (isManualMode() && (dwTimeoutManualMode > 0)) { |
|
sendStatusPage(403); |
|
} else { |
|
switchOn(); |
|
sendStatusPage(200); |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
} |
|
} |
|
static void handleOff() { |
|
if (isManualMode() && (dwTimeoutManualMode > 0)) { |
|
sendStatusPage(403); |
|
} else { |
|
switchOff(); |
|
sendStatusPage(200); |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
} |
|
} |
|
static void handleRoot() { |
|
sendStatusPage(200); |
|
} |
|
|
|
static void handleNotFound() { |
|
server.send(404, "text/plain", "Unknown URI"); |
|
} |
|
#endif |
|
|
|
static unsigned long int touchDuration = 0; |
|
ICACHE_RAM_ATTR void touchSensor() { |
|
/* |
|
Check when last touch was detected (software debounce) |
|
and in case it's long enough |
|
- Check if is manual control is enabled (i.e. not |
|
disabled OR we have no connection to the network). |
|
In this case invert ledStatus and set output |
|
- In case manual control is disabled transmit |
|
notification about button touch to MQTT broker |
|
but do not modify state ... |
|
*/ |
|
if (digitalRead(SWITCHPORT) == LOW) { |
|
touchDuration = millis(); |
|
if (isManualMode()) { |
|
// Manual mode: Perform state change (and send message if required) |
|
if (statePower) { |
|
switchOff(); |
|
} else { |
|
switchOn(); |
|
} |
|
if (dwTimeoutManualMode > 0) { |
|
dwTimeoutManualMode = TIMEOUT_MANUAL_MODE; |
|
} |
|
mqttRequestStatus(NULL, 0); |
|
} else { |
|
// Automatic mode: Simply dispatch messages (ToDo) |
|
String resp; |
|
resp += "{ \"status\" : "; |
|
resp += (statePower) ? "true" : "false"; |
|
resp += ", \"manual\" : "; |
|
resp += (isManualMode()) ? "true" : "false"; |
|
resp += ", \"eventButton\" : 1 "; |
|
resp += "}"; |
|
dwMQTTLastReport = millis(); |
|
mqttTopicOut.publish(resp.c_str()); |
|
} |
|
} else { |
|
/* |
|
We released the keypress. In case of longer keypresses we want |
|
to trigger some different actions ... |
|
*/ |
|
unsigned long int dt; |
|
unsigned long int curMillis = millis(); |
|
if(curMillis > touchDuration) { dt = curMillis - touchDuration; } else { dt = (~0)-touchDuration + curMillis; } |
|
|
|
#if TOUCHDURATION_MANUAL__MIN != TOUCHDURATION_MANUAL__MAX |
|
if((dt >= TOUCHDURATION_MANUAL__MIN) && (dt < TOUCHDURATION_MANUAL__MAX)) { |
|
// We triggered manual mode switching |
|
if(dwTimeoutManualMode == 0) { |
|
#if TIMEOUT_MANUAL_MODE > 0 |
|
dwTimeoutManualMode = TIMEOUT_MANUAL_MODE; |
|
#else |
|
dwTimeoutManualMode = 1; |
|
#endif |
|
// ToDo: Send message that we ENTERED manual mode ... |
|
} else { |
|
dwTimeoutManualMode = 0; |
|
// ToDo: Send message that we LEFT manual mode |
|
} |
|
mqttRequestStatus(NULL, 0); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
static void mqttRequestStatus(char* data, uint16_t len) { |
|
if(data != NULL) { |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
} |
|
String resp; |
|
resp += " { \"status\" : "; |
|
resp += (statePower) ? "true" : "false"; |
|
resp += ", \"manual\" : "; |
|
resp += (isManualMode()) ? "true" : "false"; |
|
if((data == NULL) && (len == 1)) { |
|
resp += ", \"errormsg\" : \"Manual mode is enabled, request not fulfilled\""; |
|
resp += ", \"error\" : \"manualset\""; |
|
} |
|
resp += "}"; |
|
|
|
dwMQTTLastReport = millis(); |
|
mqttTopicOut.publish(resp.c_str()); |
|
} |
|
|
|
static void mqttRequestOn(char* data, uint16_t len) { |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
if (isManualMode() && (dwTimeoutManualMode > 0)) { |
|
mqttRequestStatus(NULL, 1); |
|
} else { |
|
switchOn(); |
|
} |
|
} |
|
|
|
static void mqttRequestOff(char* data, uint16_t len) { |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
if (isManualMode() && (dwTimeoutManualMode > 0)) { |
|
mqttRequestStatus(NULL, 1); |
|
} else { |
|
switchOff(); |
|
} |
|
} |
|
#endif |
|
|
|
void setup() { |
|
byte mac[6]; |
|
|
|
/* |
|
Setup I/O pins: |
|
Relaisport Output |
|
LED port Output |
|
Switch port Input |
|
*/ |
|
pinMode(RELAISPORT, OUTPUT); |
|
pinMode(LEDPORT, OUTPUT); |
|
pinMode(SWITCHPORT, INPUT_PULLUP); |
|
|
|
digitalWrite(LEDPORT, LOW); |
|
digitalWrite(RELAISPORT, LOW); |
|
|
|
attachInterrupt(digitalPinToInterrupt(SWITCHPORT), touchSensor, CHANGE); |
|
|
|
#ifdef DEBUG |
|
Serial.begin(115200); |
|
#endif |
|
delay(1000); |
|
|
|
digitalWrite(LEDPORT, HIGH); |
|
delay(1000); |
|
|
|
#ifdef DEBUG |
|
delay(150); |
|
while (!Serial) { |
|
delay(1); |
|
} |
|
#endif |
|
|
|
#ifdef DEBUG |
|
Serial.println("Booting"); |
|
Serial.setDebugOutput(true); |
|
#endif |
|
|
|
WiFi.macAddress(mac); |
|
#ifdef DEBUG |
|
Serial.print("MAC: "); |
|
Serial.print(mac[0], HEX); |
|
Serial.print(":"); |
|
Serial.print(mac[1], HEX); |
|
Serial.print(":"); |
|
Serial.print(mac[2], HEX); |
|
Serial.print(":"); |
|
Serial.print(mac[3], HEX); |
|
Serial.print(":"); |
|
Serial.print(mac[4], HEX); |
|
Serial.print(":"); |
|
Serial.println(mac[5], HEX); |
|
#endif |
|
|
|
// Reset WPA Enterprise settings (ToDo: Configure EAP-TLS) |
|
wifi_station_set_wpa2_enterprise_auth(0); |
|
wifi_station_clear_cert_key(); |
|
wifi_station_clear_enterprise_ca_cert(); |
|
wifi_station_clear_enterprise_identity(); |
|
wifi_station_clear_enterprise_username(); |
|
wifi_station_clear_enterprise_password(); |
|
wifi_station_clear_enterprise_new_password(); |
|
|
|
// Try to build up WiFi connection (we do NOT wait for the connection to actually happen) |
|
WiFi.persistent(false); /* Disable WiFi persistence to avoid flash writes */ |
|
WiFi.mode(WIFI_OFF); |
|
WiFi.mode(WIFI_STA); |
|
WiFi.begin(ssid, password); |
|
|
|
#ifdef OTA_ENABLE |
|
// Hostname defaults to esp8266-[ChipID] |
|
ArduinoOTA.setHostname("{FILLIN_OTA_NAME}"); |
|
ArduinoOTA.setPassword("{FILLIN_OTA_KEY}"); |
|
ArduinoOTA.onStart([]() { |
|
#ifdef DEBUG |
|
Serial.println("Start updating"); |
|
#endif |
|
return; |
|
}); |
|
ArduinoOTA.onEnd([]() { |
|
#ifdef DEBUG |
|
Serial.println("\nEnd"); |
|
#endif |
|
return; |
|
}); |
|
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { |
|
#ifdef DEBUG |
|
Serial.printf("Progress: %u%%\r", (progress / (total / 100))); |
|
#endif |
|
return; |
|
}); |
|
ArduinoOTA.onError([](ota_error_t error) { |
|
#ifdef DEBUG |
|
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"); |
|
} |
|
#endif |
|
return; |
|
}); |
|
ArduinoOTA.begin(); |
|
#endif |
|
|
|
#ifdef CTRL_HTTP_ENABLE |
|
// Enable HTTP server if |
|
server.on("/on", handleOn); |
|
server.on("/off", handleOff); |
|
server.on("/", handleRoot); |
|
server.onNotFound(handleNotFound); |
|
server.begin(); |
|
#ifdef DEBUG |
|
Serial.println("HTTP Server started"); |
|
#endif |
|
#endif |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
mqttTopicIn_On.setCallback(&mqttRequestOn); |
|
mqttTopicIn_Off.setCallback(&mqttRequestOff); |
|
mqttTopicIn_Status.setCallback(&mqttRequestStatus); |
|
mqtt.subscribe(&mqttTopicIn_On); |
|
mqtt.subscribe(&mqttTopicIn_Off); |
|
mqtt.subscribe(&mqttTopicIn_Status); |
|
dwMqttLastConnectTry = 0; |
|
dwMQTTLastReport = millis() - MQTT_REPORT_INTERVAL; |
|
#endif |
|
|
|
statePower = false; |
|
led2State = false; |
|
dwTimeoutManualMode = 0; |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
dwPowerTimeout = 0; |
|
dwDelayBlink = 0; |
|
|
|
dwWiFiCheckTimeout = millis(); |
|
|
|
oldMillis = millis(); |
|
dwTimeoutTickLast = oldMillis; |
|
} |
|
|
|
void loop() { |
|
unsigned long currentMillis = millis(); |
|
unsigned long int dwLastWiFiCheck; |
|
|
|
if (currentMillis >= dwWiFiCheckTimeout) { |
|
dwLastWiFiCheck = currentMillis - dwWiFiCheckTimeout; |
|
} else { |
|
dwLastWiFiCheck = (~0) - dwWiFiCheckTimeout + currentMillis; |
|
} |
|
|
|
int wifiState = WiFi.status(); |
|
if (dwLastWiFiCheck > 5000) { |
|
if (wifiState != WL_CONNECTED) { |
|
stateNet = stateNetwork_Disconnected; |
|
#ifdef DEBUG |
|
Serial.println("WiFi connection not available! Running in manual mode"); |
|
#endif |
|
WiFi.mode(WIFI_OFF); |
|
WiFi.mode(WIFI_STA); |
|
WiFi.begin(ssid, password); |
|
#ifdef DEBUG |
|
Serial.print("Trying to connect to "); Serial.println(ssid); |
|
#endif |
|
} else { |
|
if (stateNet == stateNetwork_Disconnected) { |
|
#ifdef DEBUG |
|
Serial.println("WiFi connection became available"); |
|
#endif |
|
Serial.println(WiFi.localIP()); |
|
stateNet = stateNetwork_ConnectedWiFi; |
|
// ToDo: Transmit message (including our state) if we get online ... |
|
#ifndef CTRL_MQTT_ENABLE |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
#else |
|
mqtt.connect(); // Try MQTT connection ... |
|
#endif |
|
} |
|
} |
|
dwWiFiCheckTimeout = currentMillis; |
|
} |
|
// If we are connected to the network handle OTA and HTTP |
|
if (wifiState == WL_CONNECTED) { |
|
#ifdef OTA_ENABLE |
|
yield(); |
|
ArduinoOTA.handle(); |
|
#endif |
|
#ifdef CTRL_HTTP_ENABLE |
|
yield(); |
|
server.handleClient(); |
|
#endif |
|
} |
|
|
|
#ifdef CTRL_MQTT_ENABLE |
|
yield(); |
|
if (wifiState == WL_CONNECTED) { |
|
if (mqtt.connected()) { |
|
if (stateNet == stateNetwork_ConnectedWiFi) { |
|
#if TIMEOUT_AUTO_MODE > 0 |
|
dwTimeoutAutoKeepalive = TIMEOUT_AUTO_MODE; |
|
#else |
|
dwTimeoutAutoKeepalive = 1; |
|
#endif |
|
stateNet = stateNetwork_ConnectedMQTT; |
|
} |
|
mqtt.processPackets(MQTT_PROCESS_PACKETS_TIMEOUT); |
|
} else { |
|
stateNet = stateNetwork_ConnectedWiFi; |
|
if (dwMqttLastConnectTry == 0) { |
|
mqtt.disconnect(); |
|
dwMqttLastConnectTry = currentMillis; |
|
} else { |
|
unsigned long int dwTimeSinceLastTry; |
|
if (currentMillis > dwMqttLastConnectTry) { |
|
dwTimeSinceLastTry = currentMillis - dwMqttLastConnectTry; |
|
} else { |
|
dwTimeSinceLastTry = (~0) - dwMqttLastConnectTry + currentMillis; |
|
} |
|
if (dwTimeSinceLastTry > (MQTT_RECONNECT_INTERVAL*1000)) { |
|
mqtt.connect(); |
|
dwMqttLastConnectTry = 0; |
|
} |
|
} |
|
} |
|
} |
|
|
|
yield(); |
|
unsigned long int dwDurationSinceLastReport; |
|
if (currentMillis > dwMQTTLastReport) { |
|
dwDurationSinceLastReport = currentMillis - dwMQTTLastReport; |
|
} else { |
|
dwDurationSinceLastReport = (~0) - dwMQTTLastReport + currentMillis; |
|
} |
|
if (dwDurationSinceLastReport > (MQTT_REPORT_INTERVAL*1000)) { |
|
mqttRequestStatus(NULL, 0); |
|
} |
|
#endif |
|
|
|
unsigned long int dwTimeoutLastDiff; |
|
if (currentMillis >= dwTimeoutTickLast) { |
|
dwTimeoutLastDiff = currentMillis - dwTimeoutTickLast; |
|
} else { |
|
dwTimeoutLastDiff = (~0) - dwTimeoutTickLast + currentMillis; |
|
} |
|
|
|
if (dwTimeoutLastDiff >= 1000) { |
|
dwTimeoutTickLast = currentMillis; |
|
/* Possible timeout caused state transitions */ |
|
#if TIMEOUT_MANUAL_MODE > 0 |
|
if (isManualMode()) { |
|
if (dwTimeoutManualMode > 0) { |
|
dwTimeoutManualMode = dwTimeoutManualMode - 1; |
|
} |
|
// If this counter reaches 0 we automatically leave manual mode again (ToDo: Transmit message?) |
|
} |
|
#endif |
|
|
|
#if TIMEOUT_AUTO_MODE > 0 |
|
if (!isManualMode()) { |
|
if (dwTimeoutAutoKeepalive > 0) { |
|
dwTimeoutAutoKeepalive = dwTimeoutAutoKeepalive - 1; |
|
} |
|
if (dwTimeoutAutoKeepalive == 0) { |
|
// ToDo :Transmit a message about this transition |
|
} |
|
// If the dwTimeoutAutoKeepalive counter reaches 0 we automatically switch to manual mode (ToDo: Transmit message?) |
|
} |
|
#endif |
|
|
|
#if TIMEOUT_POWERON > 0 |
|
if (statePower) { |
|
if (dwPowerTimeout > 0) { |
|
dwPowerTimeout = dwPowerTimeout - 1; |
|
} |
|
if (dwPowerTimeout == 0) { |
|
switchOff(); |
|
// ToDo: Transmit timeout message additionally to power of status message sent by switchOff |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
/* LED status */ |
|
unsigned long milliDiff; |
|
|
|
if (currentMillis >= oldMillis) { |
|
milliDiff = currentMillis - oldMillis; |
|
} else { |
|
milliDiff = ((~0) - oldMillis) + currentMillis; |
|
} |
|
|
|
unsigned long int blinkDelay = getBlinkDelay(); |
|
|
|
if (blinkDelay == 0) { |
|
digitalWrite(LEDPORT, HIGH); |
|
led2State = false; |
|
} else if (blinkDelay == (~0)) { |
|
digitalWrite(LEDPORT, LOW); |
|
led2State = true; |
|
} else if (blinkDelay <= milliDiff) { |
|
oldMillis = currentMillis; |
|
digitalWrite(LEDPORT, led2State ? HIGH : LOW); |
|
led2State = !led2State; |
|
} |
|
} |