Files
lighthub/lighthub/main.cpp
Климов Андрей Николаевич b3db766b1a post-refactoring fix (Mercury)
2023-11-20 14:58:56 +03:00

3005 lines
88 KiB
C++

/* Copyright © 2017-2018 Andrey Klimov. All rights reserved.
*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Homepage: http://lazyhome.ru
GIT: https://github.com/anklimov/lighthub
e-mail anklimov@gmail.com
*/
#include "main.h"
#include "statusled.h"
#include "flashstream.h"
#include "config.h"
#if defined(__SAM3X8E__)
#include "TimerInterrupt_Generic.h"
#endif
#ifdef CRYPT
#include "RNG.h"
#endif
#ifdef SYSLOG_ENABLE
#include <Syslog.h>
#ifndef WIFI_ENABLE
EthernetUDP udpSyslogClient;
#else
WiFiUDP udpSyslogClient;
#endif
Syslog udpSyslog(udpSyslogClient, SYSLOG_PROTO_BSD);
static char syslogDeviceHostname[16];
#if defined(debugSerialPort) && !defined(NOSERIAL)
Streamlog debugSerial(&debugSerialPort,LOG_DEBUG,&udpSyslog);
Streamlog errorSerial(&debugSerialPort,LOG_ERROR,&udpSyslog,ledRED);
Streamlog infoSerial (&debugSerialPort,LOG_INFO,&udpSyslog);
#else
Streamlog debugSerial(NULL,LOG_DEBUG,&udpSyslog);
Streamlog errorSerial(NULL,LOG_ERROR,&udpSyslog,ledRED);
Streamlog infoSerial (NULL,LOG_INFO,&udpSyslog);
#endif
#else
#if defined(debugSerialPort) && !defined(NOSERIAL)
Streamlog debugSerial(&debugSerialPort,LOG_DEBUG);
Streamlog errorSerial(&debugSerialPort,LOG_ERROR, ledRED);
Streamlog infoSerial (&debugSerialPort,LOG_INFO);
#else
Streamlog debugSerial(NULL,LOG_DEBUG);
Streamlog errorSerial(NULL,LOG_ERROR, ledRED);
Streamlog infoSerial (NULL,LOG_INFO);
#endif
#endif
flashStream sysConfStream;
systemConfig sysConf(&sysConfStream);
extern long timer0_overflow_count;
#ifdef WIFI_ENABLE
WiFiClient ethClient;
#if not defined(WIFI_MANAGER_DISABLE)
WiFiManager wifiManager;
#endif
#else
#include <Dhcp.h>
EthernetClient ethClient;
#endif
#if defined(OTA)
#include <ArduinoOTA.h>
#endif
#ifdef MDNS_ENABLE
#ifndef WIFI_ENABLE
EthernetUDP mdnsUDP;
MDNS mdns(mdnsUDP);
#endif
#endif
StatusLED statusLED(ledRED);
lan_status lanStatus = INITIAL_STATE;
const char configserver[] PROGMEM = CONFIG_SERVER;
const char verval_P[] PROGMEM = QUOTE(PIO_SRC_REV);
#ifdef CRYPT
char cryptoKey[] = QUOTE(SHAREDSECRET);
#endif
#if defined(__SAM3X8E__)
UID UniqueID;
#endif
char *deviceName = NULL;
aJsonObject *topics = NULL;
aJsonObject *root = NULL;
aJsonObject *items = NULL;
aJsonObject *inputs = NULL;
aJsonObject *mqttArr = NULL;
#ifdef _modbus
aJsonObject *modbusObj = NULL;
#endif
#ifdef _owire
aJsonObject *owArr = NULL;
#endif
#ifdef _dmxout
aJsonObject *dmxArr = NULL;
#endif
#ifdef SYSLOG_ENABLE
bool syslogInitialized = false;
#endif
#ifdef WIFI_ENABLE
volatile uint32_t WiFiAwaitingTime =0;
#endif
volatile uint32_t timerPollingCheck = 0;
volatile uint32_t timerInputCheck = 0;
volatile uint32_t timerLanCheckTime = 0;
volatile uint32_t timerThermostatCheck = 0;
volatile uint32_t timerSensorCheck =0;
volatile unsigned long timerCount=0;
volatile int16_t timerNumber=-1;
volatile int8_t timerHandlerBusy=0;
volatile uint32_t cryptoSalt=0;
//uint32_t timerCtr=0;
aJsonObject *pollingItem = NULL;
bool owReady = false;
bool configOk = false; // At least once connected to MQTT
bool configLoaded = false;
bool initializedListeners = false;
uint8_t DHCP_failures = 0;
volatile int8_t ethernetIdleCount =0;
volatile int8_t configLocked = 0;
#if defined (_modbus)
ModbusMaster node;
#endif
PubSubClient mqttClient(ethClient);
bool wifiInitialized;
int8_t mqttErrorRate=0;
#if defined(__SAM3X8E__)
void watchdogSetup(void) {} //Do not remove - strong re-definition WDT Init for DUE
#endif
bool cleanConf(bool wait)
{
if (!root) return true;
bool clean = true;
if (wait)
{
debugSerial<<F("Unlocking config ...")<<endl;
uint32_t stamp=millis();
while (configLocked && !isTimeOver(stamp,millis(),10000))
{
//wdt_res();
cmdPoll();
#ifdef _owire
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
DMXCheck();
#endif
if (isNotRetainingStatus()) pollingLoop();
thermoLoop();
inputLoop(CHECK_INPUT);
yield();
}
if (configLocked)
{
errorSerial<<F("Not unlocked in 10s - continue ...")<<endl;
clean = false;
}
} //wait
debugSerial<<F("Stopping channels ...")<<endl;
timerHandlerBusy++;
//Stoping the channels
if (items)
{
aJsonObject * item = items->child;
while (item)
{
if (item->type == aJson_Array && (aJson.getArraySize(item)>0))
{
Item it(item->name);
if (it.isValid()) it.Stop();
yield();
}
item = item->next;
}
} else debugSerial<<F("nothing to stop")<<endl;
pollingItem = NULL;
debugSerial<<F("Stopped")<<endl;
delay(100);
#ifdef SYSLOG_ENABLE
syslogInitialized=false; //Garbage in memory
#endif
configLoaded=false;
debugSerial<<F("Deleting conf. RAM was:")<<freeRam();
aJson.deleteItem(root);
root = NULL;
inputs = NULL;
items = NULL;
topics = NULL;
mqttArr = NULL;
deviceName = NULL;
#ifdef _dmxout
dmxArr = NULL;
#endif
#ifdef _owire
owArr = NULL;
#endif
#ifdef _modbus
modbusObj = NULL;
#endif
debugSerial<<F(" is ")<<freeRam()<<endl;
configOk=false;
timerHandlerBusy--;
return clean;
}
bool isNotRetainingStatus() {
return (lanStatus != RETAINING_COLLECTING);
}
// Custom HTTP request handler
// Return values:
// 1 - work completed. Dont need to respond, just close socket
// 0 - no match continue with default lib behavior
// 401 - not authorized
// 400 - bad request
// 200 | CONTENT_TYPE - ok + content_type
// ... same sor other http codes
// if response != "" - put response in http answer
uint16_t httpHandler(Client& client, String request, uint8_t method, long contentLength, bool authorized, String& response )
{
#ifdef OTA
//String response = "";
debugSerial<<method<<F(" ")<<request<<endl;
if (method == HTTP_GET && request == (F("/")))
{
ArduinoOTA.sendHttpResponse(client,301,false); // Send only HTTP header, no close socket
client.println(
#ifdef REDIRECTION_URL
//Redirect to cloud PWA application
String(F("Location: " REDIRECTION_URL))
#else
String(F("Location: /index.html"))
#endif
+String(F("?mac="))+sysConf.getMACString()
+String(F("&ip="))+ toString( Ethernet.localIP())
+String(F("&port="))+ OTA_PORT
+String(F("&name="))+deviceName
);
client.println();
delay(100);
client.stop();
return 1;
}
if (method == HTTP_POST && request.startsWith(F("/item/")))
{
if (!authorized) return 401;
request.remove(0,6);
String body=client.readStringUntil('\n');
Item item((char*)request.c_str());
if (!item.isValid() || !item.Ctrl((char*) body.c_str())) return 400;
itemCmd ic;
ic.loadItem(&item,FLAG_COMMAND|FLAG_PARAMETERS);
char buf[32];
response=ic.toString(buf, sizeof(buf));
return 200 | HTTP_TEXT_PLAIN;
}
else if (method == HTTP_GET && request.startsWith(F("/item/")))
{
if (!authorized) return 401;
request.remove(0,6);
Item item((char*)request.c_str());
if (!item.isValid()) return 400;
if (item.itemType == CH_GROUP)
{if (item.isActive()) item.setCmd(CMD_ON); else item.setCmd(CMD_OFF);}
itemCmd ic;
ic.loadItem(&item,FLAG_COMMAND|FLAG_PARAMETERS);
char buf[32];
response=ic.toString(buf, sizeof(buf));
return 200 | HTTP_TEXT_PLAIN;
}
else if (method == HTTP_GET && request.startsWith(F("/ram/")))
{
if (!authorized) return 401;
aJsonObject * dumpObject;
request.remove(0,5);
if (request == "." && items) dumpObject = items;
else if (request != "" && items)
{
dumpObject = aJson.getObjectItem(items, request.c_str());
if (!dumpObject) return 404;
}
else dumpObject = root;
ArduinoOTA.sendHttpResponse(client,200 | HTTP_TEXT_JSON,false); // Send only HTTP header, no close socket
client.println();
// char* outBuf = (char*) malloc(MAX_JSON_CONF_SIZE); /* XXX: Dynamic size. */
// if (outBuf == NULL) return 500;
aJsonStream socketStream = aJsonStream(&client);
//aJsonStringStream stringStream(NULL, outBuf, MAX_JSON_CONF_SIZE);
aJson.print(dumpObject, &socketStream);
delay(100);
client.stop();
return 1;
//size_t res = sysConfStream.write((byte*) outBuf,len);
//free (outBuf);
}
else if (method == HTTP_POST && request.startsWith(F("/command/")))
{
int result = 400;
if (!authorized) return 401;
request.remove(0,9);
String body=client.readStringUntil('\n');
request+=" ";
request+=body;
debugSerial<<F("Cmd: ")<<request<<endl;
if (request.equalsIgnoreCase(F("reboot "))) ArduinoOTA.sendHttpResponse(client,200);
const char* res=request.c_str();
result = cmd_parse((char*) res);
if (! result) return 404;
return result;
}
else if (method == HTTP_POST && request.startsWith(F("/config.json")))
{
sysConf.setLoadHTTPConfig(false);
infoSerial<<(F("Config changed locally, portal disabled"))<<endl;
sysConf.setETAG("");
return 0;
}
#endif
return 0; //Unknown
}
int inTopic (char * topic, topicType tt)
{
char buf[MQTT_TOPIC_LENGTH + 1];
int pfxlen;
int intopic;
setTopic(buf,sizeof(buf)-1,tt);
pfxlen = strlen(buf);
intopic = strncmp(topic, buf, pfxlen);
// debugSerial<<buf<<" "<<pfxlen<<" "<<intopic<<endl;
if (!intopic) return pfxlen;
return 0;
}
void mqttCallback(char *topic, byte *payload, unsigned int length)
{
if (!payload || !length) {debugSerial<<F("\n")<<F("Empty: [")<<topic<<F("]")<<endl;return;}
payload[length] = 0;
int fr = freeRam();
debugSerial<<F("\n")<<fr<<F(":[")<<topic<<F("] ");
if (fr < 250+MQTT_TOPIC_LENGTH) {
errorSerial<<F("OutOfMemory!")<<endl;
return;// -2;
}
statusLED.flash(ledBLUE);
debugSerial<<F("\"")<<(char*)payload<<F("\"")<<endl;
short pfxlen = 0;
char * itemName = NULL;
char * subItem = NULL;
char savedTopic[MQTT_TOPIC_LENGTH] = "";
// in Retaining status - trying to restore previous state from retained output topic. Retained input topics are not relevant.
if (lanStatus == RETAINING_COLLECTING)
{
pfxlen=inTopic(topic,T_OUT);
if (!pfxlen) // There is not status topic
{
if (mqttClient.isRetained())
{
//pfxlen=inTopic(topic,T_BCST); //Dont delete bcast topics (just skip on restore retaining)
//if (!pfxlen) pfxlen = inTopic(topic,T_DEV);
pfxlen = inTopic(topic,T_DEV);
if (!pfxlen) return; // Not command topic
if (strrchr(topic,'$')) return;
debugSerial<<F("CleanUp retained topic ")<<topic<<endl;
mqttClient.deleteTopic(topic);
}
return;
}
}
else
{
pfxlen=inTopic(topic,T_DEV);
if (!pfxlen) pfxlen = inTopic(topic,T_BCST);
else // Personal device topic
strncpy(savedTopic,topic,sizeof(savedTopic)-1);
}
if (!pfxlen) {
debugSerial<<F("Skipping..")<<endl;
return;// -3;
}
itemName=topic+pfxlen;
// debugSerial<<itemName<<endl;
if(!strcmp_P(itemName,CMDTOPIC_P) && payload && (strlen((char*) payload)>1)) {
mqttClient.deleteTopic(topic);
cmd_parse((char *)payload);
return;// -4;
}
//if (itemName[0]=='$') return;// -6; //Skipping homie stuff
if (strrchr(topic,'$')) return;
Item item(itemName);
if (item.isValid() && (item.Ctrl((char *)payload)>0) && savedTopic[0] && lanStatus != RETAINING_COLLECTING)
//if (lanStatus != RETAINING_COLLECTING && (mqttClient.isRetained()))
{
debugSerial<<F("Complete. Remove topic ")<<savedTopic<<endl;
mqttClient.deleteTopic(savedTopic);
}
return;// -7;
}
void printMACAddress() {
//macAddress * mac = sysConf.getMAC();
infoSerial<<F("MAC:");
for (byte i = 0; i < 6; i++)
{
if (sysConf.mac[i]<16) infoSerial<<"0";
(i < 5) ?infoSerial<<_HEX(sysConf.mac[i])<<F(":"):infoSerial<<_HEX(sysConf.mac[i])<<endl;
}
}
char* getStringFromConfig(aJsonObject * a, int i)
{
aJsonObject * element = NULL;
if (!a) return NULL;
if (a->type == aJson_Array)
element = aJson.getArrayItem(a, i);
// TODO - human readable JSON objects as alias
if (element && element->type == aJson_String) return element->valuestring;
return NULL;
}
char* getStringFromConfig(aJsonObject * a, char * name)
{
aJsonObject * element = NULL;
if (!a) return NULL;
if (a->type == aJson_Object)
element = aJson.getObjectItem(a, name);
if (element && element->type == aJson_String) return element->valuestring;
return NULL;
}
#ifdef OTA
const char defaultPassword[] PROGMEM = "password";
void setupOTA(void)
{ char passwordBuf[16];
if (!sysConf.getOTApwd(passwordBuf, sizeof(passwordBuf)))
{
strcpy_P(passwordBuf,defaultPassword);
errorSerial<<F("DEFAULT password for OTA API. Use otapwd command to set")<<endl;
}
debugSerial<<passwordBuf<<endl;
ArduinoOTA.begin(Ethernet.localIP(), "Lighthub", passwordBuf, InternalStorage, sysConfStream);
ArduinoOTA.setCustomHandler(httpHandler);
infoSerial<<F("OTA initialized\n");
}
#else
void setupOTA(void) {};
#endif
void setupSyslog()
{
#ifdef SYSLOG_ENABLE
int syslogPort = 514;
short n = 0;
aJsonObject *udpSyslogArr = NULL;
if (syslogInitialized) return;
if (lanStatus<HAVE_IP_ADDRESS) return;
if (!root) return;
// udpSyslogClient.begin(SYSLOG_LOCAL_SOCKET);
udpSyslogArr = aJson.getObjectItem(root, "syslog");
if (udpSyslogArr && (n = aJson.getArraySize(udpSyslogArr))) {
char *syslogServer = getStringFromConfig(udpSyslogArr, 0);
if (n>1) syslogPort = aJson.getArrayItem(udpSyslogArr, 1)->valueint;
_inet_ntoa_r(Ethernet.localIP(),syslogDeviceHostname,sizeof(syslogDeviceHostname));
infoSerial<<F("Syslog params:")<<syslogServer<<":"<<syslogPort<<":"<<syslogDeviceHostname<<endl;
udpSyslogClient.begin(SYSLOG_LOCAL_SOCKET);
udpSyslog.server(syslogServer, syslogPort);
udpSyslog.deviceHostname(syslogDeviceHostname);
if (mqttArr) deviceName = getStringFromConfig(mqttArr, 0);
if (deviceName) udpSyslog.appName(deviceName);
else udpSyslog.appName(lighthub);
udpSyslog.defaultPriority(LOG_KERN);
syslogInitialized=true;
infoSerial<<F("UDP Syslog initialized.\n");
}
#endif
}
lan_status lanLoop() {
#ifdef NOETHER
lanStatus=DO_NOTHING;//-14;
#endif
switch (lanStatus) {
case INITIAL_STATE:
statusLED.set(ledRED|((configLoaded)?ledBLINK:0));
#if defined(WIFI_ENABLE)
onInitialStateInitLAN(); // Moves state to AWAITING_ADDRESS or HAVE_IP_ADDRESS
#else
if (Ethernet.linkStatus() != LinkOFF) onInitialStateInitLAN(); // Moves state to AWAITING_ADDRESS or HAVE_IP_ADDRESS
#endif
break;
case AWAITING_ADDRESS:
#if defined(WIFI_ENABLE)
if (WiFi.status() == WL_CONNECTED)
{
infoSerial<<F("WiFi connected. IP address: ")<<WiFi.localIP()<<endl;
wifiInitialized = true;
lanStatus = HAVE_IP_ADDRESS;
#ifdef MDNS_ENABLE
char mdnsName[32] = "LightHub";
SetBytes(sysConf.mac+4,2,mdnsName+8);
mdnsName[8+4]='\0';
if (!MDNS.begin(mdnsName))
errorSerial<<F("Error setting up MDNS responder!")<<endl;
else infoSerial<<F("mDNS responder started: ")<<mdnsName<<F(".local")<<endl;
MDNS.addService("http", "tcp", OTA_PORT);
#endif
}
else
// if (millis()>WiFiAwaitingTime)
if (isTimeOver(WiFiAwaitingTime,millis(),WIFI_TIMEOUT))
{
errorSerial<<F("\nProblem with WiFi!");
WiFi.reconnect();
return lanStatus = DO_REINIT;
}
#else
lanStatus = HAVE_IP_ADDRESS;
#endif
break;
case HAVE_IP_ADDRESS:
if (!initializedListeners)
{
setupSyslog();
setupOTA();
#ifdef _artnet
if (artnet) artnet->begin();
#endif
#ifdef IPMODBUS
setupIpmodbus();
#endif
initializedListeners = true;
}
lanStatus = LIBS_INITIALIZED;
break;
case LIBS_INITIALIZED:
statusLED.set(ledRED|ledGREEN|((configLoaded)?ledBLINK:0));
if (configLocked) return LIBS_INITIALIZED;
if (sysConf.getLoadHTTPConfig())
{
if (!configOk)
{
if (loadConfigFromHttp()==200) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
else if (configLoaded) {
infoSerial<<F("Continue with previously loaded config")<<endl;
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
}
else if (Ethernet.localIP()) lanStatus = DO_READ_RE_CONFIG;
else lanStatus = DO_REINIT; //Load from NVRAM
}
else
{
infoSerial<<F("Config is valid")<<endl;
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
}
} else
{
if (configLoaded)
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
else
lanStatus = DO_READ_RE_CONFIG;
infoSerial<<F("Loading config from portal disabled. use get ON to enable")<<endl;
}
break;
case IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER:
wdt_res();
statusLED.set(ledRED|ledGREEN|((configLoaded)?ledBLINK:0));
if (!mqttArr || ((aJson.getArraySize(mqttArr)) < 2))
{
infoSerial<<F("No MQTT configured")<<endl;
lanStatus=OPERATION_NO_MQTT;
}
else ip_ready_config_loaded_connecting_to_broker();
break;
case RETAINING_COLLECTING:
//if (millis() > timerLanCheckTime)
if (isTimeOver(timerLanCheckTime,millis(),TIMEOUT_RETAIN))
{
char buf[MQTT_TOPIC_LENGTH+1];
//Unsubscribe from status topics..
//strncpy_P(buf, outprefix, sizeof(buf));
setTopic(buf,sizeof(buf),T_OUT);
strncat(buf, "+/+/#", sizeof(buf)); // Subscribing only on separated command/parameters topics
mqttClient.unsubscribe(buf);
onMQTTConnect();
#ifdef CRYPT
//setTopic(buf,sizeof(buf),T_OUT);
strncpy(buf, "+/+/$salt", sizeof(buf)); // Only on separated cmd/val topics
mqttClient.subscribe(buf);
#endif
lanStatus = OPERATION;//3;
infoSerial<<F("Accepting commands...\n");
}
break;
case OPERATION:
statusLED.set(ledGREEN|((configLoaded)?ledBLINK:0));
if (!mqttClient.connected()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
break;
case DO_REINIT: // Pause and re-init LAN
//if (mqttClient.connected()) mqttClient.disconnect(); // Hmm hungs then cable disconnected
// problem here - if no sockets - DHCP will failed. finally (())
timerLanCheckTime = millis();// + 5000;
lanStatus = REINIT;
statusLED.set(ledRED|((configLoaded)?ledBLINK:0));
break;
case REINIT: // Pause and re-init LAN
//if (millis() > timerLanCheckTime)
if (isTimeOver(timerLanCheckTime,millis(),TIMEOUT_REINIT))
{
lanStatus = INITIAL_STATE;
}
break;
case DO_RECONNECT: // Pause and re-connect MQTT
if (mqttClient.connected()) mqttClient.disconnect();
timerLanCheckTime = millis();// + 5000;
lanStatus = RECONNECT;
/*
#if defined(WIFI_ENABLE)
if (WiFi.status() != WL_CONNECTED)
{
infoSerial<<"Reconnecting WiFi"<<endl;
lanStatus = INITIAL_STATE;
WiFi.disconnect();
WiFi.reconnect();
}
#endif
*/
break;
case RECONNECT:
//if (millis() > timerLanCheckTime)
if (isTimeOver(timerLanCheckTime,millis(),TIMEOUT_RECONNECT))
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
break;
case DO_READ_RE_CONFIG:
timerLanCheckTime = millis();
lanStatus=READ_RE_CONFIG;
break;
case READ_RE_CONFIG: // Restore config from FLASH, re-init LAN
if (loadConfigFromEEPROM()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
else
if (isTimeOver(timerLanCheckTime,millis(),TIMEOUT_RELOAD))
{
errorSerial<<F("30s EEPROM is not reloaded. Reboot");
softRebootFunc();
}
break;
case DO_NOTHING:
case OPERATION_NO_MQTT:
;
}
{
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) || defined (NRF5)
wdt_dis();
if (lanStatus >= HAVE_IP_ADDRESS)
{
int etherStatus = 0;
if (!DHCP_failures) etherStatus = Ethernet.maintain();
#ifndef Wiz5500
#define NO_LINK 5
if (Ethernet.linkStatus() == LinkOFF) etherStatus = NO_LINK;
#endif
switch (etherStatus) {
case NO_LINK:
errorSerial<<F("\nNo link")<<endl;
lanStatus = DO_REINIT;
break;
case DHCP_CHECK_RENEW_FAIL:
errorSerial<<F("Error: renewed fail")<<endl;
lanStatus = DO_REINIT;
break;
case DHCP_CHECK_RENEW_OK:
infoSerial<<F("Renewed success. IP address:");
printIPAddress(Ethernet.localIP());
infoSerial<<endl;
break;
case DHCP_CHECK_REBIND_FAIL:
errorSerial<<F("Error: rebind fail")<<endl;
/// if (mqttClient.connected()) mqttClient.disconnect(); ??
//timerLanCheckTime = millis();// + 1000;
lanStatus = DO_REINIT;
break;
case DHCP_CHECK_REBIND_OK:
infoSerial<<F("Rebind success. IP address:");
printIPAddress(Ethernet.localIP());
infoSerial<<endl;
break;
default:
break;
}
}
wdt_en();
#endif
}
return lanStatus;
}
void onMQTTConnect(){
char topic[64] = "";
char buf[128] = "";
// High level homie topics publishing
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, state_P, sizeof(topic)-1);
strncpy_P(buf, ready_P, sizeof(buf)-1);
mqttClient.publish(topic,buf,true);
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, name_P, sizeof(topic)-1);
strncpy_P(buf, nameval_P, sizeof(buf)-1);
strncat_P(buf,(verval_P),sizeof(buf)-1);
mqttClient.publish(topic,buf,true);
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, stats_P, sizeof(topic)-1);
strncpy_P(buf, statsval_P, sizeof(buf)-1);
mqttClient.publish(topic,buf,true);
#ifdef CRYPT
RNG.rand((uint8_t *) &cryptoSalt,sizeof(cryptoSalt));
setTopic(topic,sizeof(topic),T_DEV);
//strncat_P(topic, stats_P, sizeof(topic)-1);
//strncat(topic, "/", sizeof(topic));
strncat_P(topic, salt_P, sizeof(topic)-1);
printUlongValueToStr(buf, cryptoSalt);
mqttClient.publish(topic,buf,true);
#endif
#ifndef NO_HOMIE
// strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, homie_P, sizeof(topic)-1);
strncpy_P(buf, homiever_P, sizeof(buf)-1);
mqttClient.publish(topic,buf,true);
configLocked++;
if (items) {
char datatype[32]="\0";
char format [64]="\0";
aJsonObject * item = items->child;
int nodesLen=0;
while (items && item) {
if (item->type == aJson_Array && aJson.getArraySize(item)>0) {
/// strncat(buf,item->name,sizeof(buf));
/// strncat(buf,",",sizeof(buf));
nodesLen+=strlen(item->name)+1;
switch ( aJson.getArrayItem(item, I_TYPE)->valueint) {
case CH_THERMO:
strncpy_P(datatype,float_P,sizeof(datatype)-1);
format[0]=0;
break;
case CH_RELAY:
case CH_GROUP:
strncpy_P(datatype,int_P,sizeof(datatype)-1);
strncpy_P(format,intformat_P,sizeof(format)-1);
break;
case CH_RGBW:
case CH_RGBWW:
case CH_RGB:
strncpy_P(datatype,color_P,sizeof(datatype)-1);
strncpy_P(format,hsv_P,sizeof(format)-1);
break;
case CH_DIMMER:
case CH_MODBUS:
case CH_PWM:
case CH_VCTEMP:
case CH_VC:
strncpy_P(datatype,int_P,sizeof(datatype)-1);
strncpy_P(format,intformat_P,sizeof(format)-1);
break;
} //switch
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic)-1);
strncat(topic,"/cmd/",sizeof(topic)-1);
strncat_P(topic,datatype_P,sizeof(topic));
mqttClient.publish_P(topic,enum_P,true);
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic)-1);
strncat(topic,"/cmd/",sizeof(topic)-1);
strncat_P(topic,format_P,sizeof(topic));
mqttClient.publish_P(topic,enumformat_P,true);
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic)-1);
strncat(topic,"/set/",sizeof(topic)-1);
strncat_P(topic,datatype_P,sizeof(topic)-1);
mqttClient.publish(topic,datatype,true);
if (format[0])
{
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic)-1);
strncat(topic,"/set/",sizeof(topic));
strncat_P(topic,format_P,sizeof(topic)-1);
mqttClient.publish(topic,format,true);
}
} //if
yield();
item = item->next;
} //while
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, nodes_P, sizeof(topic)-1);
//debugSerial<<topic<<"->> "<<nodesLen<<endl;
mqttClient.beginPublish(topic,nodesLen-1,true);
item = items->child;
while (items && item)
{
if (item->type == aJson_Array && aJson.getArraySize(item)>0)
{
mqttClient.write((uint8_t*)item->name,strlen(item->name));
if (item->next) mqttClient.write(',');
}
yield();
item = item->next;
}
mqttClient.endPublish();
}
configLocked--;
#endif
}
void ip_ready_config_loaded_connecting_to_broker() {
int port = 1883;
char empty = 0;
short n = 0;
char *user = &empty;
char passwordBuf[16] = "";
char *password = passwordBuf;
if (mqttClient.connected())
{
//mqttClient.setKeepAlive(10);
lanStatus = RETAINING_COLLECTING;
return;
}
if (!mqttArr || ((n = aJson.getArraySize(mqttArr)) < 2)) //At least device name and broker IP must be configured
{
errorSerial<<F("At least device name and broker IP must be configured")<<endl;
lanStatus = DO_READ_RE_CONFIG;
return;
}
deviceName = getStringFromConfig(mqttArr, 0);
if (!deviceName) deviceName = (char*) lighthub;
infoSerial<<F("Device Name:")<<deviceName<<endl;
#if defined (MDNS_ENABLE) && ! defined (WIFI_ENABLE)
if (!mdns.setName(deviceName))
errorSerial<<("Error updating MDNS name!")<<endl;
else infoSerial<<F("mDNS name updated: ")<<deviceName<<F(".local")<<endl;
#endif
//debugSerial<<F("N:")<<n<<endl;
char *servername = getStringFromConfig(mqttArr, 1);
if (n >= 3) port = aJson.getArrayItem(mqttArr, 2)->valueint;
if (n >= 4) user = getStringFromConfig(mqttArr, 3);
//if (!loadFlash(OFFSET_MQTT_PWD, passwordBuf, sizeof(passwordBuf)) && (n >= 5))
if (!sysConf.getMQTTpwd(passwordBuf, sizeof(passwordBuf)) && (n >= 5))
{
password = getStringFromConfig(mqttArr, 4);
infoSerial<<F("Using MQTT password from config")<<endl;
}
mqttClient.setServer(servername, port);
mqttClient.setCallback(mqttCallback);
char willMessage[16];
char willTopic[32];
strncpy_P(willMessage,disconnected_P,sizeof(willMessage));
// strncpy_P(willTopic, outprefix, sizeof(willTopic));
setTopic(willTopic,sizeof(willTopic),T_DEV);
strncat_P(willTopic, state_P, sizeof(willTopic));
infoSerial<<F("\nAttempting MQTT connection to ")<<servername<<F(":")<<port<<F(" user:")<<user<<F(" ...");
if (!strlen(user))
{
user = NULL;
password= NULL;
}
// wdt_dis(); //potential unsafe for ethernetIdle(), but needed to avoid cyclic reboot if mosquitto out of order
if (mqttClient.connect(deviceName, user, password,willTopic,MQTTQOS1,true,willMessage))
{
mqttErrorRate = 0;
infoSerial<<F("connected as ")<<deviceName <<endl;
configOk = true;
// ... Temporary subscribe to status topic
char buf[MQTT_TOPIC_LENGTH+1];
setTopic(buf,sizeof(buf),T_OUT);
strncat(buf, "+/+/#", sizeof(buf)); // Only on separated cmd/val topics
mqttClient.subscribe(buf);
//Subscribing for command topics
//strncpy_P(buf, inprefix, sizeof(buf));
setTopic(buf,sizeof(buf),T_BCST);
strncat(buf, "#", sizeof(buf));
debugSerial.println(buf);
mqttClient.subscribe(buf);
setTopic(buf,sizeof(buf),T_DEV);
strncat(buf, "#", sizeof(buf));
debugSerial.println(buf);
mqttClient.subscribe(buf);
//onMQTTConnect();
// if (_once) {DMXput(); _once=0;}
lanStatus = RETAINING_COLLECTING;//4;
timerLanCheckTime = millis();// + 5000;
infoSerial<<F("Awaiting for retained topics")<<endl;
} else
{
errorSerial<<F("failed, rc=")<<mqttClient.state()<<F(" try again in 5 seconds")<<endl;
timerLanCheckTime = millis();// + 5000;
#ifdef RESTART_LAN_ON_MQTT_ERRORS
mqttErrorRate++;
if(mqttErrorRate>50){
errorSerial<<F("Too many MQTT connection errors. Restart LAN")<<endl;
mqttErrorRate=0;
#ifdef RESET_PIN
resetHard();
#endif
lanStatus=DO_REINIT;// GO INITIAL_STATE;
return;
}
#endif
lanStatus = DO_RECONNECT;//12;
}
}
void onInitialStateInitLAN() {
#if defined(WIFI_ENABLE)
#if defined(WIFI_MANAGER_DISABLE)
if(WiFi.status() != WL_CONNECTED) {
WiFi.mode(WIFI_STA); // ESP 32 - WiFi.disconnect(); instead
infoSerial<<F("WIFI AP/Password:")<<QUOTE(ESP_WIFI_AP)<<F("/")<<QUOTE(ESP_WIFI_PWD)<<endl;
#ifndef ARDUINO_ARCH_ESP32
wifi_set_macaddr(STATION_IF,sysConf.mac); //ESP32 to check
#endif
WiFi.begin(QUOTE(ESP_WIFI_AP), QUOTE(ESP_WIFI_PWD));
// int wifi_connection_wait = 10000;
// while (WiFi.status() != WL_CONNECTED && wifi_connection_wait > 0) {
// delay(500);
// wifi_connection_wait -= 500;
// debugSerial<<".";
// yield();
// }
// wifiInitialized = true; //???
}
#endif
lanStatus = AWAITING_ADDRESS;
WiFiAwaitingTime = millis();// + 60000L;
return;
/*
if (WiFi.status() == WL_CONNECTED) {
infoSerial<<F("WiFi connected. IP address: ")<<WiFi.localIP()<<endl;
wifiInitialized = true;
lanStatus = HAVE_IP_ADDRESS;//1;
//setupOTA();
} else
{
errorSerial<<F("\nProblem with WiFi!");
lanStatus = DO_REINIT;
//timerLanCheckTime = millis() + DHCP_RETRY_INTERVAL;
}
*/
#else // Ethernet connection
IPAddress ip, dns, gw, mask;
int res = 1;
infoSerial<<F("Starting lan")<<endl;
bool loadAddressRes = sysConf.getIP(ip);
if ( loadAddressRes &&
(!sysConf.getDHCPfallback() || (DHCP_failures >= DHCP_ATTEMPTS_FALLBACK))
)
{
infoSerial<<F("Loaded from flash IP:");
printIPAddress(ip);
if (sysConf.getDNS(dns)) {
infoSerial<<F(" DNS:");
printIPAddress(dns);
if (sysConf.getGW(gw)) {
infoSerial<<F(" GW:");
printIPAddress(gw);
if (sysConf.getMask(mask)) {
infoSerial<<F(" MASK:");
printIPAddress(mask);
Ethernet.begin(sysConf.mac, ip, dns, gw, mask);
} else Ethernet.begin(sysConf.mac, ip, dns, gw);
} else Ethernet.begin(sysConf.mac, ip, dns);
} else Ethernet.begin(sysConf.mac, ip);
infoSerial<<endl;
lanStatus = HAVE_IP_ADDRESS;
}
else {
infoSerial<<F("\nuses DHCP\n");
wdt_dis();
#if defined(ARDUINO_ARCH_STM32)
res = Ethernet.begin(sysConf.mac);
#else
res = Ethernet.begin(sysConf.mac, 12000);
#endif
wdt_en();
wdt_res();
if (res == 0) {
errorSerial<<F("Failed to configure Ethernet using DHCP. You can set ip manually!")<<F("'ip [ip[,dns[,gw[,subnet]]]]' - set static IP\n");
DHCP_failures++;
lanStatus = DO_REINIT;//-10;
//timerLanCheckTime = millis();// + DHCP_RETRY_INTERVAL;
#ifdef RESET_PIN
resetHard();
#endif
} else {
infoSerial<<F("Got IP address:");
ip=Ethernet.localIP();
printIPAddress(ip);
infoSerial<<endl;
sysConf.setIP(ip);
dns=Ethernet.dnsServerIP();
sysConf.setDNS(dns);
gw=Ethernet.gatewayIP();
sysConf.setGW(gw);
mask=Ethernet.subnetMask();
sysConf.setMask(mask);
sysConf.setDHCPfallback(true);
DHCP_failures = 0;
lanStatus = HAVE_IP_ADDRESS;
}
}//DHCP
#ifdef MDNS_ENABLE
char mdnsName[32] = "LightHub";
SetBytes(sysConf.mac+4,2,mdnsName+8);
if(!mdns.begin(Ethernet.localIP(), mdnsName))
errorSerial<<F("Error setting up MDNS responder!")<<endl;
else infoSerial<<F("mDNS responder started.")<<endl;
mdns.removeAllServiceRecords();
char txtRecord[32] = "\x10mac=";
SetBytes(sysConf.mac,6,txtRecord+5);
strncat(mdnsName,"._http",sizeof(mdnsName));
if (!mdns.addServiceRecord(mdnsName, OTA_PORT, MDNSServiceTCP, txtRecord))
errorSerial<<("Error setting up service record!")<<endl;
else infoSerial<<F("Service record: ")<<mdnsName<<F(".local")<<endl;
#endif
#endif //Ethernet
}
void resetHard() {
#ifdef RESET_PIN
infoSerial<<F("Reset Arduino with digital pin ");
infoSerial<<QUOTE(RESET_PIN);
delay(500);
pinMode(RESET_PIN, OUTPUT);
digitalWrite(RESET_PIN,LOW);
delay(500);
digitalWrite(RESET_PIN,HIGH);
delay(500);
#endif
}
int cmdFunctionHelp(int arg_cnt, char **args)
{
printFirmwareVersionAndBuildOptions();
printCurentLanConfig();
// printFreeRam();
infoSerial<<F("\nUse these commands: 'help' - this text\n"
"'mac de:ad:be:ef:fe:00' set and store MAC-address in EEPROM\n"
"'ip [ip[,dns[,gw[,subnet]]]]' - set static IP\n"
"'save' - save current config in NVRAM; ON|OFF - enable/disable autosave\n"
"'get' [config addr]' - get config from pre-configured URL and store addr, ON|OFF - enable/disable download on startup\n"
"'load' - load config from NVRAM\n"
"'pwd' - define and store MQTT password\n"
"'otapwd' - define and store HTTP API password\n"
"'log [serial_loglevel] [udp_loglevel]' - define log level (0..7)\n"
"'kill' - test watchdog\n"
"'clear' - clear EEPROM\n"
#ifndef OWIRE_DISABLE
"'search' - search 1-wire dev\n"
#endif
"'reboot' - reboot controller");
return 200;
}
void printCurentLanConfig() {
infoSerial << F("Current LAN config(ip,dns,gw,subnet):");
printIPAddress(Ethernet.localIP());
#if not defined(ESP8266) and not defined(ESP32)
printIPAddress(Ethernet.dnsServerIP());
#endif
printIPAddress(Ethernet.gatewayIP());
printIPAddress(Ethernet.subnetMask());
}
int cmdFunctionKill(int arg_cnt, char **args) {
for (byte i = 1; i < 20; i++) {
delay(1000);
infoSerial<<i;
};
return 500;
}
int cmdFunctionReboot(int arg_cnt, char **args) {
infoSerial<<F("Soft rebooting...");
softRebootFunc();
return 500;
}
#ifndef OWIRE_DISABLE
int cmdFunctionSearch(int arg_cnt, char **args) {
//infoSerial<<F("searching");
owSearch();
return 200;
}
#endif
void applyConfig() {
if (!root || configLocked) return;
configLocked++;
infoSerial<<F("Applying config")<<endl;
items = aJson.getObjectItem(root, "items");
topics = aJson.getObjectItem(root, "topics");
inputs = aJson.getObjectItem(root, "in");
mqttArr = aJson.getObjectItem(root, "mqtt");
setupSyslog();
#ifdef _dmxin
int itemsCount;
dmxArr = aJson.getObjectItem(root, "dmxin");
if (dmxArr && (itemsCount = aJson.getArraySize(dmxArr))) {
DMXinSetup(itemsCount * 4);
infoSerial<<F("DMX in started. Channels:")<<itemsCount * 4<<endl;
}
#endif
#ifdef _dmxout
int maxChannels;
short numParams;
aJsonObject *dmxoutArr = aJson.getObjectItem(root, "dmx");
if (dmxoutArr && (numParams=aJson.getArraySize(dmxoutArr)) >=1 ) {
maxChannels = aJson.getArrayItem(dmxoutArr, numParams-1)->valueint;
#ifdef _artnet
aJsonObject *artnetArr = aJson.getObjectItem(root, "artnet");
if (artnetArr)
{
uint8_t artnetMinCh = 1;
uint8_t artnetMaxCh = maxChannels;
short artnetNumParams;
if (artnetNumParams=aJson.getArraySize(artnetArr)>=2)
{
artnetMinCh = aJson.getArrayItem(artnetArr, 0)->valueint;
if (artnetMinCh<1) artnetMinCh = 1;
artnetMaxCh = aJson.getArrayItem(artnetArr, 1)->valueint;
if (artnetMaxCh>maxChannels) artnetMaxCh=maxChannels;
}
infoSerial<<F("Artnet start. Channels:")<<artnetMinCh<<F("-")<<artnetMaxCh<<endl;
artnetSetup();
artnetSetChans(artnetMinCh,artnetMaxCh);
//artnetInitialized=true;
}
#endif
DMXoutSetup(maxChannels);
infoSerial<<F("DMX out started. Channels: ")<<maxChannels<<endl;
debugSerial<<F("Free:")<<freeRam()<<endl;
}
#endif
#ifdef _modbus
modbusObj = aJson.getObjectItem(root, "modbus");
#endif
#ifdef _owire
owArr = aJson.getObjectItem(root, "ow");
if (owArr && !owReady) {
//aJsonObject *item = owArr->child;
owReady = owSetup();
if (owReady) infoSerial<<F("One wire Ready\n");
}
#endif
// Digital output related Items initialization
pollingItem=NULL;
if (items) {
aJsonObject * item = items->child;
while (items && item)
if (item->type == aJson_Array && aJson.getArraySize(item)>1) {
Item it(item);
if (it.isValid() && !it.Setup()) {
//Legacy Setup
short inverse = 0;
int pin=it.getArg();
if (pin<0) {pin=-pin; inverse = 1;}
int cmd = it.getCmd();
switch (it.itemType) {
case CH_THERMO:
if (cmd<1) it.setCmd(CMD_OFF);
it.setFlag(FLAG_COMMAND);
if (it.itemVal) it.setFlag(FLAG_PARAMETERS);
if (isProtectedPin(pin)) {errorSerial<<F("pin protected: ")<<pin<<endl; break;}
pinMode(pin, OUTPUT);
digitalWrite(pin, false); //Initially, all thermostates are LOW (OFF for electho heaters, open for water NO)
debugSerial<<F("Thermo:")<<pin<<F("=LOW")<<F(";");
break;
case CH_RELAY:
{
int k;
pinMode(pin, OUTPUT);
if (isProtectedPin(pin)) digitalWrite (pin,LOW);
else
{
if (inverse)
digitalWrite(pin, k = ((cmd == CMD_ON) ? LOW : HIGH));
else
digitalWrite(pin, k = ((cmd == CMD_ON) ? HIGH : LOW));
debugSerial<<F("Pin:")<<pin<<F("=")<<k<<F(";");
}
}
break;
} //switch
} //isValid
yield();
item = item->next;
} //if
pollingItem = items->child;
}
debugSerial<<endl;
inputSetup();
printConfigSummary();
configLoaded=true;
if (ethClient.connected())
{
ethClient.stop(); //Refresh MQTT connection
lanStatus=IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
}
if (lanStatus == OPERATION_NO_MQTT) lanStatus=IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
configLocked--;
}
void printConfigSummary() {
infoSerial<<F("\nConfigured:")<<F("\nitems ");
printBool(items);
infoSerial<<F("\ninputs ");
printBool(inputs);
#ifndef MODBUS_DISABLE
infoSerial<<F("\nmodbus v1 (+)");
// printBool(modbusObj);
#endif
#ifndef MBUS_DISABLE
infoSerial<<F("\nmodbus v2");
printBool(modbusObj);
#endif
infoSerial<<F("\nmqtt ");
printBool(mqttArr);
#ifdef _owire
infoSerial<<F("\n1-wire ");
printBool(owArr);
#endif
#ifdef SYSLOG_ENABLE
infoSerial<<F("\nSyslog ");
printBool(syslogInitialized);
#endif
#ifdef _artnet
infoSerial<<F("\nArtnet ");
printBool(artnet);
#endif
infoSerial << endl;
infoSerial<<F("RAM=")<<freeRam()<<endl;
}
int cmdFunctionLoad(int arg_cnt, char **args) {
/*
if (!loadConfigFromEEPROM())
{
lanStatus=DO_REINIT;
return 500;
}
*/
lanStatus=DO_READ_RE_CONFIG;
return 200;
}
int loadConfigFromEEPROM()
{
if (configLocked) return 0;
configLocked++;
infoSerial<<F("Loading Config from EEPROM")<<endl;
#if defined(FS_STORAGE)
sysConfStream.open("/config.json",'r');
#else
sysConfStream.open(FN_CONFIG_JSON,'r');
#endif
if (sysConfStream.peek() == '{') {
debugSerial<<F("JSON detected")<<endl;
aJsonStream as = aJsonStream(&sysConfStream);
cleanConf(false);
root = aJson.parse(&as);
sysConfStream.close();
if (!root) {
errorSerial<<F("load failed")<<endl;
sysConf.setETAG("");
// sysConfStream.close();
configLocked--;
return 0;
}
infoSerial<<F("Loaded from EEPROM")<<endl;
configLocked--;
applyConfig();
sysConf.loadETAG();
//ethClient.stop(); //Refresh MQTT connect to get retained info
return 1;
} else if (sysConfStream.available())
{
sysConfStream.putEOF(); //truncate garbage
sysConf.setETAG("");
sysConf.saveETAG();
}
sysConfStream.close();
infoSerial<<F("No stored config")<<endl;
configLocked--;
return 0;
}
int cmdFunctionSave(int arg_cnt, char **args)
{
if (arg_cnt>1)
{
if (!strcasecmp_P(args[1],ON_P)) sysConf.setSaveSuccedConfig(true);
if (!strcasecmp_P(args[1],OFF_P)) sysConf.setSaveSuccedConfig(false);
infoSerial<<F("Config autosave:")<<sysConf.getSaveSuccedConfig()<<endl;
return 200;
}
#if defined(FS_STORAGE)
sysConfStream.open("/config.json",'w');
#else
sysConfStream.open(FN_CONFIG_JSON,'w');
#endif
aJsonStream jsonEEPROMStream = aJsonStream(&sysConfStream);
infoSerial<<F("Saving config to EEPROM..");
aJson.print(root, &jsonEEPROMStream);
sysConfStream.close();
infoSerial<<F("Saved to EEPROM")<<endl;
//#endif
sysConf.saveETAG();
return 200;
}
int cmdFunctionLoglevel(int arg_cnt, char **args)
{
int res = 400;
if (arg_cnt>1)
{
serialDebugLevel=atoi(args[1]);
sysConf.setSerialDebuglevel(serialDebugLevel);
res = 200;
}
if (arg_cnt>2)
{
udpDebugLevel=atoi(args[2]);
sysConf.setUdpDebuglevel(udpDebugLevel);
res = 200;
}
infoSerial<<F("Serial debug level:")<<serialDebugLevel<<F("\nSyslog debug level:")<<udpDebugLevel<<endl;
return res;
}
int cmdFunctionIp(int arg_cnt, char **args)
{
IPAddress ip0(0, 0, 0, 0);
IPAddress ip;
/*
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) || defined(NRF5)
DNSClient dns;
#define _inet_aton(cp, addr) dns._inet_aton(cp, addr)
#else
#define _inet_aton(cp, addr) _inet_aton(cp, addr)
#endif
*/
// switch (arg_cnt) {
// case 5:
if (arg_cnt>4 && _inet_aton(args[4], ip)) sysConf.setMask(ip);
else sysConf.setMask(ip0);
// case 4:
if (arg_cnt>3 && _inet_aton(args[3], ip)) sysConf.setGW(ip);
else sysConf.setGW(ip0);
// case 3:
if (arg_cnt>2 && _inet_aton(args[2], ip)) sysConf.setDNS(ip);
else sysConf.setDNS(ip0);
// case 2:
if (arg_cnt>1 && _inet_aton(args[1], ip)) sysConf.setIP(ip);
else sysConf.setIP(ip0);
// break;
// case 1: //dynamic IP
if (arg_cnt==1)
{
sysConf.setIP(ip0);
infoSerial<<F("Set dynamic IP\n");
}
/*
IPAddress current_ip = Ethernet.localIP();
IPAddress current_mask = Ethernet.subnetMask();
IPAddress current_gw = Ethernet.gatewayIP();
IPAddress current_dns = Ethernet.dnsServerIP();
saveFlash(OFFSET_IP, current_ip);
saveFlash(OFFSET_MASK, current_mask);
saveFlash(OFFSET_GW, current_gw);
saveFlash(OFFSET_DNS, current_dns);
debugSerial<<F("Saved current config(ip,dns,gw,subnet):");
printIPAddress(current_ip);
printIPAddress(current_dns);
printIPAddress(current_gw);
printIPAddress(current_mask); */
//}
infoSerial<<F("Saved\n");
sysConf.setDHCPfallback(false);
return 200;
}
int cmdFunctionClearEEPROM(int arg_cnt, char **args){
#ifdef FS_STORAGE
if (SPIFFS.format()) infoSerial<<F("FS Formatted\n");
#endif
if (sysConf.clear()) infoSerial<<F("EEPROM cleared\n");
#if defined(FS_STORAGE)
sysConfStream.open("/config.json",'r');
#else
sysConfStream.open(FN_CONFIG_JSON,'r');
#endif
sysConfStream.putEOF();
sysConfStream.close();
return 200;
}
int cmdFunctionPwd(int arg_cnt, char **args)
{ //char empty[]="";
if (arg_cnt)
sysConf.setMQTTpwd(args[1]);
else sysConf.setMQTTpwd();
infoSerial<<F("MQTT Password updated\n");
return 200;
}
int cmdFunctionOTAPwd(int arg_cnt, char **args)
{ //char empty[]="";
if (arg_cnt)
sysConf.setOTApwd(args[1]);
else sysConf.setOTApwd();
infoSerial<<F("OTA Password updated\n");
return 200;
}
int cmdFunctionSetMac(int arg_cnt, char **args) {
char dummy;
uint8_t mac[6];
if (sscanf(args[1], "%x:%x:%x:%x:%x:%x%c", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5], &dummy) < 6) {
errorSerial<<F("could not parse: ")<<args[1];
return 400;
}
sysConf.setMAC(mac);
printMACAddress();
infoSerial<<F("Updated\n");
return 200;
}
int cmdFunctionGet(int arg_cnt, char **args) {
if (arg_cnt>1)
{
if (!strcasecmp_P(args[1],ON_P)) {sysConf.setLoadHTTPConfig(true); return 200;};
if (!strcasecmp_P(args[1],OFF_P)) {sysConf.setLoadHTTPConfig(false); return 200;};
infoSerial<<F("Loading HTTP config on startup:")<<sysConf.getLoadHTTPConfig()<<endl;
sysConf.setServer(args[1]);
infoSerial<<args[1]<<F(" Saved")<<endl;
}
if (lanStatus>=HAVE_IP_ADDRESS)
{
int retCode=loadConfigFromHttp();
if (retCode==200)
{
lanStatus =IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
return 200;
}
else if (retCode == -1)
{
debugSerial<<F("Releasing socket and retry")<<endl;
configOk=false;
lanStatus=LIBS_INITIALIZED;
ethClient.stop(); // Release MQTT socket
return 201;
}
// Not Loaded
if (configLoaded) lanStatus =IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
else lanStatus = DO_READ_RE_CONFIG;
return retCode;
}
errorSerial<<F("No IP adress")<<endl;
return 500;
}
void printBool(bool arg) { (arg) ? infoSerial<<F("+") : infoSerial<<F("-"); }
const char * headerKeys[]={"ETag"};
void headerHandlerProc(String header)
{
debugSerial<<header<<endl;
//ETag: W/"51e-17bffcd0547"
if (header.startsWith(F("ETag: ")))
{
sysConf.setETAG(header);
}
}
int loadConfigFromHttp()
{
//macAddress * mac = sysConf.getMAC();
int responseStatusCode = 0;
char URI[64];
char configServer[32]="";
if (!sysConf.getServer(configServer,sizeof(configServer)))
{
strncpy_P(configServer,configserver,sizeof(configServer));
infoSerial<<F(" Default config server used: ")<<configServer<<endl;
}
#ifndef DEVICE_NAME
snprintf(URI, sizeof(URI), "/cnf/%02x-%02x-%02x-%02x-%02x-%02x.config.json", sysConf.mac[0], sysConf.mac[1], sysConf.mac[2], sysConf.mac[3], sysConf.mac[4],
sysConf.mac[5]);
#else
#ifndef FLASH_64KB
snprintf(URI, sizeof(URI), "/cnf/%s_config.json",QUOTE(DEVICE_NAME));
#else
strncpy(URI, "/cnf/", sizeof(URI));
strncat(URI, QUOTE(DEVICE_NAME), sizeof(URI));
strncat(URI, "_config.json", sizeof(URI));
#endif
#endif
infoSerial<<F("Config URI: http://")<<configServer<<URI<<endl;
#if defined(ARDUINO_ARCH_AVR)
FILE *configStream;
//wdt_dis();
//if (freeRam()<512) cleanConf();
HTTPClient hclient(configServer, 80);
String ETAG = sysConf.getETAG();
http_client_parameter get_header[] = {
{"If-None-Match",NULL},{NULL,NULL}
};
get_header[0].value = ETAG.c_str();
debugSerial<<F("free ")<<freeRam()<<endl;delay(100);
hclient.setHeaderHandler(headerHandlerProc);
// FILE is the return STREAM type of the HTTPClient
configStream = hclient.getURI(URI,NULL,get_header);
responseStatusCode = hclient.getLastReturnCode();
debugSerial<<F("http retcode ")<<responseStatusCode<<endl;delay(100);
//wdt_en();
if (configStream != NULL) {
if (responseStatusCode == 200) {
infoSerial<<F("got Config\n"); delay(500);
aJsonFileStream as = aJsonFileStream(configStream);
noInterrupts();
cleanConf(true);
root = aJson.parse(&as);
interrupts();
hclient.closeStream(configStream); // this is very important -- be sure to close the STREAM
if (!root)
{
sysConf.setETAG("");
errorSerial<<F("Config parsing failed\n");
return 0;
}
else {
applyConfig();
if (configLoaded && sysConf.getSaveSuccedConfig()) cmdFunctionSave(0,NULL);
infoSerial<<F("Done.\n");
return 200;
}
}
else if (responseStatusCode == 304)
{
infoSerial<<F("Config not changed\n");
hclient.closeStream(configStream);
return responseStatusCode;
}
else
{
errorSerial<<F("ERROR: Server returned ");
hclient.closeStream(configStream);
errorSerial<<responseStatusCode<<endl;
return responseStatusCode;
}
} else {
debugSerial<<F("failed to connect\n");
return -1;
}
#endif
#if defined(__SAM3X8E__) || defined(ARDUINO_ARCH_STM32) || defined (NRF5) //|| defined(ARDUINO_ARCH_AVR)//|| defined(ARDUINO_ARCH_ESP32) //|| defined(ARDUINO_ARCH_ESP8266)
#if defined(WIFI_ENABLE)
WiFiClient configEthClient;
#else
EthernetClient configEthClient;
#endif
//String response;
HttpClient htclient = HttpClient(configEthClient, configServer, 80);
//htclient.stop(); //_socket =MAX
htclient.setHttpResponseTimeout(4000);
wdt_res();
//debugSerial<<"making GET request");get
debugSerial<<F("Before request: Free:")<<freeRam()<<endl;
htclient.beginRequest();
responseStatusCode = htclient.get(URI);
htclient.sendHeader("If-None-Match",sysConf.getETAG());
htclient.endRequest();
if (responseStatusCode == HTTP_SUCCESS)
{
// read the status code and body of the response
responseStatusCode = htclient.responseStatusCode();
while (htclient.headerAvailable())
if (htclient.readHeaderName().equalsIgnoreCase("ETAG")) sysConf.setETAG(htclient.readHeaderValue());
//response = htclient.responseBody();
wdt_res();
infoSerial<<F("HTTP Status code: ")<<responseStatusCode<<endl;
if (responseStatusCode == 200) {
aJsonStream socketStream = aJsonStream(&htclient);
debugSerial<<F("Free:")<<freeRam()<<endl;
cleanConf(true);
debugSerial<<F("Configuration cleaned")<<endl;
debugSerial<<F("Free:")<<freeRam()<<endl;
//root = aJson.parse((char *) response.c_str());
root = aJson.parse(&socketStream);
htclient.stop();
if (!root) {
errorSerial<<F("Config parsing failed\n");
sysConf.setETAG("");
return 0;
}
else {
debugSerial<<F("Parsed. Free:")<<freeRam()<<endl;
//debugSerial<<response;
applyConfig();
infoSerial<<F("Done.\n");
if (configLoaded && sysConf.getSaveSuccedConfig()) cmdFunctionSave(0,NULL);
return 200;
}
}
else if (responseStatusCode == 304)
{
errorSerial<<F("Config not changed\n");
htclient.stop();
return responseStatusCode;
}
else {
errorSerial<<F("Config retrieving failed\n");
htclient.stop();
return responseStatusCode;
}
}
else
{
errorSerial<<F("Connect failed\n");
return -1;
}
#endif
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) //|| defined (NRF5)
#if defined(WIFI_ENABLE)
WiFiClient configEthClient;
#else
EthernetClient configEthClient;
#endif
HTTPClient httpClient;
String fullURI = "http://";
fullURI+=configServer;
fullURI+=URI;
#if defined(ARDUINO_ARCH_ESP8266)
httpClient.begin(configEthClient,fullURI);
#else
httpClient.begin(fullURI);
#endif
httpClient.addHeader("If-None-Match",sysConf.getETAG());
httpClient.collectHeaders(headerKeys,1);
responseStatusCode = httpClient.GET();
if (responseStatusCode > 0) {
infoSerial.printf("[HTTP] GET... code: %d\n", responseStatusCode);
if (responseStatusCode == HTTP_CODE_OK)
{
WiFiClient * stream = httpClient.getStreamPtr();
if (stream)
{
aJsonStream socketStream = aJsonStream(stream);
sysConf.setETAG(httpClient.header("ETag"));
//String response = httpClient.getString();
//debugSerial<<response;
cleanConf(true);
//root = aJson.parse((char *) response.c_str());
root = aJson.parse(&socketStream);
httpClient.end();
} else
{
httpClient.end();
return -1;
}
if (!root) {
sysConf.setETAG("");
errorSerial<<F("Config parsing failed\n");
return 0;
} else {
applyConfig();
if (configLoaded && sysConf.getSaveSuccedConfig()) cmdFunctionSave(0,NULL);
infoSerial<<F("Done.\n");
return 200;
}
}
else if (responseStatusCode == HTTP_CODE_NOT_MODIFIED)
{
httpClient.end();
errorSerial<<F("Config not changed\n");
return responseStatusCode;
}
else
{
httpClient.end();
errorSerial<<F("Config retrieving failed\n");
return responseStatusCode;
}
} else
{
errorSerial.printf("[HTTP] GET... failed, error: %s\n", httpClient.errorToString(responseStatusCode).c_str());
httpClient.end();
return responseStatusCode;
}
#endif
}
void preTransmission() {
#ifdef CONTROLLINO
// set DE and RE on HIGH
PORTJ |= B01100000;
#else
digitalWrite(TXEnablePin, 1);
#endif
}
void postTransmission() {
#ifdef CONTROLLINO
// set DE and RE on LOW
PORTJ &= B10011111;
#else
digitalWrite(TXEnablePin, 0);
#endif
}
void TimerHandler(void)
{
timerCount=micros();
if (configLoaded && !timerHandlerBusy)
{
timerHandlerBusy++;
interrupts();
inputLoop(CHECK_INTERRUPT);
#ifdef DMX_SMOOTH
DMXOUT_propagate();
#endif
timerHandlerBusy--;
}
timerCount=micros()-timerCount;
}
#if defined(__SAM3X8E__) && defined (TIMER_INT)
int16_t attachTimer(double microseconds, timerCallback callback, const char* TimerName)
{
if (timerNumber!=-1) return timerNumber;
DueTimerInterrupt dueTimerInterrupt = DueTimer.getAvailable();
dueTimerInterrupt.attachInterruptInterval(microseconds, callback);
timerNumber = dueTimerInterrupt.getTimerNumber();
debugSerial<<TimerName<<F(" attached to Timer(")<<timerNumber<<F(")")<<endl;
//DueTimer.Timers[timerNumber].irq
NVIC_SetPriority(TC0_IRQn,2);
debugSerial << "USART0 prio:" << NVIC_GetPriority (USART0_IRQn)<< " TC0 prio:" << NVIC_GetPriority (TC0_IRQn)<<endl;
return timerNumber;
}
#endif
#if defined(__SAM3X8E__) && defined (PULSEPIN12)
#define MATURA_PULSE 100
#define MATURA_PERIOD 2500
void maturaTimerHandler(void){ }
volatile int maturaTimerNumber = -1;
int16_t attachMaturaTimer()
{
if (maturaTimerNumber==-1)
{
DueTimerInterrupt dueTimerInterrupt = DueTimerInterrupt(8);
dueTimerInterrupt.attachInterruptInterval(MATURA_PERIOD*1000, maturaTimerHandler);
maturaTimerNumber = dueTimerInterrupt.getTimerNumber();
TC_SetRB(TC2,2,REG_TC2_RC2 - (REG_TC2_RC2 / MATURA_PERIOD * MATURA_PULSE));
REG_TC2_CMR2 |= TC_CMR_BCPB_SET | TC_CMR_BCPC_CLEAR | TC_CMR_EEVT_XC0; // on CTR==A -> SET; on CTR == C - CLEAR pin, enable TIOB as out
}
debugSerial<<F("Matura attached to Timer(")<<maturaTimerNumber<<F(")")<<endl;
//pinMode(11,OUTPUT); //PIO_PD7B_TIOA8 PERIPH_B //T7
pinMode(12,OUTPUT); //PIO_PD8B_TIOB8 PERIPH_B //T8
// Memo for some future use timers:
//pinMode(2,OUTPUT); //PIO_PB25B_TIOA0 PERIPH_B
//pinMode(13,OUTPUT);//PIO_PB27B_TIOB0 PERIPH_B
//pinMode(3,OUTPUT); //PIO_PC28B_TIOA7 PERIPH_B
//pinMode(10,OUTPUT);//PIO_PC29B_TIOB7 PERIPH_B
//pinMode(5,OUTPUT); //PIO_PC25B_TIOA6 PERIPH_B
//pinMode(4,OUTPUT); //PIO_PC26B_TIOB6 PERIPH_B
// 11 & 12 pins example (TC2 ch2 (#8) A & B channels for some future use)
//REG_PIOD_PDR = PIO_PD7 | PIO_PD8;
//REG_PIOD_ABSR |= PIO_PD7 | PIO_PD8;
//REG_TC2_CMR2 |= TC_CMR_ACPA_SET | TC_CMR_ACPC_CLEAR | TC_CMR_BCPB_SET | TC_CMR_BCPC_CLEAR | TC_CMR_EEVT_XC0;
//TC_SetRA(TC2,2,1000000);TC_SetRB(TC2,2,1500000);
REG_PIOD_PDR = PIO_PD8; //Disabling PD8 IO - move to pereferia
REG_PIOD_ABSR |= PIO_PD8; //Select Pereferia B
//REG_TC2_CMR2 |= TC_CMR_BCPB_SET | TC_CMR_BCPC_CLEAR | TC_CMR_EEVT_XC0; // on CTR==A -> SET; on CTR == C - CLEAR pin, enable TIOB as out
//REG_PIOD_WPMR = PIO_WPMR_WPKEY(0x50494f) | 1; //Write protect whole IOD
return maturaTimerNumber;
}
#else
int16_t attachMaturaTimer(){return 0;};
#endif
#if defined(WIFI_ENABLE)
#if defined (ESP32)
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
{
debugSerial.printf("[WiFi-event] event: %d\n", event);
switch (event) {
case SYSTEM_EVENT_WIFI_READY:
debugSerial.println("WiFi interface ready");
break;
case SYSTEM_EVENT_SCAN_DONE:
debugSerial.println("Completed scan for access points");
break;
case SYSTEM_EVENT_STA_START:
debugSerial.println("WiFi client started");
break;
case SYSTEM_EVENT_STA_STOP:
debugSerial.println("WiFi client stopped");
//changeState(MYSTATE_OFFLINE);
break;
case SYSTEM_EVENT_STA_CONNECTED:
debugSerial.println("Connected to access point");
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
debugSerial.println("Disconnected from WiFi access point");
WiFi.reconnect();
break;
case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
debugSerial.println("Authentication mode of access point has changed");
break;
case SYSTEM_EVENT_STA_GOT_IP:
debugSerial.print("Obtained IP address: ");
//Serial.println(WiFi.localIP());
//Serial.println("WiFi connected");
//Serial.print("IP address: ");
debugSerial.println(IPAddress(info.got_ip.ip_info.ip.addr));
//changeState(MYSTATE_ONLINE);
break;
case SYSTEM_EVENT_STA_LOST_IP:
debugSerial.println("Lost IP address and IP address is reset to 0");
//changeState(MYSTATE_OFFLINE);
break;
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
debugSerial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_FAILED:
debugSerial.println("WiFi Protected Setup (WPS): failed in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
debugSerial.println("WiFi Protected Setup (WPS): timeout in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_PIN:
debugSerial.println("WiFi Protected Setup (WPS): pin code in enrollee mode");
break;
case SYSTEM_EVENT_AP_START:
debugSerial.println("WiFi access point started");
break;
case SYSTEM_EVENT_AP_STOP:
debugSerial.println("WiFi access point stopped");
break;
case SYSTEM_EVENT_AP_STACONNECTED:
debugSerial.println("Client connected");
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
debugSerial.println("Client disconnected");
break;
case SYSTEM_EVENT_AP_STAIPASSIGNED:
debugSerial.println("Assigned IP address to client");
break;
case SYSTEM_EVENT_AP_PROBEREQRECVED:
debugSerial.println("Received probe request");
break;
case SYSTEM_EVENT_GOT_IP6:
debugSerial.println("IPv6 is preferred");
break;
case SYSTEM_EVENT_ETH_START:
debugSerial.println("Ethernet started");
break;
case SYSTEM_EVENT_ETH_STOP:
debugSerial.println("Ethernet stopped");
break;
case SYSTEM_EVENT_ETH_CONNECTED:
debugSerial.println("Ethernet connected");
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
debugSerial.println("Ethernet disconnected");
break;
case SYSTEM_EVENT_ETH_GOT_IP:
debugSerial.println("Obtained IP address");
break;
default: break;
}
}
#endif
#endif
void setup_main() {
#if (SERIAL_BAUD)
#if defined(debugSerialPort) && !defined(NOSERIAL)
debugSerialPort.begin(SERIAL_BAUD);
#endif
#else
#if not defined (__SAM3X8E__) && defined (debugSerialPort) && !defined(NOSERIAL)
debugSerialPort.begin();
#endif
delay(1000);
#endif
#ifndef ESP_EEPROM_SIZE
#define ESP_EEPROM_SIZE 4096-10
#endif
#if defined (FS_STORAGE)
#if defined(FS_PREPARE)
//Initialize File System
#if defined ARDUINO_ARCH_ESP32
if(SPIFFS.begin(true))
#else
if(SPIFFS.begin())
#endif
{
#if defined(debugSerialPort) && !defined(NOSERIAL)
debugSerialPort.println("SPIFFS Initialize....ok");
#endif
}
else
{
#if defined(debugSerialPort) && !defined(NOSERIAL)
debugSerialPort.println("SPIFFS Initialization...failed");
#endif
}
#endif
#else
#if defined(ESP8266) || defined(ESP32)
EEPROM.begin(ESP_EEPROM_SIZE);
int streamSize = EEPROM.length();
sysConfStream.setSize(streamSize-EEPROM_offsetJSON);
debugSerial<<F("FLASH Init: ")<<streamSize<<endl;
#endif
#endif
//debugSerialPort << "Checkin EEPROM integrity (signature)"<<endl;
bool needClean = false;
#ifdef CONFIG_CLEAN_PIN
pinMode(CONFIG_CLEAN_PIN,INPUT_PULLUP);
int i = 0;
while ((digitalRead(CONFIG_CLEAN_PIN)==LOW) && !needClean)
{
statusLED.set(ledRED);
delay(500);
statusLED.set(ledGREEN);
delay(500);
statusLED.set(ledBLUE);
delay(500);
if (i>4) needClean = true;
i++;
}
//if (needClean) cmdFunctionClearEEPROM(0, NULL);
#endif
if (!sysConf.isValidSysConf() || needClean)
{
#if defined(debugSerialPort) && !defined(NOSERIAL)
debugSerialPort.println(F("Initializing EEPROM."));
#endif
cmdFunctionClearEEPROM(0, NULL);
//sysConf.clear();
}
else debugSerial << F("EEPROM signature ok")<<endl;
// scan_i2c_bus();
serialDebugLevel=sysConf.getSerialDebuglevel();
udpDebugLevel=sysConf.getUdpDebuglevel();
#if defined(__SAM3X8E__)
memset(&UniqueID,0,sizeof(UniqueID));
#endif
#if defined(M5STACK)
// Initialize the M5Stack object
M5.begin();
#endif
setupCmdArduino();
printFirmwareVersionAndBuildOptions();
#ifdef SD_CARD_INSERTED
sd_card_w5100_setup();
#endif
// Serial.print("Sig4=");
// Serial.println(FLASH_START[0],HEX);
setupMacAddress(); //тут почему-то не считывается из флэш
#ifdef _modbus
#ifdef CONTROLLINO
//set PORTJ pin 5,6 direction (RE,DE)
DDRJ |= B01100000;
//set RE,DE on LOW
PORTJ &= B10011111;
#else
pinMode(TXEnablePin, OUTPUT);
#endif
modbusSerial.begin(MODBUS_SERIAL_BAUD,MODBUS_SERIAL_PARAM);
node.idle(&modbusIdle);
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
#endif
delay(20);
//owReady = 0;
mqttClient.setCallback(mqttCallback);
//#ifdef _artnet
// artnetSetup();
//#endif
#if defined(WIFI_ENABLE)
#if defined (ESP32)
WiFi.onEvent(WiFiEvent);
#endif
#endif
#if defined(WIFI_ENABLE) and not defined(WIFI_MANAGER_DISABLE)
// WiFiManager wifiManager;
wifiManager.setTimeout(180);
#if defined(ESP_WIFI_AP) and defined(ESP_WIFI_PWD)
wifiInitialized = wifiManager.autoConnect(QUOTE(ESP_WIFI_AP), QUOTE(ESP_WIFI_PWD));
#else
wifiInitialized = wifiManager.autoConnect();
#endif
#endif
delay(LAN_INIT_DELAY);//for LAN-shield initializing
#if defined(W5500_CS_PIN) && ! defined(WIFI_ENABLE)
Ethernet.init(W5500_CS_PIN);
infoSerial<<F("Use W5500 pin: ");
infoSerial<<QUOTE(W5500_CS_PIN)<<endl;
#endif
loadConfigFromEEPROM();
}
void printFirmwareVersionAndBuildOptions() {
infoSerial<<F("\nLazyhome.ru LightHub controller ")<<F(QUOTE(PIO_SRC_REV))<<F(" C++ version:")<<F(QUOTE(__cplusplus))<<endl;
infoSerial<<F("\nConfig server:")<<F(CONFIG_SERVER);
#ifdef CUSTOM_FIRMWARE_MAC
infoSerial<<F("\nFirmware MAC Address " QUOTE(CUSTOM_FIRMWARE_MAC));
#endif
#ifdef CONTROLLINO
infoSerial<<F("\n(+)CONTROLLINO");
#endif
#ifndef WATCH_DOG_TICKER_DISABLE
infoSerial<<F("\n(+)WATCHDOG");
#else
infoSerial<<F("\n(-)WATCHDOG");
#endif
#ifdef DISABLE_FREERAM_PRINT
infoSerial<<F("\n(-)FreeRam printing");
#else
infoSerial<<F("\n(+)FreeRam printing");
#endif
#ifdef WIFI_ENABLE
infoSerial<<F("\n(+)WiFi");
#elif Wiz5500
infoSerial<<F("\n(+)WizNet5500");
#elif Wiz5100
infoSerial<<F("\n(+)Wiznet5100");
#else
infoSerial<<F("\n(+)Wiznet5x00");
#endif
#ifndef DMX_DISABLE
infoSerial<<F("\n(+)DMX");
#ifdef FASTLED
infoSerial<<F("\n(+)FASTLED");
#else
infoSerial<<F("\n(+)ADAFRUIT LED");
#endif
#else
infoSerial<<F("\n(-)DMX");
#endif
#ifdef _modbus
infoSerial<<F("\n(+)MODBUS " QUOTE(MODBUS_SERIAL_PARAM) " at " QUOTE(modbusSerial) " speed:" QUOTE(MODBUS_SERIAL_BAUD));
#else
infoSerial<<F("\n(-)MODBUS");
#endif
#ifdef IPMODBUS
infoSerial<<F("\n(+)IPMODBUS ");// QUOTE(MODBUS_TCP_PARAM) " at " QUOTE(modbusSerial) " speed:" QUOTE(MODBUS_TCP_BAUD));
#else
infoSerial<<F("\n(-)IPMODBUS");
#endif
#ifndef OWIRE_DISABLE
infoSerial<<F("\n(+)OWIRE");
#ifdef USE_1W_PIN
infoSerial<<F("\n(-)DS2482-100 USE_1W_PIN=")<<QUOTE(USE_1W_PIN);
#else
infoSerial<<F("\n(+)DS2482-100");
#endif
#else
infoSerial<<F("\n(-)OWIRE");
#endif
#ifndef DHT_DISABLE
infoSerial<<F("\n(+)DHT");
#else
infoSerial<<F("\n(-)DHT");
#endif
#ifndef COUNTER_DISABLE
infoSerial<<F("\n(+)COUNTER");
#else
infoSerial<<F("\n(-)COUNTER");
#endif
#ifdef SD_CARD_INSERTED
infoSerial<<F("\n(+)SDCARD");
#endif
#ifdef RESET_PIN
infoSerial<<F("\n(+)HARDRESET on pin=")<<QUOTE(RESET_PIN);
#else
infoSerial<<F("\n(-)HARDRESET, using soft");
#endif
#ifdef RESTART_LAN_ON_MQTT_ERRORS
infoSerial<<F("\n(+)RESTART_LAN_ON_MQTT_ERRORS");
#else
infoSerial<<F("\n(-)RESTART_LAN_ON_MQTT_ERRORS");
#endif
#ifndef CSSHDC_DISABLE
infoSerial<<F("\n(+)CCS811 & HDC1080");
#else
infoSerial<<F("\n(-)CCS811 & HDC1080");
#endif
#ifndef AC_DISABLE
infoSerial<<F("\n(+)AC HAIER on " QUOTE(AC_Serial));
#else
infoSerial<<F("\n(-)AC HAIER");
#endif
#ifndef MOTOR_DISABLE
infoSerial<<F("\n(+)MOTOR CTR");
#else
infoSerial<<F("\n(-)MOTOR CTR");
#endif
#ifndef SPILED_DISABLE
infoSerial<<F("\n(+)SPI LED");
#else
infoSerial<<F("\n(-)SPI LED");
#endif
#ifdef OTA
infoSerial<<F("\n(+)OTA");
#else
infoSerial<<F("\n(-)OTA");
#endif
#ifdef ARTNET_ENABLE
infoSerial<<F("\n(+)ARTNET");
#else
infoSerial<<F("\n(-)ARTNET");
#endif
#ifdef MCP23017
infoSerial<<F("\n(+)MCP23017");
#else
infoSerial<<F("\n(-)MCP23017");
#endif
#ifndef PID_DISABLE
infoSerial<<F("\n(+)PID");
#else
infoSerial<<F("\n(-)PID");
#endif
#ifdef SYSLOG_ENABLE
infoSerial<<F("\n(+)SYSLOG");
#else
infoSerial<<F("\n(-)SYSLOG");
#endif
#ifdef UARTBRIDGE_ENABLE
infoSerial<<F("\n(+)UARTBRIDGE " QUOTE(MODULE_UATRBRIDGE_UARTA) "<=>" QUOTE(MODULE_UATRBRIDGE_UARTB));
#else
infoSerial<<F("\n(-)UARTBRIDGE");
#endif
#ifdef MDNS_ENABLE
infoSerial<<F("\n(+)MDNS");
#else
infoSerial<<F("\n(-)MDNS");
#endif
#ifndef RELAY_DISABLE
infoSerial<<F("\n(+)PWM_RELAY");
#else
infoSerial<<F("\n(-)PWM_RELAY");
#endif
#ifndef MULTIVENT_DISABLE
infoSerial<<F("\n(+)MULTIVENT");
#else
infoSerial<<F("\n(-)MULTIVENT");
#endif
#ifdef HUMIDIFIER_ENABLE
infoSerial<<F("\n(+)HUMIDIFIER");
#endif
#ifdef NOSERIAL
infoSerial<<F("\nNOSERIAL");
#endif
#ifdef ELEVATOR_ENABLE
infoSerial<<F("\n(+)ELEVATOR");
#endif
#ifdef MERCURY_ENABLE
infoSerial<<F("\n(+)MERCURY");
#else
infoSerial<<F("\n(-)MERCURY");
#endif
//#ifdef IPMODBUS
//infoSerial<<F("\n(+)IPMODBUS");
//#endif
infoSerial<<endl;
// WDT_Disable( WDT ) ;
#if defined(__SAM3X8E__)
debugSerial<<F("Reading 128 bits unique identifier")<<endl ;
ReadUniqueID( UniqueID.UID_Long ) ;
infoSerial<< F ("ID: ");
for (byte b = 0 ; b < 4 ; b++)
infoSerial<< _HEX((unsigned int) UniqueID.UID_Long [b]);
infoSerial<< endl;
#endif
}
void publishStat(){
long fr = freeRam();
char topic[64];
char intbuf[16];
uint32_t ut = millis()/1000UL;
if (!mqttClient.connected() || ethernetIdleCount) return;
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, stats_P, sizeof(topic)-1);
strncat(topic, "/", sizeof(topic));
strncat_P(topic, freeheap_P, sizeof(topic)-1);
printUlongValueToStr(intbuf, fr);
mqttClient.publish(topic,intbuf,true);
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, stats_P, sizeof(topic)-1);
strncat(topic, "/", sizeof(topic));
strncat_P(topic, uptime_P, sizeof(topic)-1);
printUlongValueToStr(intbuf, ut);
mqttClient.publish(topic,intbuf,true);
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, state_P, sizeof(topic)-1);
strncpy_P(intbuf, ready_P, sizeof(intbuf)-1);
mqttClient.publish(topic,intbuf,true);
#ifdef CRYPT
RNG.rand((uint8_t *) &cryptoSalt,sizeof(cryptoSalt));
setTopic(topic,sizeof(topic),T_DEV);
//strncat_P(topic, stats_P, sizeof(topic)-1);
//strncat(topic, "/", sizeof(topic));
strncat_P(topic, salt_P, sizeof(topic)-1);
printUlongValueToStr(intbuf, cryptoSalt);
mqttClient.publish(topic,intbuf,true);
#endif
}
void setupMacAddress() {
//Check MAC, stored in NVRAM
if (!sysConf.getMAC()) {
infoSerial<<F("No MAC configured: set firmware's MAC\n");
#if defined (CUSTOM_FIRMWARE_MAC) //Forced MAC from compiler's directive
const char *macStr = QUOTE(CUSTOM_FIRMWARE_MAC);//colon(:) separated from build options
parseBytes(macStr, ':', sysConf.mac, 6, 16);
sysConf.mac[0]&=0xFE;
sysConf.mac[0]|=2;
#elif defined(WIFI_ENABLE)
//Using original MPU MAC
WiFi.begin();
WiFi.macAddress(sysConf.mac);
#elif defined(__SAM3X8E__)
//Lets make MAC from MPU serial#
sysConf.mac[0]=0xDE;
for (byte b = 0 ; b < 5 ; b++)
sysConf.mac[b+1]=UniqueID.UID_Byte [b*3] + UniqueID.UID_Byte [b*3+1] + UniqueID.UID_Byte [b*3+2];
#elif defined DEFAULT_FIRMWARE_MAC
uint8_t defaultMac[6] = DEFAULT_FIRMWARE_MAC;//comma(,) separated hex-array, hard-coded
memcpy(sysConf.mac,defaultMac,6);
#endif
}
printMACAddress();
}
void setupCmdArduino() {
//cmdInit(uint32_t(SERIAL_BAUD));
cmdInit();
debugSerial<<(F(">>>"));
cmdAdd("help", cmdFunctionHelp);
cmdAdd("save", cmdFunctionSave);
cmdAdd("load", cmdFunctionLoad);
cmdAdd("get", cmdFunctionGet);
#ifndef FLASH_64KB
cmdAdd("mac", cmdFunctionSetMac);
#endif
cmdAdd("kill", cmdFunctionKill);
//cmdAdd("req", cmdFunctionReq);
cmdAdd("ip", cmdFunctionIp);
cmdAdd("pwd", cmdFunctionPwd);
cmdAdd("otapwd", cmdFunctionOTAPwd);
cmdAdd("clear",cmdFunctionClearEEPROM);
cmdAdd("reboot",cmdFunctionReboot);
cmdAdd("log",cmdFunctionLoglevel);
#ifndef OWIRE_DISABLE
cmdAdd("search",cmdFunctionSearch);
#endif
}
void loop_main() {
statusLED.poll();
#if defined(M5STACK)
// Initialize the M5Stack object
yield();
M5.update();
#endif
wdt_res();
yield();
cmdPoll();
if (lanLoop() > HAVE_IP_ADDRESS) {
mqttClient.loop();
#if defined(OTA)
yield();
ArduinoOTA.poll();
#endif
#ifdef _artnet
yield();
if (artnet) artnet->read(); ///hung if network not initialized
#endif
#ifdef MDNS_ENABLE
#ifndef WIFI_ENABLE
yield();
mdns.run();
#elif defined(ARDUINO_ARCH_ESP8266)
MDNS.update();
#endif
#endif
}
#ifdef _owire
yield();
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
yield();
DMXCheck();
#endif
if (items) {
if (isNotRetainingStatus()) pollingLoop();
yield();
thermoLoop();
}
yield();
inputLoop(CHECK_INPUT);
yield();
inputSensorsLoop();
#if defined (_espdmx)
yield();
dmxout.update();
#endif
#ifdef IPMODBUS
if (initializedListeners) ipmodbusLoop();
#endif
}
//static uint32_t tm=0;
void owIdle(void) {
// timerCtr++;
#ifdef _artnet
if (artnet && (lanStatus>=HAVE_IP_ADDRESS)) artnet->read();
#endif
wdt_res();
inputLoop(CHECK_INPUT);
//inputLoop(CHECK_INTERRUPT);
/*
if (isTimeOver(tm,millis(),100))
{
tm=millis();
debugSerial<<F("1WT: Tick")<<endl;
}
*/
#if defined (_espdmx)
yield();
dmxout.update();
#endif
return; //?????
#ifdef _dmxin
yield();
DMXCheck();
#endif
#ifdef IPMODBUS
if (initializedListeners) ipmodbusLoop();
#endif
}
void ethernetIdle(void){
ethernetIdleCount++;
wdt_res();
yield();
inputLoop(CHECK_INTERRUPT);
yield();
cmdPoll();
ethernetIdleCount--;
};
void modbusIdle(void) {
wdt_res();
statusLED.poll();
yield();
cmdPoll();
yield();
inputLoop(CHECK_INPUT);
if (lanLoop() > HAVE_IP_ADDRESS)
{ // Begin network runners
yield();
mqttClient.loop();
#ifdef _artnet
if (artnet) artnet->read();
#endif
#if defined(OTA)
yield();
ArduinoOTA.poll();
#endif
#ifdef MDNS_ENABLE
#ifndef WIFI_ENABLE
mdns.run();
#elif defined(ARDUINO_ARCH_ESP8266)
MDNS.update();
#endif
#endif
} //End network runners
#ifdef _dmxin
DMXCheck();
#endif
#if defined (_espdmx)
dmxout.update();
#endif
}
volatile int8_t inputLoopBusy = 0;
void inputLoop(short cause) {
if (!inputs || inputLoopBusy) return;
inputLoopBusy++;
configLocked++;
//if (millis() > timerInputCheck)
if (isTimeOver(timerInputCheck,millis(),INTERVAL_CHECK_INPUT))
{
aJsonObject *input = inputs->child;
while (input) {
if (input->type == aJson_Object) {
// Check for nested inputs
aJsonObject * inputArray = aJson.getObjectItem(input, "act");
if (inputArray && (inputArray->type == aJson_Array))
{
aJsonObject *inputObj = inputArray->child;
while(inputObj)
{
Input in(inputObj,input);
in.Poll(cause);
//yield();
inputObj = inputObj->next;
}
}
else
{
Input in(input);
in.Poll(cause);
}
}
//yield();
input = input->next;
}
if (cause != CHECK_INTERRUPT) timerInputCheck = millis();// + INTERVAL_CHECK_INPUT;
inCache.invalidateInputCache();
}
configLocked--;
inputLoopBusy--;
}
void inputSensorsLoop() {
if (!inputs || inputLoopBusy) return;
//inputLoopBusy++;
configLocked++;
if (isTimeOver(timerSensorCheck,millis(),INTERVAL_CHECK_SENSOR))
{
aJsonObject *input = inputs->child;
while (input) {
if ((input->type == aJson_Object)) {
Input in(input);
in.Poll(CHECK_SENSOR);
}
yield();
inputLoop(CHECK_INPUT);
input = input->next;
}
timerSensorCheck = millis();
}
configLocked--;
//inputLoopBusy--;
}
void inputSetup(void) {
infoSerial<<F("Initializing Inputs")<<endl;
if (!inputs) return;
configLocked++;
aJsonObject *input = inputs->child;
while (input) {
if ((input->type == aJson_Object)) {
Input in(input);
in.setup();
}
yield();
input = input->next;
}
#if defined(__SAM3X8E__) && defined (TIMER_INT)
// Interval in microsecs
attachTimer(TIMER_CHECK_INPUT * 1000, TimerHandler, "ITimer");
attachMaturaTimer();
#endif
configLocked--;
}
// POLLINT_FAST - as often AS possible every item
// POLLING_1S - once per second every item
// POLLING_SLOW - just one item every 1S (Note: item::Poll() should return true if some action done - it will postpone next SLOW POLLING)
void pollingLoop(void) {
if (!items) return;
bool secExpired = isTimeOver(timerPollingCheck,millis(),INTERVAL_SLOW_POLLING);
if (secExpired) timerPollingCheck = millis();
configLocked++;
aJsonObject * item = items->child;
while (items && item)
if (item->type == aJson_Array && aJson.getArraySize(item)>1) {
Item it(item);
if (it.isValid()) {
it.Poll((secExpired)?POLLING_1S:POLLING_FAST);
} //isValid
yield();
item = item->next;
} //if
configLocked--;
// SLOW POLLING
boolean done = false;
if (lanStatus == RETAINING_COLLECTING) return;
if (secExpired)
{
while (pollingItem && !done) {
if (pollingItem->type == aJson_Array) {
Item it(pollingItem);
uint32_t ret = it.Poll(POLLING_SLOW);
if (ret)
{
////// timerPollingCheck = millis();// + ret; //INTERVAL_CHECK_MODBUS;
done = true;
}
}//if
if (!pollingItem) return; //Config was re-readed
yield();
pollingItem = pollingItem->next;
if (!pollingItem) {
pollingItem = items->child;
return;
} //start from 1-st element
} //while
}//if
}
////// Legacy Thermostat code below - to be moved in module /////
enum heaterMode {HEATER_HEAT,HEATER_OFF,HEATER_ERROR};
void thermoRelay(int pin, heaterMode on)
{
int thermoPin = abs(pin);
if(isProtectedPin(thermoPin)) {errorSerial<<F("pin disabled ")<<thermoPin<<endl;return;}
pinMode(thermoPin, OUTPUT);
if (on == HEATER_ERROR)
{
digitalWrite(thermoPin, LOW);
debugSerial<<F(" BYPASS")<<endl;
}
else if (on == HEATER_HEAT)
{
digitalWrite(thermoPin, (pin<0)?LOW:HIGH);
debugSerial<<F(" ON")<<endl;
}
else
{
digitalWrite(thermoPin, (pin<0)?HIGH:LOW);
debugSerial<<F(" OFF")<<endl;
}
}
void thermoLoop(void) {
if (!isTimeOver(timerThermostatCheck,millis(),THERMOSTAT_CHECK_PERIOD))
return;
if (!items) return;
bool thermostatCheckPrinted = false;
configLocked++;
for (aJsonObject *thermoItem = items->child; thermoItem; thermoItem = thermoItem->next) {
if ((thermoItem->type == aJson_Array) && (aJson.getArrayItem(thermoItem, I_TYPE)->valueint == CH_THERMO))
{
Item thermostat(thermoItem); //Init Item
if (!thermostat.isValid()) continue;
itemCmd thermostatCmd(&thermostat); // Got itemCmd
thermostatStore tStore;
tStore.asint=thermostat.getExt();
int thermoPin = thermostat.getArg(0);
float thermoSetting = thermostatCmd.getFloat();
int thermoStateCommand = thermostat.getCmd();
float curTemp = (float) tStore.tempX100/100.;
bool active = thermostat.isActive();
//float overHeatTemp = thermostat.getFloatArg(1);
//if (overHeatTemp == 0.) overHeatTemp = THERMO_OVERHEAT_CELSIUS;
float overHeatTemp = THERMO_OVERHEAT_CELSIUS;
debugSerial << F(" Set:") << thermoSetting << F(" Cur:") << curTemp
<< F(" cmd:") << thermoStateCommand;
if (tStore.timestamp16) //Valid temperature
{
if (isTimeOver(tStore.timestamp16,millisNZ(8) & 0xFFFF,PERIOD_THERMOSTAT_FAILED >> 8,0xFFFF))
{
errorSerial<<thermoItem->name<<F(" Alarm Expired\n");
mqttClient.publish("/alarm/snsr", thermoItem->name);
tStore.timestamp16=0; //Stop termostat
thermostat.setExt(tStore.asint);
thermoRelay(thermoPin,HEATER_ERROR);
}
else
{ // Not expired yet
if (curTemp > overHeatTemp) mqttClient.publish("/alarm/ovrht", thermoItem->name);
if (!active) thermoRelay(thermoPin,HEATER_OFF);//OFF
else if (curTemp < thermoSetting - THERMO_GIST_CELSIUS) thermoRelay(thermoPin,HEATER_HEAT);//ON
else if (curTemp >= thermoSetting) thermoRelay(thermoPin,HEATER_OFF);//OFF
else debugSerial<<F(" -target zone-")<<endl; // Nothing to do
}
}
else debugSerial<<F(" Expired\n");
thermostatCheckPrinted = true;
} //item is termostat
} //for
configLocked--;
timerThermostatCheck = millis();// + THERMOSTAT_CHECK_PERIOD;
publishStat();
#ifndef DISABLE_FREERAM_PRINT
(thermostatCheckPrinted) ? debugSerial<<F("\nRAM=")<<freeRam()<<F(" Locks:")<<configLocked: debugSerial<<F(" ")<<freeRam()<<F(" Locks:")<<configLocked;
debugSerial<<F(" Timer:")<<timerCount<<endl;
#endif
}