Files
lighthub/lighthub/main.cpp

2000 lines
57 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"
#if defined(OTA)
#include <ArduinoOTA.h>
#endif
#if defined(__SAM3X8E__)
DueFlashStorage EEPROM;
EthernetClient ethClient;
#endif
#if defined(ARDUINO_ARCH_AVR)
EthernetClient ethClient;
#endif
#ifdef ARDUINO_ARCH_ESP8266
WiFiClient ethClient;
#if not defined(WIFI_MANAGER_DISABLE)
WiFiManager wifiManager;
#endif
#endif
#ifdef ARDUINO_ARCH_ESP32
WiFiClient ethClient;
NRFFlashStorage EEPROM;
#if not defined(WIFI_MANAGER_DISABLE)
WiFiManager wifiManager;
#endif
#endif
#ifdef ARDUINO_ARCH_STM32
EthernetClient ethClient;
NRFFlashStorage EEPROM;
#endif
#ifdef NRF5
NRFFlashStorage EEPROM;
EthernetClient ethClient;
#endif
#ifdef SYSLOG_ENABLE
#include <Syslog.h>
EthernetUDP udpSyslogClient;
Syslog udpSyslog(udpSyslogClient, SYSLOG_PROTO_IETF);
unsigned long nextSyslogPingTime;
#endif
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;
#ifndef MODBUS_DISABLE
aJsonObject *modbusArr = NULL;
#endif
#ifdef _owire
aJsonObject *owArr = NULL;
#endif
#ifdef _dmxout
aJsonObject *dmxArr = NULL;
#endif
#ifdef SYSLOG_ENABLE
aJsonObject *udpSyslogArr = NULL;
#endif
uint32_t nextPollingCheck = 0;
uint32_t nextInputCheck = 0;
uint32_t nextLanCheckTime = 0;
uint32_t nextThermostatCheck = 0;
uint32_t nextSensorCheck =0;
aJsonObject *pollingItem = NULL;
bool owReady = false;
bool configOk = false;
int8_t ethernetIdleCount =0;
int8_t configLocked = 0;
#ifdef _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 ...");
while (configLocked)
{
//wdt_res();
cmdPoll();
#ifdef _owire
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
DMXCheck();
#endif
if (lanStatus != RETAINING_COLLECTING) pollingLoop();
thermoLoop();
inputLoop();
}
pollingItem = NULL;;
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
#ifndef MODBUS_DISABLE
modbusArr = NULL;
#endif
debugSerial<<F(" is ")<<freeRam()<<endl;
}
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) {
debugSerial<<F("OOM!");
return;
}
for (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, !(lanStatus == RETAINING_COLLECTING),subItem);
} //valid item
}
void printMACAddress() {
debugSerial<<F("MAC:");
for (byte i = 0; i < 6; i++)
{
if (mac[i]<16) debugSerial<<"0";
#ifdef WITH_PRINTEX_LIB
(i < 5) ?debugSerial<<hex <<(mac[i])<<F(":"):debugSerial<<hex<<(mac[i])<<endl;
#else
(i < 5) ?debugSerial<<_HEX(mac[i])<<F(":"):debugSerial<<_HEX(mac[i])<<endl;
#endif
}
}
lan_status lanLoop() {
#ifdef NOETHER
lanStatus=DO_NOTHING;//-14;
#endif
switch (lanStatus) {
case INITIAL_STATE:
if (millis() > nextLanCheckTime)
onInitialStateInitLAN();
break;
case HAVE_IP_ADDRESS:
if (configLocked) return HAVE_IP_ADDRESS;
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();
ip_ready_config_loaded_connecting_to_broker();
break;
case RETAINING_COLLECTING:
if (millis() > nextLanCheckTime) {
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;
debugSerial<<F("Accepting commands...\n");
break;
}
case OPERATION:
if (!mqttClient.connected()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
break;
case AWAITING_ADDRESS:
if (millis() > nextLanCheckTime)
lanStatus = INITIAL_STATE;//0;
break;
case RECONNECT:
if (millis() > nextLanCheckTime)
lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
break;
case READ_RE_CONFIG:
if (loadConfigFromEEPROM()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
else {
nextLanCheckTime = millis() + 5000;
lanStatus = AWAITING_ADDRESS;//-10;
}
break;
case DO_NOTHING:;
}
{
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
if (WiFi.status() != WL_CONNECTED)
{
wifiInitialized=false;
lanStatus = INITIAL_STATE;
}
#endif
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) || defined (NRF5)
wdt_dis();
if (lanStatus > 0)
switch (Ethernet.maintain()) {
case NO_LINK:
debugSerial<<F("No link")<<endl;
if (mqttClient.connected()) mqttClient.disconnect();
nextLanCheckTime = millis() + 30000;
lanStatus = AWAITING_ADDRESS;//-10;
break;
case DHCP_CHECK_RENEW_FAIL:
debugSerial<<F("Error: renewed fail");
if (mqttClient.connected()) mqttClient.disconnect();
nextLanCheckTime = millis() + 1000;
lanStatus = AWAITING_ADDRESS;//-10;
break;
case DHCP_CHECK_RENEW_OK:
debugSerial<<F("Renewed success. IP address:");
printIPAddress(Ethernet.localIP());
break;
case DHCP_CHECK_REBIND_FAIL:
debugSerial<<F("Error: rebind fail");
if (mqttClient.connected()) mqttClient.disconnect();
nextLanCheckTime = millis() + 1000;
lanStatus = AWAITING_ADDRESS;//-10;
break;
case DHCP_CHECK_REBIND_OK:
debugSerial<<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);
}
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
}
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 ip_ready_config_loaded_connecting_to_broker() {
short n = 0;
int port = 1883;
char empty = 0;
char *user = &empty;
char passwordBuf[16] = "";
char *password = passwordBuf;
int syslogPort = 514;
char syslogDeviceHostname[16];
if (mqttArr && (aJson.getArraySize(mqttArr)))
{
deviceName = getStringFromConfig(mqttArr, 0);
debugSerial<<F("Device Name:")<<deviceName<<endl;
}
#ifdef SYSLOG_ENABLE
//debugSerial<<"debugSerial:";
delay(100);
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));
/*
char *syslogDeviceHostname = aJson.getArrayItem(udpSyslogArr, 2)->valuestring;
char *syslogAppname = aJson.getArrayItem(udpSyslogArr, 3)->valuestring;
*/
debugSerial<<F("Syslog params:")<<syslogServer<<":"<<syslogPort<<":"<<syslogDeviceHostname<<":"<<deviceName<<endl;
udpSyslog.server(syslogServer, syslogPort);
udpSyslog.deviceHostname(syslogDeviceHostname);
if (deviceName) udpSyslog.appName(deviceName);
udpSyslog.defaultPriority(LOG_KERN);
udpSyslog.log(LOG_INFO, F("UDP Syslog initialized!"));
debugSerial<<F("UDP Syslog initialized!\n");
}
#endif
if (!mqttClient.connected() && mqttArr && ((n = aJson.getArraySize(mqttArr)) > 1)) {
// char *client_id = aJson.getArrayItem(mqttArr, 0)->valuestring;
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);
debugSerial<<F("Using MQTT password from config");
}
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));
debugSerial<<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;
debugSerial<<F("connected as ")<<deviceName <<endl;
// wdt_en();
configOk = true;
// ... Temporary subscribe to status topic
char buf[MQTT_TOPIC_LENGTH+1];
// strncpy_P(buf, outprefix, sizeof(buf));
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);
//restoreState();
onMQTTConnect();
// if (_once) {DMXput(); _once=0;}
lanStatus = RETAINING_COLLECTING;//4;
nextLanCheckTime = millis() + 5000;
debugSerial<<F("Awaiting for retained topics");
} else {
debugSerial<<F("failed, rc=")<<mqttClient.state()<<F(" try again in 5 seconds")<<endl;
nextLanCheckTime = millis() + 5000;
#ifdef RESTART_LAN_ON_MQTT_ERRORS
mqttErrorRate++;
if(mqttErrorRate>50){
debugSerial<<F("Too many MQTT connection errors. Restart LAN"));
mqttErrorRate=0;
#ifdef RESET_PIN
resetHard();
#endif
lanStatus=INITIAL_STATE;
return;
}
#endif
lanStatus = RECONNECT;//12;
}
}
}
void onInitialStateInitLAN() {
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
#if defined(WIFI_MANAGER_DISABLE)
if(WiFi.status() != WL_CONNECTED) {
WiFi.mode(WIFI_STA); // ESP 32 - WiFi.disconnect(); instead
debugSerial<<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<<".";
}
wifiInitialized = true; //???
}
#else
// Wifi Manager
if (WiFi.status() != WL_CONNECTED)
{
wifiManager.setTimeout(30);
#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
#endif
/*
#if defined(ARDUINO_ARCH_ESP32) and defined(WIFI_MANAGER_DISABLE)
if(!wifiInitialized) {
// WiFi.mode(WIFI_STA);
WiFi.disconnect();
debugSerial<<F("WIFI AP/Password:")<<QUOTE(ESP_WIFI_AP)<<F("/")<<QUOTE(ESP_WIFI_PWD)<<endl;
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<<".";
}
wifiInitialized = true;
}
#endif
*/
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
if (WiFi.status() == WL_CONNECTED) {
debugSerial<<F("WiFi connected. IP address: ")<<WiFi.localIP()<<endl;
lanStatus = HAVE_IP_ADDRESS;//1;
#ifdef OTA
// start the OTEthernet library with internal (flash) based storage
ArduinoOTA.begin(Ethernet.localIP(), "Lighthub", "password", InternalStorage);
#endif
} else
{
debugSerial<<F("Problem with WiFi!");
nextLanCheckTime = millis() + DHCP_RETRY_INTERVAL;
}
#endif
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__)||defined(ARDUINO_ARCH_STM32) || defined (NRF5)
#ifdef W5500_CS_PIN
Ethernet.w5500_cspin = W5500_CS_PIN;
debugSerial<<F("Use W5500 pin: ");
debugSerial<<(Ethernet.w5500_cspin)<<endl;
#endif
IPAddress ip, dns, gw, mask;
int res = 1;
debugSerial<<F("Starting lan")<<endl;
if (ipLoadFromFlash(OFFSET_IP, ip)) {
debugSerial<<"Loaded from flash IP:";
printIPAddress(ip);
if (ipLoadFromFlash(OFFSET_DNS, dns)) {
debugSerial<<" DNS:";
printIPAddress(dns);
if (ipLoadFromFlash(OFFSET_GW, gw)) {
debugSerial<<" GW:";
printIPAddress(gw);
if (ipLoadFromFlash(OFFSET_MASK, mask)) {
debugSerial<<" 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);
debugSerial<<endl;
lanStatus = HAVE_IP_ADDRESS;
#ifdef OTA
// start the OTEthernet library with internal (flash) based storage
ArduinoOTA.begin(Ethernet.localIP(), "Lighthub", "password", InternalStorage);
#endif
#ifdef _artnet
if (artnet) artnet->begin();
#endif
}
else {
debugSerial<<"\nNo IP data found in flash\n";
wdt_dis();
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) || defined (NRF5)
res = Ethernet.begin(mac, 12000);
#endif
#if defined(ARDUINO_ARCH_STM32)
res = Ethernet.begin(mac);
#endif
wdt_en();
wdt_res();
if (res == 0) {
debugSerial<<F("Failed to configure Ethernet using DHCP. You can set ip manually!")<<F("'ip [ip[,dns[,gw[,subnet]]]]' - set static IP\n");
lanStatus = AWAITING_ADDRESS;//-10;
nextLanCheckTime = millis() + DHCP_RETRY_INTERVAL;
#ifdef RESET_PIN
resetHard();
#endif
} else {
debugSerial<<F("Got IP address:");
printIPAddress(Ethernet.localIP());
lanStatus = HAVE_IP_ADDRESS;//1;
#ifdef OTA
// start the OTEthernet library with internal (flash) based storage
ArduinoOTA.begin(Ethernet.localIP(), "Lighthub", "password", InternalStorage);
#endif
#ifdef _artnet
if (artnet) artnet->begin();
#endif
}
}
#endif
}
void resetHard() {
#ifdef RESET_PIN
debugSerial<<F("Reset Arduino with digital pin ");
debugSerial<<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:")<<valstr<<F("<");
aJsonObject *owObj = aJson.getObjectItem(owArr, addrstr);
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();
#ifdef SYSLOG_ENABLE
// udpSyslog.logf(LOG_INFO, "free RAM: %d",freeRam());
#endif
printCurentLanConfig();
// printFreeRam();
debugSerial<<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() {
debugSerial << 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);
debugSerial<<i;
};
}
void cmdFunctionReboot(int arg_cnt, char **args) {
debugSerial<<F("Soft rebooting...");
softRebootFunc();
}
void applyConfig() {
if (!root) return;
configLocked++;
#ifdef _dmxin
int itemsCount;
dmxArr = aJson.getObjectItem(root, "dmxin");
if (dmxArr && (itemsCount = aJson.getArraySize(dmxArr))) {
DMXinSetup(itemsCount * 4);
debugSerial<<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);
debugSerial<<F("DMX out started. Channels: ")<<maxChannels<<endl;
}
#endif
#ifdef _modbus
modbusArr = aJson.getObjectItem(root, "modbus");
#endif
#ifdef _owire
owArr = aJson.getObjectItem(root, "ow");
if (owArr && !owReady) {
aJsonObject *item = owArr->child;
owReady = owSetup(&Changed);
if (owReady) debugSerial<<F("One wire Ready\n");
t_count = 0;
while (item && owReady) {
if ((item->type == aJson_Object)) {
DeviceAddress addr;
//debugSerial<<F("Add:")),debugSerial<<item->name);
SetAddr(item->name, addr);
owAdd(addr);
}
item = item->next;
}
}
#endif
items = aJson.getObjectItem(root, "items");
topics = aJson.getObjectItem(root, "topics");
// 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
item = item->next;
} //if
pollingItem = items->child;
}
inputs = aJson.getObjectItem(root, "in");
mqttArr = aJson.getObjectItem(root, "mqtt");
inputSetup();
#ifdef SYSLOG_ENABLE
udpSyslogArr = aJson.getObjectItem(root, "syslog");
#endif
printConfigSummary();
configLocked--;
}
void printConfigSummary() {
debugSerial<<F("\nConfigured:")<<F("\nitems ");
printBool(items);
debugSerial<<F("\ninputs ");
printBool(inputs);
#ifndef MODBUS_DISABLE
debugSerial<<F("\nmodbus ");
printBool(modbusArr);
#endif
debugSerial<<F("\nmqtt ");
printBool(mqttArr);
#ifdef _owire
debugSerial<<F("\n1-wire ");
printBool(owArr);
#endif
#ifdef SYSLOG_ENABLE
debugSerial<<F("\nudp syslog ");
printBool(udpSyslogArr);
#endif
debugSerial << endl;
debugSerial<<F("RAM=")<<freeRam()<<endl;
}
void cmdFunctionLoad(int arg_cnt, char **args) {
loadConfigFromEEPROM();
// restoreState();
}
int loadConfigFromEEPROM()
{
char ch;
debugSerial<<F("Loading Config from EEPROM")<<endl;
ch = EEPROM.read(EEPROM_offset);
if (ch == '{') {
aJsonEEPROMStream as = aJsonEEPROMStream(EEPROM_offset);
cleanConf();
root = aJson.parse(&as);
if (!root) {
debugSerial<<F("load failed")<<endl;
return 0;
}
debugSerial<<F("Loaded")<<endl;
applyConfig();
ethClient.stop(); //Refresh MQTT connect to get retained info
return 1;
} else {
debugSerial<<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] = "/";
debugSerial<<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) {
debugSerial<<F("got MQTT Config");
root = aJson.parse(as);
if (!root) {
debugSerial<<F("\nload failed\n");
return 0;
}
debugSerial<<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;
}
debugSerial<<F("Saving config to EEPROM..");
aJsonStringStream stringStream(NULL, outBuf, saveBufLen);
aJson.print(root, &stringStream);
int len = strlen(outBuf);
outBuf[len++]= EOF;
EEPROM.write(EEPROM_offset,(byte*) outBuf,len);
free (outBuf);
debugSerial<<F("Saved to EEPROM");
}
#else
void cmdFunctionSave(int arg_cnt, char **args)
{
aJsonEEPROMStream jsonEEPROMStream = aJsonEEPROMStream(EEPROM_offset);
debugSerial<<F("Saving config to EEPROM..");
aJson.print(root, &jsonEEPROMStream);
jsonEEPROMStream.putEOF();
debugSerial<<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);
debugSerial<<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); */
//}
debugSerial<<F("Saved\n");
}
void cmdFunctionClearEEPROM(int arg_cnt, char **args){
for (int i = 0; i < 512; i++)
EEPROM.write(i, 0);
debugSerial<<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);
debugSerial<<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) {
debugSerial<<F("could not parse: ")<<args[1];
return;
}
printMACAddress();
for (short i = 0; i < 6; i++) { EEPROM.write(i, mac[i]); }
debugSerial<<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) ? debugSerial<<F("+") : debugSerial<<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 ch;
char URI[64];
char configServer[32]="";
if (arg_cnt > 1) {
strncpy(configServer, args[1], sizeof(configServer) - 1);
saveFlash(OFFSET_CONFIGSERVER, configServer);
debugSerial<<configServer<<F(" Saved")<<endl;
} else if (!loadFlash(OFFSET_CONFIGSERVER, configServer))
{
strncpy_P(configServer,configserver,sizeof(configServer));
debugSerial<<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
debugSerial<<F("Config URI: http://")<<configServer<<URI<<endl;
#if defined(ARDUINO_ARCH_AVR)
FILE *configStream;
wdt_dis();
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) {
debugSerial<<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) {
debugSerial<<F("Config parsing failed\n");
nextLanCheckTime = millis() + 15000;
return READ_RE_CONFIG;//-11;
} else {
debugSerial<<F("Applying.\n");
applyConfig();
debugSerial<<F("Done.\n");
}
} else {
debugSerial<<F("ERROR: Server returned ");
debugSerial<<responseStatusCode;
nextLanCheckTime = millis() + 5000;
return READ_RE_CONFIG;//-11;
}
} else {
debugSerial<<F("failed to connect\n");
// debugSerial<<F(" try again in 5 seconds\n");
nextLanCheckTime = 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(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
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();
debugSerial<<F("HTTP Status code: ");
debugSerial<<responseStatusCode<<" ";
//debugSerial<<"GET Response: ");
if (responseStatusCode == 200) {
cleanConf();
root = aJson.parse((char *) response.c_str());
if (!root) {
debugSerial<<F("Config parsing failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
} else {
debugSerial<<response;
applyConfig();
debugSerial<<F("Done.\n");
}
} else {
debugSerial<<F("Config retrieving failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
}
} else {
debugSerial<<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) {
debugSerial.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) {
debugSerial<<F("Config parsing failed\n");
return READ_RE_CONFIG;
} else {
debugSerial<<F("Config OK, Applying\n");
applyConfig();
debugSerial<<F("Done.\n");
}
} else {
debugSerial<<F("Config retrieving failed\n");
return READ_RE_CONFIG;//-11; //Load from NVRAM
}
} else {
debugSerial.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() {
//Serial.println("Hello");
//delay(1000);
#if defined(__SAM3X8E__)
memset(&UniqueID,0,sizeof(UniqueID));
#endif
#if defined(M5STACK)
// Initialize the M5Stack object
M5.begin();
#endif
setupCmdArduino();
printFirmwareVersionAndBuildOptions();
// scan_i2c_bus();
#ifdef SD_CARD_INSERTED
sd_card_w5100_setup();
#endif
setupMacAddress();
#if defined(ARDUINO_ARCH_ESP8266)
EEPROM.begin(ESP_EEPROM_SIZE);
#endif
loadConfigFromEEPROM();
#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
if (oneWire) oneWire->idle(&owIdle);
#endif
mqttClient.setCallback(mqttCallback);
#ifdef _artnet
ArtnetSetup();
#endif
#if (defined(ARDUINO_ARCH_ESP8266) or defined(ARDUINO_ARCH_ESP32)) 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();
}
void printFirmwareVersionAndBuildOptions() {
debugSerial<<F("\nLazyhome.ru LightHub controller ")<<F(QUOTE(PIO_SRC_REV))<<F(" C++ version:")<<F(QUOTE(__cplusplus))<<endl;
#ifdef CONTROLLINO
debugSerial<<F("\n(+)CONTROLLINO");
#endif
#ifdef WATCH_DOG_TICKER_DISABLE
debugSerial<<F("\n(-)WATCHDOG");
#else
debugSerial<<F("\n(+)WATCHDOG");
#endif
debugSerial<<F("\nConfig server:")<<F(CONFIG_SERVER)<<F("\nFirmware MAC Address ")<<F(QUOTE(CUSTOM_FIRMWARE_MAC));
#ifdef DISABLE_FREERAM_PRINT
debugSerial<<F("\n(-)FreeRam printing");
#else
debugSerial<<F("\n(+)FreeRam printing");
#endif
#ifdef USE_1W_PIN
debugSerial<<F("\n(-)DS2482-100 USE_1W_PIN=")<<QUOTE(USE_1W_PIN);
#else
debugSerial<<F("\n(+)DS2482-100");
#endif
#ifdef Wiz5500
debugSerial<<F("\n(+)WizNet5500");
#endif
#ifdef DMX_DISABLE
debugSerial<<F("\n(-)DMX");
#else
debugSerial<<F("\n(+)DMX");
#endif
#ifdef MODBUS_DISABLE
debugSerial<<F("\n(-)MODBUS");
#else
debugSerial<<F("\n(+)MODBUS");
#endif
#ifdef OWIRE_DISABLE
debugSerial<<F("\n(-)OWIRE");
#else
debugSerial<<F("\n(+)OWIRE");
#endif
#ifndef DHT_DISABLE
debugSerial<<F("\n(+)DHT");
#else
debugSerial<<F("\n(-)DHT");
#endif
#ifndef COUNTER_DISABLE
debugSerial<<F("\n(+)COUNTER");
#else
debugSerial<<F("\n(-)COUNTER");
#endif
#ifdef SD_CARD_INSERTED
debugSerial<<F("\n(+)SDCARD");
#endif
#ifdef RESET_PIN
debugSerial<<F("\n(+)HARDRESET on pin=")<<QUOTE(RESET_PIN);
#else
debugSerial<<F("\n(-)HARDRESET, using soft");
#endif
#ifdef RESTART_LAN_ON_MQTT_ERRORS
debugSerial<<F("\n(+)RESTART_LAN_ON_MQTT_ERRORS");
#else
debugSerial<<F("\n(-)RESTART_LAN_ON_MQTT_ERRORS");
#endif
#ifdef CSSHDC_DISABLE
debugSerial<<F("\n(-)CCS811 & HDC1080");
#else
debugSerial<<F("\n(+)CCS811 & HDC1080");
#endif
#ifndef AC_DISABLE
debugSerial<<F("\n(+)AC HAIER");
#else
debugSerial<<F("\n(-)AC HAIER");
#endif
#ifndef MOTOR_DISABLE
debugSerial<<F("\n(+)MOTOR CTR");
#else
debugSerial<<F("\n(-)MOTOR CTR");
#endif
#ifndef SPILED_DISABLE
debugSerial<<F("\n(+)SPI LED");
#else
debugSerial<<F("\n(-)SPI LED");
#endif
#ifndef FASTLED
debugSerial<<F("\n(+)FASTLED");
#else
debugSerial<<F("\n(+)ADAFRUIT LED");
#endif
debugSerial<<endl;
// WDT_Disable( WDT ) ;
#if defined(__SAM3X8E__)
Serial.println(F("Reading 128 bits unique identifier") ) ;
ReadUniqueID( UniqueID.UID_Long ) ;
Serial.print ("ID: ") ;
for (byte b = 0 ; b < 4 ; b++)
Serial.print ((unsigned int) UniqueID.UID_Long [b], HEX) ;
Serial.println () ;
#endif
}
void publishStat(){
long fr = freeRam();
char topic[64];
char intbuf[16];
uint32_t ut = millis()/1000UL;
if (!mqttClient.connected() || ethernetIdleCount) return;
// debugSerial<<F("\nfree RAM: ")<<fr;
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) {
debugSerial<<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(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
//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() {
#if defined(OTA)
ArduinoOTA.poll();
#endif
#if defined(M5STACK)
// Initialize the M5Stack object
M5.update();
#endif
wdt_res();
cmdPoll();
if (lanLoop() > HAVE_IP_ADDRESS) {
mqttClient.loop();
#ifdef _artnet
if (artnet) artnet->read(); ///hung
#endif
}
#ifdef _owire
if (owReady && owArr) owLoop();
#endif
#ifdef _dmxin
// unsigned long lastpacket = DMXSerial.noDataSince();
DMXCheck();
#endif
if (items) {
// #ifndef MODBUS_DISABLE
if (lanStatus != RETAINING_COLLECTING) pollingLoop();
// #endif
//#ifdef _owire
thermoLoop();
//#endif
}
inputLoop();
#if defined (_espdmx)
dmxout.update();
#endif
#ifdef SYSLOG_ENABLE
// debugSerial<<F("#"));
// udpSyslog.log(LOG_INFO, "Ping syslog:");
#endif
}
void owIdle(void) {
#ifdef _artnet
if (artnet && (lanStatus>=HAVE_IP_ADDRESS)) artnet->read();
#endif
wdt_res();
return;
#ifdef _dmxin
DMXCheck();
#endif
#if defined (_espdmx)
dmxout.update();
#endif
}
void ethernetIdle(void){
ethernetIdleCount++;
wdt_res();
inputLoop();
ethernetIdleCount--;
};
void modbusIdle(void) {
wdt_res();
if (lanLoop() > 1) {
mqttClient.loop();
#ifdef _artnet
if (artnet) artnet->read();
#endif
inputLoop();
}
#ifdef _dmxin
DMXCheck();
#endif
#if defined (_espdmx)
dmxout.update();
#endif
}
void inputLoop(void) {
if (!inputs) return;
configLocked++;
if (millis() > nextInputCheck) {
aJsonObject *input = inputs->child;
while (input) {
if ((input->type == aJson_Object)) {
Input in(input);
in.Poll(CHECK_INPUT);
}
input = input->next;
}
nextInputCheck = millis() + INTERVAL_CHECK_INPUT;
}
if (millis() > nextSensorCheck) {
aJsonObject *input = inputs->child;
while (input) {
if ((input->type == aJson_Object)) {
Input in(input);
in.Poll(CHECK_SENSOR);
}
input = input->next;
}
nextSensorCheck = 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();
}
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
item = item->next;
} //if
}
configLocked--;
// SLOW POLLING
boolean done = false;
if (lanStatus == RETAINING_COLLECTING) return;
if (millis() > nextPollingCheck) {
while (pollingItem && !done) {
if (pollingItem->type == aJson_Array) {
Item it(pollingItem);
uint32_t ret = it.Poll(POLLING_SLOW);
if (ret)
{
nextPollingCheck = millis() + ret; //INTERVAL_CHECK_MODBUS;
done = true;
}
}//if
if (!pollingItem) return; //Config was re-readed
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() < nextThermostatCheck)
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)) {
int thermoPin = aJson.getArrayItem(thermoItem, I_ARG)->valueint;
float thermoSetting = aJson.getArrayItem(thermoItem, I_VAL)->valueint; ///
int thermoStateCommand = aJson.getArrayItem(thermoItem, I_CMD)->valueint;
float curTemp = aJson.getArrayItem(thermoExtensionArray, IET_TEMP)->valuefloat;
if (!aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint) {
debugSerial<<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--;
nextThermostatCheck = 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(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;}