Files
lighthub/lighthub/main.cpp

2218 lines
62 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
*
*
* Done:
* MQMT/openhab
* 1-wire
* DMX - out
* DMX IN
* 1809 strip out (discarded)
* Modbus master Out
* DHCP
* JSON config
* cli
* PWM Out 7,8,9
* 1-w relay out
* Termostat out
Todo (backlog)
===
rotary encoder local ctrl ?
analog in local ctrl
Smooth regulation/fading
PID Termostat out ?
dmx relay out
Relay array channel
Relay DMX array channel
Config URL & MQTT password commandline configuration
1-wire Update refactoring (save memory)
Topic configuration
Timer
Modbus response check
control/debug (Commandline) over MQTT
more Modbus dimmers
todo DUE related:
PWM freq fix
Config webserver
SSL
todo ESP:
Config webserver
SSL
ESP32
PWM Out
*/
#include "main.h"
#include "statusled.h"
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>
//bool OTA_initialized=false;
#endif
#if defined(__SAM3X8E__)
DueFlashStorage EEPROM;
#endif
#ifdef ARDUINO_ARCH_ESP32
NRFFlashStorage EEPROM;
#endif
#ifdef ARDUINO_ARCH_STM32
NRFFlashStorage EEPROM;
#endif
#ifdef NRF5
NRFFlashStorage EEPROM;
#endif
#ifdef SYSLOG_ENABLE
#include <Syslog.h>
#ifndef WIFI_ENABLE
EthernetUDP udpSyslogClient;
#else
WiFiUDP udpSyslogClient;
#endif
Syslog udpSyslog(udpSyslogClient, SYSLOG_PROTO_BSD);
unsigned long timerSyslogPingTime;
static char syslogDeviceHostname[16];
Streamlog debugSerial(&debugSerialPort,LOG_DEBUG,&udpSyslog);
Streamlog errorSerial(&debugSerialPort,LOG_ERROR,&udpSyslog,ledRED);
Streamlog infoSerial (&debugSerialPort,LOG_INFO,&udpSyslog);
#else
Streamlog debugSerial(&debugSerialPort,LOG_DEBUG);
Streamlog errorSerial(&debugSerialPort,LOG_ERROR, ledRED);
Streamlog infoSerial (&debugSerialPort,LOG_INFO);
#endif
statusLED LED(ledRED);
lan_status lanStatus = INITIAL_STATE;
const char configserver[] PROGMEM = CONFIG_SERVER;
const char verval_P[] PROGMEM = QUOTE(PIO_SRC_REV);
#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
uint32_t timerPollingCheck = 0;
uint32_t timerInputCheck = 0;
uint32_t timerLanCheckTime = 0;
uint32_t timerThermostatCheck = 0;
uint32_t timerSensorCheck =0;
uint32_t WiFiAwaitingTime =0;
aJsonObject *pollingItem = NULL;
bool owReady = false;
bool configOk = false; // At least once connected to MQTT
bool configLoaded = false;
bool initializedListeners = false;
int8_t ethernetIdleCount =0;
int8_t configLocked = 0;
#if defined (_modbus)
ModbusMaster node;
#endif
byte mac[6];
PubSubClient mqttClient(ethClient);
bool wifiInitialized;
int mqttErrorRate;
#if defined(__SAM3X8E__)
void watchdogSetup(void) {} //Do not remove - strong re-definition WDT Init for DUE
#endif
void cleanConf()
{
if (!root) return;
debugSerial<<F("Unlocking config ...")<<endl;
while (configLocked)
{
//wdt_res();
cmdPoll();
#ifdef _owire
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
DMXCheck();
#endif
if (isNotRetainingStatus()) pollingLoop();
thermoLoop();
inputLoop();
yield();
}
debugSerial<<F("Stopping channels ...")<<endl;
//Stoping the channels
aJsonObject * item = items->child;
while (items && item)
{
if (item->type == aJson_Array && aJson.getArraySize(item)>0)
{
Item it(item->name);
if (it.isValid()) it.Stop();
yield();
}
item = item->next;
}
pollingItem = NULL;
debugSerial<<F("Stopped")<<endl;
#ifdef SYSLOG_ENABLE
syslogInitialized=false; //Garbage in memory
#endif
debugSerial<<F("Deleting conf. RAM was:")<<freeRam();
aJson.deleteItem(root);
root = NULL;
inputs = NULL;
items = NULL;
topics = NULL;
mqttArr = NULL;
#ifdef _dmxout
dmxArr = NULL;
#endif
#ifdef _owire
owArr = NULL;
#endif
#ifdef _modbus
modbusObj = NULL;
#endif
debugSerial<<F(" is ")<<freeRam()<<endl;
configLoaded=false;
configOk=false;
}
bool isNotRetainingStatus() {
return (lanStatus != RETAINING_COLLECTING);
}
void mqttCallback(char *topic, byte *payload, unsigned int length) {
debugSerial<<F("\n[")<<topic<<F("] ");
if (!payload) return;
payload[length] = 0;
int fr = freeRam();
if (fr < 250) {
errorSerial<<F("OutOfMemory!")<<endl;
return;
}
LED.flash(ledBLUE);
for (unsigned int i = 0; i < length; i++)
debugSerial<<((char) payload[i]);
debugSerial<<endl;
short intopic = 0;
short pfxlen = 0;
char * itemName = NULL;
char * subItem = NULL;
{
char buf[MQTT_TOPIC_LENGTH + 1];
if (lanStatus == RETAINING_COLLECTING)
{
setTopic(buf,sizeof(buf),T_OUT);
pfxlen = strlen(buf);
intopic = strncmp(topic, buf, pfxlen);
}
else
{
setTopic(buf,sizeof(buf),T_BCST);
pfxlen = strlen(buf);
intopic = strncmp(topic, buf, pfxlen );
if (intopic)
{
setTopic(buf,sizeof(buf),T_DEV);
pfxlen = strlen(buf);
intopic = strncmp(topic, buf, pfxlen);
}
}
}
// in Retaining status - trying to restore previous state from retained output topic. Retained input topics are not relevant.
if (intopic) {
debugSerial<<F("Skipping..");
return;
}
itemName=topic+pfxlen;
if(!strcmp(itemName,CMDTOPIC) && payload && (strlen((char*) payload)>1)) {
// mqttClient.publish(topic, "");
cmd_parse((char *)payload);
return;
}
if (subItem = strchr(itemName, '/'))
{
*subItem = 0;
subItem++;
if (*subItem=='$') return; //Skipping homie stuff
// debugSerial<<F("Subitem:")<<subItem<<endl;
}
// debugSerial<<F("Item:")<<itemName<<endl;
if (itemName[0]=='$') return; //Skipping homie stuff
Item item(itemName);
if (item.isValid()) {
/*
if (item.itemType == CH_GROUP && (lanStatus == RETAINING_COLLECTING))
return; //Do not restore group channels - they consist not relevant data */
item.Ctrl((char *)payload,subItem);
} //valid item
}
void printMACAddress() {
infoSerial<<F("MAC:");
for (byte i = 0; i < 6; i++)
{
if (mac[i]<16) infoSerial<<"0";
#ifdef WITH_PRINTEX_LIB
(i < 5) ?infoSerial<<hex <<(mac[i])<<F(":"):infoSerial<<hex<<(mac[i])<<endl;
#else
(i < 5) ?infoSerial<<_HEX(mac[i])<<F(":"):infoSerial<<_HEX(mac[i])<<endl;
#endif
}
}
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;
}
void setupOTA(void)
{
#ifdef OTA
//if (OTA_initialized) return;
// ArduinoOTA.end();
// start the OTEthernet library with internal (flash) based storage
ArduinoOTA.begin(Ethernet.localIP(), "Lighthub", "password", InternalStorage);
infoSerial<<F("OTA initialized\n");
//OTA_initialized=true;
#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;
udpSyslog.server(syslogServer, syslogPort);
udpSyslog.deviceHostname(syslogDeviceHostname);
if (mqttArr) deviceName = getStringFromConfig(mqttArr, 0);
if (deviceName) udpSyslog.appName(deviceName);
udpSyslog.defaultPriority(LOG_KERN);
syslogInitialized=true;
infoSerial<<F("UDP Syslog initialized.\n");
}
#endif
}
lan_status lanLoop() {
#ifdef NOETHER
lanStatus=DO_NOTHING;//-14;
#endif
//Serial.println(lanStatus);
switch (lanStatus) {
case INITIAL_STATE:
// LED.set(ledRED|((configLoaded)?ledBLINK:0));
LED.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;
}
else
// if (millis()>WiFiAwaitingTime)
if (isTimeOver(WiFiAwaitingTime,millis(),WIFI_TIMEOUT))
{
errorSerial<<F("\nProblem with WiFi!");
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
initializedListeners = true;
}
lanStatus = LIBS_INITIALIZED;
break;
case LIBS_INITIALIZED:
LED.set(ledRED|ledGREEN|((configLoaded)?ledBLINK:0));
if (configLocked) return LIBS_INITIALIZED;
if (!configOk)
lanStatus = loadConfigFromHttp(0, NULL);
else lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
break;
case IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER:
wdt_res();
LED.set(ledRED|ledGREEN|((configLoaded)?ledBLINK:0));
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);
lanStatus = OPERATION;//3;
infoSerial<<F("Accepting commands...\n");
}
break;
case OPERATION:
LED.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
timerLanCheckTime = millis();// + 5000;
lanStatus = REINIT;
LED.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;
break;
case RECONNECT:
//if (millis() > timerLanCheckTime)
if (isTimeOver(timerLanCheckTime,millis(),TIMEOUT_RECONNECT))
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
break;
case READ_RE_CONFIG: // Restore config from FLASH, re-init LAN
if (loadConfigFromEEPROM()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
else {
//timerLanCheckTime = millis();// + 5000;
lanStatus = DO_REINIT;//-10;
}
break;
case DO_NOTHING:;
}
{
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) || defined (NRF5)
wdt_dis();
if (lanStatus >= HAVE_IP_ADDRESS)
{
int 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");
lanStatus = DO_REINIT;
break;
case DHCP_CHECK_RENEW_OK:
infoSerial<<F("Renewed success. IP address:");
printIPAddress(Ethernet.localIP());
break;
case DHCP_CHECK_REBIND_FAIL:
errorSerial<<F("Error: rebind fail");
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());
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));
strncpy_P(buf, ready_P, sizeof(buf));
mqttClient.publish(topic,buf,true);
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, name_P, sizeof(topic));
strncpy_P(buf, nameval_P, sizeof(buf));
strncat_P(buf,(verval_P),sizeof(buf));
mqttClient.publish(topic,buf,true);
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, stats_P, sizeof(topic));
strncpy_P(buf, statsval_P, sizeof(buf));
mqttClient.publish(topic,buf,true);
#ifndef NO_HOMIE
// strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, homie_P, sizeof(topic));
strncpy_P(buf, homiever_P, sizeof(buf));
mqttClient.publish(topic,buf,true);
configLocked++;
if (items) {
char datatype[32]="\0";
char format [64]="\0";
aJsonObject * item = items->child;
while (items && item)
if (item->type == aJson_Array && aJson.getArraySize(item)>0) {
/// strncat(buf,item->name,sizeof(buf));
/// strncat(buf,",",sizeof(buf));
switch ( aJson.getArrayItem(item, I_TYPE)->valueint) {
case CH_THERMO:
strncpy_P(datatype,float_P,sizeof(datatype));
format[0]=0;
break;
case CH_RELAY:
case CH_GROUP:
strncpy_P(datatype,enum_P,sizeof(datatype));
strncpy_P(format,enumformat_P,sizeof(format));
break;
case CH_RGBW:
case CH_RGB:
strncpy_P(datatype,color_P,sizeof(datatype));
strncpy_P(format,hsv_P,sizeof(format));
break;
case CH_DIMMER:
case CH_MODBUS:
case CH_PWM:
case CH_VCTEMP:
case CH_VC:
strncpy_P(datatype,int_P,sizeof(datatype));
strncpy_P(format,intformat_P,sizeof(format));
break;
} //switch
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic));
strncat(topic,"/",sizeof(topic));
strncat_P(topic,datatype_P,sizeof(topic));
mqttClient.publish(topic,datatype,true);
if (format[0])
{
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat(topic,item->name,sizeof(topic));
strncat(topic,"/",sizeof(topic));
strncat_P(topic,format_P,sizeof(topic));
mqttClient.publish(topic,format,true);
}
yield();
item = item->next;
} //if
//strncpy_P(topic, outprefix, sizeof(topic));
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, nodes_P, sizeof(topic));
/// mqttClient.publish(topic,buf,true);
}
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())
{
lanStatus = RETAINING_COLLECTING;
return;
}
if (!mqttArr || ((n = aJson.getArraySize(mqttArr)) < 2)) //At least device name and broker IP must be configured
{
lanStatus = READ_RE_CONFIG;
return;
}
deviceName = getStringFromConfig(mqttArr, 0);
infoSerial<<F("Device Name:")<<deviceName<<endl;
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))
{
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));
Serial.println(buf);
mqttClient.subscribe(buf);
setTopic(buf,sizeof(buf),T_DEV);
strncat(buf, "#", sizeof(buf));
Serial.println(buf);
mqttClient.subscribe(buf);
onMQTTConnect();
// if (_once) {DMXput(); _once=0;}
lanStatus = RETAINING_COLLECTING;//4;
timerLanCheckTime = millis();// + 5000;
infoSerial<<F("Awaiting for retained topics");
} 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"));
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;
wifi_set_macaddr(STATION_IF,mac); //ESP32 to check
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;
if (ipLoadFromFlash(OFFSET_IP, ip)) {
infoSerial<<F("Loaded from flash IP:");
printIPAddress(ip);
if (ipLoadFromFlash(OFFSET_DNS, dns)) {
infoSerial<<F(" DNS:");
printIPAddress(dns);
if (ipLoadFromFlash(OFFSET_GW, gw)) {
infoSerial<<F(" GW:");
printIPAddress(gw);
if (ipLoadFromFlash(OFFSET_MASK, mask)) {
infoSerial<<F(" MASK:");
printIPAddress(mask);
Ethernet.begin(mac, ip, dns, gw, mask);
} else Ethernet.begin(mac, ip, dns, gw);
} else Ethernet.begin(mac, ip, dns);
} else Ethernet.begin(mac, ip);
infoSerial<<endl;
lanStatus = HAVE_IP_ADDRESS;
}
else {
infoSerial<<F("\nuses DHCP\n");
wdt_dis();
#if defined(ARDUINO_ARCH_STM32)
res = Ethernet.begin(mac);
#else
res = Ethernet.begin(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");
lanStatus = DO_REINIT;//-10;
//timerLanCheckTime = millis();// + DHCP_RETRY_INTERVAL;
#ifdef RESET_PIN
resetHard();
#endif
} else {
infoSerial<<F("Got IP address:");
printIPAddress(Ethernet.localIP());
lanStatus = HAVE_IP_ADDRESS;
}
}
#endif
}
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
}
#ifdef _owire
void Changed(int i, DeviceAddress addr, float currentTemp) {
char addrstr[32] = "NIL";
//char addrbuf[17];
//char valstr[16] = "NIL";
//char *owEmitString = NULL;
char *owItem = NULL;
SetBytes(addr, 8, addrstr);
addrstr[17] = 0;
if (!root) return;
//printFloatValueToStr(currentTemp,valstr);
debugSerial<<endl<<F("T:")<<currentTemp<<F("<")<<addrstr<<F(">")<<endl;
aJsonObject *owObj = aJson.getObjectItem(owArr, addrstr);
if ((currentTemp != -127.0) && (currentTemp != 85.0) && (currentTemp != 0.0))
executeCommand(owObj,-1,itemCmd(currentTemp));
/*
if (owObj) {
owEmitString = getStringFromConfig(owObj, "emit");
debugSerial<<owEmitString<<F(">")<<endl;
if ((currentTemp != -127.0) && (currentTemp != 85.0) && (currentTemp != 0.0))
{
if (owEmitString) // publish temperature to MQTT if configured
{
#ifdef WITH_DOMOTICZ
aJsonObject *idx = aJson.getObjectItem(owObj, "idx");
if (idx && && idx->type ==aJson_String && idx->valuestring) {//DOMOTICZ json format support
debugSerial << endl << idx->valuestring << F(" Domoticz valstr:");
char valstr[50];
sprintf(valstr, "{\"idx\":%s,\"svalue\":\"%.1f\"}", idx->valuestring, currentTemp);
debugSerial << valstr;
if (mqttClient.connected() && !ethernetIdleCount)
mqttClient.publish(owEmitString, valstr);
return;
}
#endif
//strcpy_P(addrstr, outprefix);
setTopic(addrstr,sizeof(addrstr),T_OUT);
strncat(addrstr, owEmitString, sizeof(addrstr));
if (mqttClient.connected() && !ethernetIdleCount)
mqttClient.publish(addrstr, valstr);
}
// And translate temp to internal items
owItem = getStringFromConfig(owObj, "item");
if (owItem)
thermoSetCurTemp(owItem, currentTemp); ///TODO: Refactore using Items interface
} // if valid temperature
} // if Address in config
else debugSerial<<addrstr<<F(">")<<endl; // No item found
*/
}
#endif //_owire
void 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 config in NVRAM\n"
"'get' [config addr]' - get config from pre-configured URL and store addr\n"
"'load' - load config from NVRAM\n"
"'pwd' - define MQTT password\n"
"'kill' - test watchdog\n"
"'clear' - clear EEPROM\n"
"'reboot' - reboot controller");
}
void printCurentLanConfig() {
infoSerial << F("Current LAN config(ip,dns,gw,subnet):");
printIPAddress(Ethernet.localIP());
// printIPAddress(Ethernet.dnsServerIP());
printIPAddress(Ethernet.gatewayIP());
printIPAddress(Ethernet.subnetMask());
}
void cmdFunctionKill(int arg_cnt, char **args) {
for (byte i = 1; i < 20; i++) {
delay(1000);
infoSerial<<i;
};
}
void cmdFunctionReboot(int arg_cnt, char **args) {
infoSerial<<F("Soft rebooting...");
softRebootFunc();
}
void applyConfig() {
if (!root) return;
configLocked++;
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 ) {
DMXoutSetup(maxChannels = aJson.getArrayItem(dmxoutArr, numParams-1)->valueint);
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(&Changed);
if (owReady) infoSerial<<F("One wire Ready\n");
t_count = 0;
while (item && owReady) {
if ((item->type == aJson_Object)) {
DeviceAddress addr;
//infoSerial<<F("Add:")),infoSerial<<item->name);
SetAddr(item->name, addr);
owAdd(addr);
}
yield();
item = item->next;
}
}
#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);
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 (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;
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("\nudp syslog ");
printBool(udpSyslogArr);
#endif
*/
infoSerial << endl;
infoSerial<<F("RAM=")<<freeRam()<<endl;
}
void cmdFunctionLoad(int arg_cnt, char **args) {
loadConfigFromEEPROM();
// restoreState();
}
int loadConfigFromEEPROM()
{
char ch;
infoSerial<<F("Loading Config from EEPROM")<<endl;
ch = EEPROM.read(EEPROM_offsetJSON);
if (ch == '{') {
aJsonEEPROMStream as = aJsonEEPROMStream(EEPROM_offsetJSON);
cleanConf();
root = aJson.parse(&as);
if (!root) {
errorSerial<<F("load failed")<<endl;
return 0;
}
infoSerial<<F("Loaded")<<endl;
applyConfig();
ethClient.stop(); //Refresh MQTT connect to get retained info
return 1;
} else {
infoSerial<<F("No stored config")<<endl;
return 0;
}
return 0;
}
void cmdFunctionReq(int arg_cnt, char **args) {
mqttConfigRequest(arg_cnt, args);
// restoreState();
}
int mqttConfigRequest(int arg_cnt, char **args)
{
char buf[25] = "/";
infoSerial<<F("\nrequest MQTT Config");
SetBytes((uint8_t *) mac, 6, buf + 1);
buf[13] = 0;
strncat(buf, "/resp/#", 25);
debugSerial<<buf;
mqttClient.subscribe(buf);
buf[13] = 0;
strncat(buf, "/req/conf", 25);
debugSerial<<buf;
mqttClient.publish(buf, "1");
return 1;
}
int mqttConfigResp(char *as) {
infoSerial<<F("got MQTT Config");
root = aJson.parse(as);
if (!root) {
errorSerial<<F("\nload failed\n");
return 0;
}
infoSerial<<F("\nLoaded");
applyConfig();
return 1;
}
#if defined(__SAM3X8E__)
#define saveBufLen 16000
void cmdFunctionSave(int arg_cnt, char **args)
{
char* outBuf = (char*) malloc(saveBufLen); /* XXX: Dynamic size. */
if (outBuf == NULL)
{
return;
}
infoSerial<<F("Saving config to EEPROM..");
aJsonStringStream stringStream(NULL, outBuf, saveBufLen);
aJson.print(root, &stringStream);
int len = strlen(outBuf);
outBuf[len++]= EOF;
EEPROM.write(EEPROM_offsetJSON,(byte*) outBuf,len);
free (outBuf);
infoSerial<<F("Saved to EEPROM");
}
#else
void cmdFunctionSave(int arg_cnt, char **args)
{
aJsonEEPROMStream jsonEEPROMStream = aJsonEEPROMStream(EEPROM_offsetJSON);
infoSerial<<F("Saving config to EEPROM..");
aJson.print(root, &jsonEEPROMStream);
jsonEEPROMStream.putEOF();
infoSerial<<F("Saved to EEPROM");
}
#endif
void 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)) saveFlash(OFFSET_MASK, ip);
else saveFlash(OFFSET_MASK, ip0);
// case 4:
if (arg_cnt>3 && inet_aton(args[3], ip)) saveFlash(OFFSET_GW, ip);
else saveFlash(OFFSET_GW, ip0);
// case 3:
if (arg_cnt>2 && inet_aton(args[2], ip)) saveFlash(OFFSET_DNS, ip);
else saveFlash(OFFSET_DNS, ip0);
// case 2:
if (arg_cnt>1 && inet_aton(args[1], ip)) saveFlash(OFFSET_IP, ip);
else saveFlash(OFFSET_IP, ip0);
// break;
// case 1: //dynamic IP
if (arg_cnt==1)
{
saveFlash(OFFSET_IP,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");
}
void cmdFunctionClearEEPROM(int arg_cnt, char **args){
for (int i = OFFSET_MAC; i < OFFSET_MAC+EEPROM_FIX_PART_LEN+1; i++) //Fi[ part +{]
EEPROM.write(i, 0);
for (int i = 0; i < EEPROM_SIGNATURE_LENGTH; i++)
EEPROM.write(i+OFFSET_SIGNATURE,EEPROM_signature[i]);
infoSerial<<F("EEPROM cleared\n");
}
void cmdFunctionPwd(int arg_cnt, char **args)
{ char empty[]="";
if (arg_cnt)
saveFlash(OFFSET_MQTT_PWD,args[1]);
else saveFlash(OFFSET_MQTT_PWD,empty);
infoSerial<<F("Password updated\n");
}
void cmdFunctionSetMac(int arg_cnt, char **args) {
char dummy;
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;
}
printMACAddress();
for (short i = 0; i < 6; i++) { EEPROM.write(i, mac[i]); }
infoSerial<<F("Updated\n");
}
void cmdFunctionGet(int arg_cnt, char **args) {
lanStatus= loadConfigFromHttp(arg_cnt, args);
ethClient.stop(); //Refresh MQTT connect to get retained info
//restoreState();
}
void printBool(bool arg) { (arg) ? infoSerial<<F("+") : infoSerial<<F("-"); }
void saveFlash(short n, char *str) {
short len=strlen(str);
if (len>MAXFLASHSTR-1) len=MAXFLASHSTR-1;
for(int i=0;i<len;i++) EEPROM.write(n+i,str[i]);
EEPROM.write(n+len,0);
#if defined(ARDUINO_ARCH_ESP8266)
// write the data to EEPROM
short res = EEPROM.commitReset();
Serial.println((res) ? "EEPROM Commit OK" : "Commit failed");
#endif
}
int loadFlash(short n, char *str, short l) {
short i;
uint8_t ch = EEPROM.read(n);
if (!ch || (ch == 0xff)) return 0;
for (i=0;i<l-1 && (str[i] = EEPROM.read(n++));i++);
str[i]=0;
return 1;
}
void saveFlash(short n, IPAddress& ip) {
for(int i=0;i<4;i++) EEPROM.write(n++,ip[i]);
#if defined(ARDUINO_ARCH_ESP8266)
// write the data to EEPROM
short res = EEPROM.commitReset();
Serial.println((res) ? "EEPROM Commit OK" : "Commit failed");
#endif
}
int ipLoadFromFlash(short n, IPAddress &ip) {
for (int i = 0; i < 4; i++)
ip[i] = EEPROM.read(n++);
return (ip[0] && ((ip[0] != 0xff) || (ip[1] != 0xff) || (ip[2] != 0xff) || (ip[3] != 0xff)));
}
lan_status loadConfigFromHttp(int arg_cnt, char **args)
{
int responseStatusCode = 0;
char URI[64];
char configServer[32]="";
if (arg_cnt > 1) {
strncpy(configServer, args[1], sizeof(configServer) - 1);
saveFlash(OFFSET_CONFIGSERVER, configServer);
infoSerial<<configServer<<F(" Saved")<<endl;
} else if (!loadFlash(OFFSET_CONFIGSERVER, 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", mac[0], mac[1], mac[2], mac[3], mac[4],
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);
// FILE is the return STREAM type of the HTTPClient
configStream = hclient.getURI(URI);
responseStatusCode = hclient.getLastReturnCode();
//wdt_en();
if (configStream != NULL) {
if (responseStatusCode == 200) {
infoSerial<<F("got Config\n");
char c;
aJsonFileStream as = aJsonFileStream(configStream);
noInterrupts();
cleanConf();
root = aJson.parse(&as);
interrupts();
hclient.closeStream(configStream); // this is very important -- be sure to close the STREAM
if (!root) {
errorSerial<<F("Config parsing failed\n");
// timerLanCheckTime = millis();// + 15000;
return READ_RE_CONFIG;//-11;
} else {
infoSerial<<F("Applying.\n");
applyConfig();
infoSerial<<F("Done.\n");
}
} else {
errorSerial<<F("ERROR: Server returned ");
errorSerial<<responseStatusCode<<endl;
// timerLanCheckTime = millis();// + 5000;
return READ_RE_CONFIG;//-11;
}
} else {
debugSerial<<F("failed to connect\n");
// debugSerial<<F(" try again in 5 seconds\n");
// timerLanCheckTime = millis();// + 5000;
return READ_RE_CONFIG;//-11;
}
#endif
#if defined(__SAM3X8E__) || defined(ARDUINO_ARCH_STM32) || defined (NRF5) //|| 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
htclient.beginRequest();
responseStatusCode = htclient.get(URI);
htclient.endRequest();
if (responseStatusCode == HTTP_SUCCESS)
{
// read the status code and body of the response
responseStatusCode = htclient.responseStatusCode();
response = htclient.responseBody();
htclient.stop();
wdt_res();
infoSerial<<F("HTTP Status code: ")<<responseStatusCode<<endl;
//delay(1000);
if (responseStatusCode == 200) {
debugSerial<<F("GET Response: ")<<response<<endl;
debugSerial<<F("Free:")<<freeRam()<<endl;
cleanConf();
debugSerial<<F("Configuration cleaned")<<endl;
debugSerial<<F("Free:")<<freeRam()<<endl;
root = aJson.parse((char *) response.c_str());
if (!root) {
errorSerial<<F("Config parsing failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
} else {
debugSerial<<F("Parsed. Free:")<<freeRam()<<endl;
//debugSerial<<response;
applyConfig();
infoSerial<<F("Done.\n");
}
} else {
errorSerial<<F("Config retrieving failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
}
} else {
errorSerial<<F("Connect failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
}
#endif
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) //|| defined (NRF5)
HTTPClient httpClient;
String fullURI = "http://";
fullURI+=configServer;
fullURI+=URI;
httpClient.begin(fullURI);
int httpResponseCode = httpClient.GET();
if (httpResponseCode > 0) {
infoSerial.printf("[HTTP] GET... code: %d\n", httpResponseCode);
if (httpResponseCode == HTTP_CODE_OK) {
String response = httpClient.getString();
debugSerial<<response;
cleanConf();
root = aJson.parse((char *) response.c_str());
if (!root) {
errorSerial<<F("Config parsing failed\n");
return READ_RE_CONFIG;
} else {
infoSerial<<F("Config OK, Applying\n");
applyConfig();
infoSerial<<F("Done.\n");
}
} else {
errorSerial<<F("Config retrieving failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
}
} else {
errorSerial.printf("[HTTP] GET... failed, error: %s\n", httpClient.errorToString(httpResponseCode).c_str());
httpClient.end();
return READ_RE_CONFIG;
}
httpClient.end();
#endif
return IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
}
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 setup_main() {
#if defined(__SAM3X8E__)
memset(&UniqueID,0,sizeof(UniqueID));
#endif
#if defined(M5STACK)
// Initialize the M5Stack object
M5.begin();
#endif
setupCmdArduino();
printFirmwareVersionAndBuildOptions();
//Checkin EEPROM integrity (signature)
for (int i=0;i<EEPROM_SIGNATURE_LENGTH;i++)
if (EEPROM.read(i+OFFSET_SIGNATURE)!=EEPROM_signature[i])
{
cmdFunctionClearEEPROM(0,NULL);
break;
}
// scan_i2c_bus();
#ifdef SD_CARD_INSERTED
sd_card_w5100_setup();
#endif
setupMacAddress();
#if defined(ARDUINO_ARCH_ESP8266)
EEPROM.begin(ESP_EEPROM_SIZE);
#endif
#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);
node.idle(&modbusIdle);
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
#endif
delay(20);
//owReady = 0;
#ifdef _owire
setupOwIdle(&owIdle);
#endif
mqttClient.setCallback(mqttCallback);
#ifdef _artnet
ArtnetSetup();
#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
//TODO: checkForRemoteSketchUpdate();
#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;
#ifdef CONTROLLINO
infoSerial<<F("\n(+)CONTROLLINO");
#endif
#ifndef WATCH_DOG_TICKER_DISABLE
infoSerial<<F("\n(+)WATCHDOG");
#else
infoSerial<<F("\n(-)WATCHDOG");
#endif
infoSerial<<F("\nConfig server:")<<F(CONFIG_SERVER)<<F("\nFirmware MAC Address ")<<F(QUOTE(CUSTOM_FIRMWARE_MAC));
#ifdef DISABLE_FREERAM_PRINT
infoSerial<<F("\n(-)FreeRam printing");
#else
infoSerial<<F("\n(+)FreeRam printing");
#endif
#ifdef USE_1W_PIN
infoSerial<<F("\n(-)DS2482-100 USE_1W_PIN=")<<QUOTE(USE_1W_PIN);
#else
infoSerial<<F("\n(+)DS2482-100");
#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");
#else
infoSerial<<F("\n(-)MODBUS");
#endif
#ifndef OWIRE_DISABLE
infoSerial<<F("\n(+)OWIRE");
#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");
#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
//udpSyslogClient.begin(SYSLOG_LOCAL_SOCKET);
infoSerial<<F("\n(+)SYSLOG");
#else
infoSerial<<F("\n(-)SYSLOG");
#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));
strncat(topic, "/", sizeof(topic));
strncat_P(topic, freeheap_P, sizeof(topic));
printUlongValueToStr(intbuf, fr);
mqttClient.publish(topic,intbuf,true);
setTopic(topic,sizeof(topic),T_DEV);
strncat_P(topic, stats_P, sizeof(topic));
strncat(topic, "/", sizeof(topic));
strncat_P(topic, uptime_P, sizeof(topic));
printUlongValueToStr(intbuf, ut);
mqttClient.publish(topic,intbuf,true);
}
void setupMacAddress() {
//Check MAC, stored in NVRAM
bool isMacValid = false;
for (short i = 0; i < 6; i++) {
mac[i] = EEPROM.read(i);
if (mac[i] != 0 && mac[i] != 0xff) isMacValid = true;
}
if (!isMacValid) {
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, ':', mac, 6, 16);
mac[0]&=0xFE;
mac[0]|=2;
#elif defined(WIFI_ENABLE)
//Using original MPU MAC
WiFi.begin();
WiFi.macAddress(mac);
#elif defined(__SAM3X8E__)
//Lets make MAC from MPU serial#
mac[0]=0xDE;
for (byte b = 0 ; b < 5 ; b++)
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(mac,defaultMac,6);
#endif
}
printMACAddress();
}
void setupCmdArduino() {
cmdInit(uint32_t(SERIAL_BAUD));
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("clear",cmdFunctionClearEEPROM);
cmdAdd("reboot",cmdFunctionReboot);
}
void loop_main() {
LED.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
#endif
}
#ifdef _owire
yield();
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
yield();
DMXCheck();
#endif
if (items) {
if (isNotRetainingStatus()) pollingLoop();
yield();
thermoLoop();
}
yield();
inputLoop();
#if defined (_espdmx)
yield();
dmxout.update();
#endif
}
void owIdle(void) {
#ifdef _artnet
if (artnet && (lanStatus>=HAVE_IP_ADDRESS)) artnet->read();
#endif
wdt_res();
return;
#ifdef _dmxin
yield();
DMXCheck();
#endif
#if defined (_espdmx)
yield();
dmxout.update();
#endif
}
void ethernetIdle(void){
ethernetIdleCount++;
wdt_res();
yield();
inputLoop();
ethernetIdleCount--;
};
void modbusIdle(void) {
LED.poll();
yield();
cmdPoll();
wdt_res();
if (lanLoop() > HAVE_IP_ADDRESS) {
yield();
mqttClient.loop();
#ifdef _artnet
if (artnet) artnet->read();
#endif
yield();
inputLoop();
}
#ifdef _dmxin
DMXCheck();
#endif
#if defined (_espdmx)
dmxout.update();
#endif
}
void inputLoop(void) {
if (!inputs) return;
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(CHECK_INPUT);
yield();
inputObj = inputObj->next;
}
}
else
{
Input in(input);
in.Poll(CHECK_INPUT);
}
}
yield();
input = input->next;
}
timerInputCheck = millis();// + INTERVAL_CHECK_INPUT;
inCache.invalidateInputCache();
}
//if (millis() > timerSensorCheck)
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();
input = input->next;
}
timerSensorCheck = millis();// + INTERVAL_CHECK_SENSOR;
}
configLocked--;
}
void inputSetup(void) {
if (!inputs) return;
configLocked++;
aJsonObject *input = inputs->child;
while (input) {
if ((input->type == aJson_Object)) {
Input in(input);
in.setup();
}
yield();
input = input->next;
}
configLocked--;
}
void pollingLoop(void) {
// FAST POLLINT - as often AS possible every item
configLocked++;
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.Poll(POLLING_FAST);
} //isValid
yield();
item = item->next;
} //if
}
configLocked--;
// SLOW POLLING
boolean done = false;
if (lanStatus == RETAINING_COLLECTING) return;
//if (millis() > timerPollingCheck)
if (isTimeOver(timerPollingCheck,millis(),INTERVAL_SLOW_POLLING))
{
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
}
bool isThermostatWithMinArraySize(aJsonObject *item, int minimalArraySize) {
return (item->type == aJson_Array) && (aJson.getArrayItem(item, I_TYPE)->valueint == CH_THERMO) &&
(aJson.getArraySize(item) >= minimalArraySize);
}
bool thermoDisabledOrDisconnected(aJsonObject *thermoExtensionArray, int thermoStateCommand) {
if (aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint == 0) return true;
switch (thermoStateCommand) {
case CMD_ON:
case CMD_XON:
case CMD_AUTO:
case CMD_HEAT:
return false;
default: return true;
}
}
//TODO: refactoring
void thermoLoop(void) {
// if (millis() < timerThermostatCheck)
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 (isThermostatWithMinArraySize(thermoItem, 5)) {
aJsonObject *thermoExtensionArray = aJson.getArrayItem(thermoItem, I_EXT);
if (thermoExtensionArray && (aJson.getArraySize(thermoExtensionArray) > 1)) {
Item thermostat(thermoItem);
if (!thermostat.isValid()) continue;
itemCmd thermostatCmd(&thermostat);
//int thermoPin = aJson.getArrayItem(thermoItem, I_ARG)->valueint;
int thermoPin = thermostat.getArg(0);
//float thermoSetting = aJson.getArrayItem(thermoItem, I_VAL)->valueint; ///
float thermoSetting = thermostatCmd.getFloat();
int thermoStateCommand = aJson.getArrayItem(thermoItem, I_CMD)->valueint;
float curTemp = aJson.getArrayItem(thermoExtensionArray, IET_TEMP)->valuefloat;
if (!aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint) {
errorSerial<<thermoItem->name<<F(" Expired\n");
} else {
if (!(--aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint))
mqttClient.publish("/alarm/snsr", thermoItem->name);
}
if (curTemp > THERMO_OVERHEAT_CELSIUS) mqttClient.publish("/alarm/ovrht", thermoItem->name);
debugSerial << endl << thermoItem->name << F(" Set:") << thermoSetting << F(" Cur:") << curTemp
<< F(" cmd:") << thermoStateCommand;
if (thermoPin<0) pinMode(-thermoPin, OUTPUT); else pinMode(thermoPin, OUTPUT);
if (thermoDisabledOrDisconnected(thermoExtensionArray, thermoStateCommand)) {
if (thermoPin<0) digitalWrite(-thermoPin, LOW); else digitalWrite(thermoPin, LOW);
// Caution - for water heaters (negative pin#) if some comes wrong (or no connection with termometers output is LOW - valve OPEN)
// OFF - also VALVE is OPEN (no teat control)
debugSerial<<F(" OFF");
} else {
if (curTemp < thermoSetting - THERMO_GIST_CELSIUS) {
if (thermoPin<0) digitalWrite(-thermoPin, LOW); else digitalWrite(thermoPin, HIGH);
debugSerial<<F(" ON");
} //too cold
else if (curTemp >= thermoSetting) {
if (thermoPin<0) digitalWrite(-thermoPin, HIGH); else digitalWrite(thermoPin, LOW);
debugSerial<<F(" OFF");
} //Reached settings
else debugSerial<<F(" -target zone-"); // Nothing to do
}
thermostatCheckPrinted = true;
}
}
}
configLocked--;
timerThermostatCheck = millis();// + THERMOSTAT_CHECK_PERIOD;
publishStat();
#ifndef DISABLE_FREERAM_PRINT
(thermostatCheckPrinted) ? debugSerial<<F("\nRAM=")<<freeRam()<<" " : debugSerial<<F(" ")<<freeRam()<<F(" ");
#endif
}
short thermoSetCurTemp(char *name, float t) {
if (!name || !strlen(name)) return 0;
if (items) {
aJsonObject *thermoItem = aJson.getObjectItem(items, name);
if (!thermoItem) return 0;
if (isThermostatWithMinArraySize(thermoItem, 4)) {
aJsonObject *extArray = NULL;
if (aJson.getArraySize(thermoItem) == 4) //No thermo extension yet
{
extArray = aJson.createArray(); //Create Ext Array
aJsonObject *ocurt = aJson.createItem(t); //Create float
aJsonObject *oattempts = aJson.createItem((long int) T_ATTEMPTS); //Create int
aJson.addItemToArray(extArray, ocurt);
aJson.addItemToArray(extArray, oattempts);
aJson.addItemToArray(thermoItem, extArray); //Adding to thermoItem
}
else if (extArray = aJson.getArrayItem(thermoItem, I_EXT)) {
aJsonObject *att = aJson.getArrayItem(extArray, IET_ATTEMPTS);
aJson.getArrayItem(extArray, IET_TEMP)->valuefloat = t;
if (att->valueint == 0) mqttClient.publish("/alarmoff/snsr", thermoItem->name);
att->valueint = (int) T_ATTEMPTS;
}
}
return 1;}
return 0;}