mirror of
https://github.com/letscontrolit/ArduinoEasy.git
synced 2026-03-03 08:34:02 +01:00
1996 lines
54 KiB
C++
1996 lines
54 KiB
C++
#include <utility/W5100.h>
|
|
void ShowSocketStatus() {
|
|
|
|
Serial.println("ETHERNET SOCKET LIST");
|
|
Serial.println("#:Status Port Destination DPort");
|
|
Serial.println("0=avail,14=waiting,17=connected,22=UDP,1C=close wait");
|
|
String l_line = "";
|
|
l_line.reserve(64);
|
|
char l_buffer[10] = "";
|
|
for (uint8_t i = 0; i < MAX_SOCK_NUM; i++) {
|
|
l_line = "#" + String(i);
|
|
uint8_t s = W5100.readSnSR(i); //status
|
|
l_line += ":0x";
|
|
sprintf(l_buffer,"%x",s);
|
|
l_line += l_buffer;
|
|
l_line += " ";
|
|
l_line += String(W5100.readSnPORT(i)); //port
|
|
l_line += " D:";
|
|
uint8_t dip[4];
|
|
W5100.readSnDIPR(i, dip); //IP Address
|
|
for (int j=0; j<4; j++) {
|
|
l_line += int(dip[j]);
|
|
if (j<3) l_line += ".";
|
|
}
|
|
l_line += " (";
|
|
l_line += String(W5100.readSnDPORT(i)); //port on destination
|
|
l_line += ") ";
|
|
Serial.println(l_line);
|
|
}
|
|
}
|
|
|
|
void debugRAM(byte id, int objsize)
|
|
{
|
|
Serial.print(F("Function:"));
|
|
Serial.print(id);
|
|
Serial.print(F(" RAM:"));
|
|
Serial.print(FreeMem());
|
|
Serial.print(F(" objsize:"));
|
|
Serial.println(objsize);
|
|
}
|
|
|
|
void SelectSDCard(boolean sd)
|
|
{
|
|
digitalWrite(EthernetShield_CS_W5100, sd);
|
|
digitalWrite(EthernetShield_CS_SDCard, !sd);
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
Get value count from sensor type
|
|
\*********************************************************************************************/
|
|
|
|
byte getValueCountFromSensorType(byte sensorType)
|
|
{
|
|
byte valueCount = 0;
|
|
|
|
switch (sensorType)
|
|
{
|
|
case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc
|
|
case SENSOR_TYPE_SWITCH:
|
|
case SENSOR_TYPE_DIMMER:
|
|
valueCount = 1;
|
|
break;
|
|
case SENSOR_TYPE_LONG: // single LONG value, stored in two floats (rfid tags)
|
|
valueCount = 1;
|
|
break;
|
|
case SENSOR_TYPE_TEMP_HUM:
|
|
case SENSOR_TYPE_TEMP_BARO:
|
|
case SENSOR_TYPE_DUAL:
|
|
valueCount = 2;
|
|
break;
|
|
case SENSOR_TYPE_TEMP_HUM_BARO:
|
|
case SENSOR_TYPE_TRIPLE:
|
|
valueCount = 3;
|
|
break;
|
|
case SENSOR_TYPE_QUAD:
|
|
valueCount = 4;
|
|
break;
|
|
}
|
|
return valueCount;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
Workaround for removing trailing white space when String() converts a float with 0 decimals
|
|
\*********************************************************************************************/
|
|
String toString(float value, byte decimals)
|
|
{
|
|
String sValue = String(value, decimals);
|
|
sValue.trim();
|
|
return sValue;
|
|
}
|
|
|
|
/*********************************************************************************************\
|
|
Parse a string and get the xth command or parameter
|
|
\*********************************************************************************************/
|
|
String parseString(String& string, byte indexFind)
|
|
{
|
|
String tmpString = string;
|
|
tmpString += ",";
|
|
tmpString.replace(" ", ",");
|
|
String locateString = "";
|
|
byte count = 0;
|
|
int index = tmpString.indexOf(',');
|
|
while (index > 0)
|
|
{
|
|
count++;
|
|
locateString = tmpString.substring(0, index);
|
|
tmpString = tmpString.substring(index + 1);
|
|
index = tmpString.indexOf(',');
|
|
if (count == indexFind)
|
|
{
|
|
locateString.toLowerCase();
|
|
return locateString;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
Parse a string and get the xth command or parameter
|
|
\*********************************************************************************************/
|
|
int getParamStartPos(String& string, byte indexFind)
|
|
{
|
|
String tmpString = string;
|
|
byte count = 0;
|
|
tmpString.replace(" ", ",");
|
|
for (int x = 0; x < tmpString.length(); x++)
|
|
{
|
|
if (tmpString.charAt(x) == ',')
|
|
{
|
|
count++;
|
|
if (count == (indexFind - 1))
|
|
return x + 1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
set pin mode & state (info table)
|
|
\*********************************************************************************************/
|
|
boolean setPinState(byte plugin, byte index, byte mode, uint16_t value)
|
|
{
|
|
// plugin number and index form a unique key
|
|
// first check if this pin is already known
|
|
boolean reUse = false;
|
|
for (byte x = 0; x < PINSTATE_TABLE_MAX; x++)
|
|
if ((pinStates[x].plugin == plugin) && (pinStates[x].index == index))
|
|
{
|
|
pinStates[x].mode = mode;
|
|
pinStates[x].value = value;
|
|
reUse = true;
|
|
break;
|
|
}
|
|
|
|
if (!reUse)
|
|
{
|
|
for (byte x = 0; x < PINSTATE_TABLE_MAX; x++)
|
|
if (pinStates[x].plugin == 0)
|
|
{
|
|
pinStates[x].plugin = plugin;
|
|
pinStates[x].index = index;
|
|
pinStates[x].mode = mode;
|
|
pinStates[x].value = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
get pin mode & state (info table)
|
|
\*********************************************************************************************/
|
|
boolean getPinState(byte plugin, byte index, byte *mode, uint16_t *value)
|
|
{
|
|
for (byte x = 0; x < PINSTATE_TABLE_MAX; x++)
|
|
if ((pinStates[x].plugin == plugin) && (pinStates[x].index == index))
|
|
{
|
|
*mode = pinStates[x].mode;
|
|
*value = pinStates[x].value;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
check if pin mode & state is known (info table)
|
|
\*********************************************************************************************/
|
|
boolean hasPinState(byte plugin, byte index)
|
|
{
|
|
for (byte x = 0; x < PINSTATE_TABLE_MAX; x++)
|
|
if ((pinStates[x].plugin == plugin) && (pinStates[x].index == index))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************\
|
|
report pin mode & state (info table) using json
|
|
\*********************************************************************************************/
|
|
String getPinStateJSON(boolean search, byte plugin, byte index, String& log, uint16_t noSearchValue)
|
|
{
|
|
printToWebJSON = true;
|
|
byte mode = PIN_MODE_INPUT;
|
|
uint16_t value = noSearchValue;
|
|
String reply = "";
|
|
boolean found = false;
|
|
|
|
if (search)
|
|
{
|
|
for (byte x = 0; x < PINSTATE_TABLE_MAX; x++)
|
|
if ((pinStates[x].plugin == plugin) && (pinStates[x].index == index))
|
|
{
|
|
mode = pinStates[x].mode;
|
|
value = pinStates[x].value;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!search || (search && found))
|
|
{
|
|
reply += F("{\n\"log\": \"");
|
|
reply += log.substring(7, 32); // truncate to 25 chars, max MQTT message size = 128 including header...
|
|
reply += F("\",\n\"plugin\": ");
|
|
reply += plugin;
|
|
reply += F(",\n\"pin\": ");
|
|
reply += index;
|
|
reply += F(",\n\"mode\": \"");
|
|
switch (mode)
|
|
{
|
|
case PIN_MODE_UNDEFINED:
|
|
reply += F("undefined");
|
|
break;
|
|
case PIN_MODE_INPUT:
|
|
reply += F("input");
|
|
break;
|
|
case PIN_MODE_OUTPUT:
|
|
reply += F("output");
|
|
break;
|
|
case PIN_MODE_PWM:
|
|
reply += F("PWM");
|
|
break;
|
|
case PIN_MODE_SERVO:
|
|
reply += F("servo");
|
|
break;
|
|
}
|
|
reply += F("\",\n\"state\": ");
|
|
reply += value;
|
|
reply += F("\n}\n");
|
|
return reply;
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Unsigned long Timer timeOut check
|
|
\*********************************************************************************************/
|
|
|
|
boolean timeOut(unsigned long timer)
|
|
{
|
|
// This routine solves the 49 day bug without the need for separate start time and duration
|
|
// that would need two 32 bit variables if duration is not static
|
|
// It limits the maximum delay to 24.9 days.
|
|
|
|
unsigned long now = millis();
|
|
if (((now >= timer) && ((now - timer) < 1 << 31)) || ((timer >= now) && (timer - now > 1 << 31)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Status LED
|
|
\*********************************************************************************************/
|
|
void statusLED(boolean traffic)
|
|
{
|
|
if (Settings.Pin_status_led == -1)
|
|
return;
|
|
|
|
static unsigned long timer = 0;
|
|
static byte currentState = 0;
|
|
|
|
if (traffic)
|
|
{
|
|
currentState = HIGH;
|
|
digitalWrite(Settings.Pin_status_led, currentState); // blink off
|
|
timer = millis() + 100;
|
|
}
|
|
|
|
if (timer == 0 || millis() > timer)
|
|
{
|
|
timer = 0;
|
|
byte state = HIGH;
|
|
// todo if (WiFi.status() == WL_CONNECTED)
|
|
// state = LOW;
|
|
|
|
if (currentState != state)
|
|
{
|
|
currentState = state;
|
|
pinMode(Settings.Pin_status_led, OUTPUT);
|
|
digitalWrite(Settings.Pin_status_led, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
delay in milliseconds with background processing
|
|
\*********************************************************************************************/
|
|
void delayMillis(unsigned long delay)
|
|
{
|
|
unsigned long timer = millis() + delay;
|
|
while (millis() < timer)
|
|
backgroundtasks();
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Parse a command string to event struct
|
|
\*********************************************************************************************/
|
|
void parseCommandString(struct EventStruct *event, String& string)
|
|
{
|
|
char command[80];
|
|
command[0] = 0;
|
|
char TmpStr1[80];
|
|
TmpStr1[0] = 0;
|
|
|
|
string.toCharArray(command, 80);
|
|
event->Par1 = 0;
|
|
event->Par2 = 0;
|
|
event->Par3 = 0;
|
|
|
|
if (GetArgv(command, TmpStr1, 2)) event->Par1 = str2int(TmpStr1);
|
|
if (GetArgv(command, TmpStr1, 3)) event->Par2 = str2int(TmpStr1);
|
|
if (GetArgv(command, TmpStr1, 4)) event->Par3 = str2int(TmpStr1);
|
|
}
|
|
|
|
/********************************************************************************************\
|
|
Clear task settings for given task
|
|
\*********************************************************************************************/
|
|
void taskClear(byte taskIndex, boolean save)
|
|
{
|
|
Settings.TaskDeviceNumber[taskIndex] = 0;
|
|
ExtraTaskSettings.TaskDeviceName[0] = 0;
|
|
Settings.TaskDeviceID[taskIndex] = 0;
|
|
Settings.TaskDeviceDataFeed[taskIndex] = 0;
|
|
Settings.TaskDevicePin1[taskIndex] = -1;
|
|
Settings.TaskDevicePin2[taskIndex] = -1;
|
|
Settings.TaskDevicePin3[taskIndex] = -1;
|
|
Settings.TaskDevicePort[taskIndex] = 0;
|
|
Settings.TaskDeviceSendData[taskIndex] = true;
|
|
Settings.TaskDeviceGlobalSync[taskIndex] = false;
|
|
Settings.TaskDeviceTimer[taskIndex] = 0;
|
|
|
|
for (byte x = 0; x < PLUGIN_CONFIGVAR_MAX; x++)
|
|
Settings.TaskDevicePluginConfig[taskIndex][x] = 0;
|
|
|
|
for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++)
|
|
{
|
|
ExtraTaskSettings.TaskDeviceFormula[varNr][0] = 0;
|
|
ExtraTaskSettings.TaskDeviceValueNames[varNr][0] = 0;
|
|
ExtraTaskSettings.TaskDeviceValueDecimals[varNr] = 2;
|
|
}
|
|
|
|
for (byte varNr = 0; varNr < PLUGIN_EXTRACONFIGVAR_MAX; varNr++)
|
|
ExtraTaskSettings.TaskDevicePluginConfigLong[varNr] = 0;
|
|
|
|
if (save)
|
|
{
|
|
SaveTaskSettings(taskIndex);
|
|
SaveSettings();
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Use DNS to resolve hostname to ip address
|
|
\*********************************************************************************************/
|
|
void getIPfromHostName()
|
|
{
|
|
IPAddress IP;
|
|
if (Settings.ControllerHostName[0] != 0)
|
|
{
|
|
// todo WiFi.hostByName(Settings.ControllerHostName, IP);
|
|
for (byte x = 0; x < 4; x++)
|
|
Settings.Controller_IP[x] = IP[x];
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Fix stuff to clear out differences between releases
|
|
\*********************************************************************************************/
|
|
void BuildFixes()
|
|
{
|
|
Serial.println(F("\nBuild changed!"));
|
|
|
|
|
|
Settings.Build = BUILD;
|
|
SaveSettings();
|
|
}
|
|
|
|
void fileSystemCheck()
|
|
{
|
|
pinMode(EthernetShield_CS_SDCard, OUTPUT);
|
|
pinMode(EthernetShield_CS_W5100, OUTPUT);
|
|
SelectSDCard(true);
|
|
|
|
if (SD.begin(EthernetShield_CS_SDCard))
|
|
{
|
|
String log = F("SD Mount successful");
|
|
Serial.println(log);
|
|
//ExecuteCommand(0,"sdcard");
|
|
if (!SD.exists("config.txt"))
|
|
{
|
|
String log = F("Creating config.txt");
|
|
Serial.println(log);
|
|
File f = SD.open("config.txt", FILE_WRITE);
|
|
if (f)
|
|
{
|
|
for (unsigned long x = 0; x < 32768; x++)
|
|
f.write((byte)0);
|
|
f.close();
|
|
}
|
|
log = F("Ready creating config.txt");
|
|
Serial.println(log);
|
|
f = SD.open("security.txt", FILE_WRITE);
|
|
if (f)
|
|
{
|
|
for (int x = 0; x < 512; x++)
|
|
f.write((byte)0);
|
|
f.close();
|
|
}
|
|
f = SD.open("rules.txt", FILE_WRITE);
|
|
f.close();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String log = F("SD Mount failed");
|
|
Serial.println(log);
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Find device index corresponding to task number setting
|
|
\*********************************************************************************************/
|
|
byte getDeviceIndex(byte Number)
|
|
{
|
|
byte DeviceIndex = 0;
|
|
for (byte x = 0; x <= deviceCount ; x++)
|
|
if (Device[x].Number == Number)
|
|
DeviceIndex = x;
|
|
return DeviceIndex;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Find protocol index corresponding to protocol setting
|
|
\*********************************************************************************************/
|
|
byte getProtocolIndex(byte Number)
|
|
{
|
|
byte ProtocolIndex = 0;
|
|
for (byte x = 0; x <= protocolCount ; x++)
|
|
if (Protocol[x].Number == Number)
|
|
ProtocolIndex = x;
|
|
return ProtocolIndex;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Find positional parameter in a char string
|
|
\*********************************************************************************************/
|
|
boolean GetArgv(const char *string, char *argv, int argc)
|
|
{
|
|
int string_pos = 0, argv_pos = 0, argc_pos = 0;
|
|
char c, d;
|
|
|
|
while (string_pos < strlen(string))
|
|
{
|
|
c = string[string_pos];
|
|
d = string[string_pos + 1];
|
|
|
|
if (c == ' ' && d == ' ') {}
|
|
else if (c == ' ' && d == ',') {}
|
|
else if (c == ',' && d == ' ') {}
|
|
else if (c == ' ' && d >= 33 && d <= 126) {}
|
|
else if (c == ',' && d >= 33 && d <= 126) {}
|
|
else
|
|
{
|
|
argv[argv_pos++] = c;
|
|
argv[argv_pos] = 0;
|
|
|
|
if (d == ' ' || d == ',' || d == 0)
|
|
{
|
|
argv[argv_pos] = 0;
|
|
argc_pos++;
|
|
|
|
if (argc_pos == argc)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
argv[0] = 0;
|
|
argv_pos = 0;
|
|
string_pos++;
|
|
}
|
|
}
|
|
string_pos++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Convert a char string to integer
|
|
\*********************************************************************************************/
|
|
unsigned long str2int(char *string)
|
|
{
|
|
unsigned long temp = atof(string);
|
|
return temp;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Convert a char string to IP byte array
|
|
\*********************************************************************************************/
|
|
boolean str2ip(char *string, byte* IP)
|
|
{
|
|
byte c;
|
|
byte part = 0;
|
|
int value = 0;
|
|
|
|
for (int x = 0; x <= strlen(string); x++)
|
|
{
|
|
c = string[x];
|
|
if (isdigit(c))
|
|
{
|
|
value *= 10;
|
|
value += c - '0';
|
|
}
|
|
|
|
else if (c == '.' || c == 0) // next octet from IP address
|
|
{
|
|
if (value <= 255)
|
|
IP[part++] = value;
|
|
else
|
|
return false;
|
|
value = 0;
|
|
}
|
|
else if (c == ' ') // ignore these
|
|
;
|
|
else // invalid token
|
|
return false;
|
|
}
|
|
if (part == 4) // correct number of octets
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void SaveSettings(void)
|
|
{
|
|
SaveToFile((char*)"config.txt", 0, (byte*)&Settings, sizeof(struct SettingsStruct));
|
|
SaveToFile((char*)"security.txt", 0, (byte*)&SecuritySettings, sizeof(struct SecurityStruct));
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Load settings from SPIFFS
|
|
\*********************************************************************************************/
|
|
boolean LoadSettings()
|
|
{
|
|
LoadFromFile((char*)"config.txt", 0, (byte*)&Settings, sizeof(struct SettingsStruct));
|
|
LoadFromFile((char*)"security.txt", 0, (byte*)&SecuritySettings, sizeof(struct SecurityStruct));
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save Task settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void SaveTaskSettings(byte TaskIndex)
|
|
{
|
|
ExtraTaskSettings.TaskIndex = TaskIndex;
|
|
SaveToFile((char*)"config.txt", 4096 + (TaskIndex * 1024), (byte*)&ExtraTaskSettings, sizeof(struct ExtraTaskSettingsStruct));
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Load Task settings from SPIFFS
|
|
\*********************************************************************************************/
|
|
void LoadTaskSettings(byte TaskIndex)
|
|
{
|
|
if (ExtraTaskSettings.TaskIndex == TaskIndex)
|
|
return;
|
|
|
|
LoadFromFile((char*)"config.txt", 4096 + (TaskIndex * 1024), (byte*)&ExtraTaskSettings, sizeof(struct ExtraTaskSettingsStruct));
|
|
ExtraTaskSettings.TaskIndex = TaskIndex; // Needed when an empty task was requested
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save Custom Task settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void SaveCustomTaskSettings(int TaskIndex, byte* memAddress, int datasize)
|
|
{
|
|
if (datasize > 512)
|
|
return;
|
|
SaveToFile((char*)"config.txt", 4096 + (TaskIndex * 1024) + 512, memAddress, datasize);
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save Custom Task settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void LoadCustomTaskSettings(int TaskIndex, byte* memAddress, int datasize)
|
|
{
|
|
if (datasize > 512)
|
|
return;
|
|
LoadFromFile((char*)"config.txt", 4096 + (TaskIndex * 1024) + 512, memAddress, datasize);
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save Custom Controller settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void SaveCustomControllerSettings(byte* memAddress, int datasize)
|
|
{
|
|
if (datasize > 4096)
|
|
return;
|
|
SaveToFile((char*)"config.txt", 28672, memAddress, datasize);
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save Custom Controller settings to SPIFFS
|
|
\*********************************************************************************************/
|
|
void LoadCustomControllerSettings(byte* memAddress, int datasize)
|
|
{
|
|
if (datasize > 4096)
|
|
return;
|
|
LoadFromFile((char*)"config.txt", 28672, memAddress, datasize);
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Save data into config file on SPIFFS
|
|
\*********************************************************************************************/
|
|
void SaveToFile(char* fname, int index, byte* memAddress, int datasize)
|
|
{
|
|
File f = SD.open(fname, FILE_WRITE);
|
|
if (f)
|
|
{
|
|
f.seek(index);
|
|
byte *pointerToByteToSave = memAddress;
|
|
for (int x = 0; x < datasize ; x++)
|
|
{
|
|
f.write(*pointerToByteToSave);
|
|
pointerToByteToSave++;
|
|
}
|
|
f.close();
|
|
String log = F("FILE : File saved");
|
|
addLog(LOG_LEVEL_INFO, log);
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Load data from config file on SPIFFS
|
|
\*********************************************************************************************/
|
|
void LoadFromFile(char* fname, int index, byte* memAddress, int datasize)
|
|
{
|
|
File f = SD.open(fname);
|
|
if (f)
|
|
{
|
|
f.seek(index);
|
|
byte *pointerToByteToRead = memAddress;
|
|
for (int x = 0; x < datasize; x++)
|
|
{
|
|
*pointerToByteToRead = f.read();
|
|
pointerToByteToRead++;// next byte
|
|
}
|
|
f.close();
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Reset all settings to factory defaults
|
|
\*********************************************************************************************/
|
|
void ResetFactory(void)
|
|
{
|
|
// Direct Serial is allowed here, since this is only an emergency task.
|
|
SD.remove("config.txt");
|
|
SD.remove("security.txt");
|
|
SD.remove("rules.txt");
|
|
File f = SD.open("config.txt", FILE_WRITE);
|
|
if (f)
|
|
{
|
|
for (unsigned long x = 0; x < 32768; x++)
|
|
f.write((byte)0);
|
|
f.close();
|
|
|
|
}
|
|
f = SD.open("security.txt", FILE_WRITE);
|
|
if (f)
|
|
{
|
|
for (int x = 0; x < 512; x++)
|
|
f.write((byte)0);
|
|
f.close();
|
|
}
|
|
f = SD.open("rules.txt", FILE_WRITE);
|
|
f.close();
|
|
|
|
LoadSettings();
|
|
// now we set all parameters that need to be non-zero as default value
|
|
|
|
#if DEFAULT_USE_STATIC_IP
|
|
str2ip((char*)DEFAULT_IP, Settings.IP);
|
|
str2ip((char*)DEFAULT_DNS, Settings.DNS);
|
|
str2ip((char*)DEFAULT_GW, Settings.Gateway);
|
|
str2ip((char*)DEFAULT_SUBNET, Settings.Subnet);
|
|
#endif
|
|
|
|
Settings.PID = ARDUINO_PROJECT_PID;
|
|
Settings.Version = VERSION;
|
|
Settings.Unit = UNIT;
|
|
SecuritySettings.Password[0] = 0;
|
|
str2ip((char*)DEFAULT_SERVER, Settings.Controller_IP);
|
|
Settings.ControllerPort = DEFAULT_PORT;
|
|
Settings.Delay = DEFAULT_DELAY;
|
|
Settings.Pin_status_led = -1;
|
|
Settings.Protocol = DEFAULT_PROTOCOL;
|
|
strcpy_P(Settings.Name, PSTR(DEFAULT_NAME));
|
|
Settings.SerialLogLevel = 4;
|
|
Settings.BaudRate = 115200;
|
|
Settings.MessageDelay = 1000;
|
|
Settings.CustomCSS = false;
|
|
for (byte x = 0; x < TASKS_MAX; x++)
|
|
{
|
|
Settings.TaskDevicePin1[x] = -1;
|
|
Settings.TaskDevicePin2[x] = -1;
|
|
Settings.TaskDevicePin3[x] = -1;
|
|
Settings.TaskDevicePin1PullUp[x] = true;
|
|
Settings.TaskDevicePin1Inversed[x] = false;
|
|
Settings.TaskDeviceSendData[x] = true;
|
|
Settings.TaskDeviceTimer[x] = Settings.Delay;
|
|
}
|
|
Settings.Build = BUILD;
|
|
Settings.UseSerial = true;
|
|
Settings.UDPPort =65500;
|
|
SaveSettings();
|
|
delay(1000);
|
|
Reboot();
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
If RX and TX tied together, perform emergency reset to get the system out of boot loops
|
|
\*********************************************************************************************/
|
|
|
|
void emergencyReset()
|
|
{
|
|
// Direct Serial is allowed here, since this is only an emergency task.
|
|
Serial.begin(115200);
|
|
Serial.write(0xAA);
|
|
Serial.write(0x55);
|
|
delay(1);
|
|
if (Serial.available() == 2)
|
|
if (Serial.read() == 0xAA && Serial.read() == 0x55)
|
|
{
|
|
Serial.println(F("System will reset in 10 seconds..."));
|
|
delay(10000);
|
|
ResetFactory();
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Get free system mem
|
|
\*********************************************************************************************/
|
|
uint8_t *heapptr, *stackptr;
|
|
|
|
unsigned long FreeMem(void)
|
|
{
|
|
stackptr = (uint8_t *)malloc(4); // use stackptr temporarily
|
|
heapptr = stackptr; // save value of heap pointer
|
|
free(stackptr); // free up the memory again (sets stackptr to 0)
|
|
stackptr = (uint8_t *)(SP); // save value of stack pointer
|
|
return (stackptr-heapptr);
|
|
}
|
|
|
|
/********************************************************************************************\
|
|
In memory convert float to long
|
|
\*********************************************************************************************/
|
|
unsigned long float2ul(float f)
|
|
{
|
|
unsigned long ul;
|
|
memcpy(&ul, &f, 4);
|
|
return ul;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
In memory convert long to float
|
|
\*********************************************************************************************/
|
|
float ul2float(unsigned long ul)
|
|
{
|
|
float f;
|
|
memcpy(&f, &ul, 4);
|
|
return f;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Add to log
|
|
\*********************************************************************************************/
|
|
void addLog(byte loglevel, String& string)
|
|
{
|
|
addLog(loglevel, string.c_str());
|
|
}
|
|
|
|
void addLog(byte loglevel, const char *line)
|
|
{
|
|
if (Settings.UseSerial)
|
|
if (loglevel <= Settings.SerialLogLevel)
|
|
Serial.println(line);
|
|
|
|
if (loglevel <= Settings.SyslogLevel)
|
|
syslog(line);
|
|
|
|
if (loglevel <= Settings.SDLogLevel)
|
|
{
|
|
File logFile = SD.open("log.txt", FILE_WRITE);
|
|
if (logFile)
|
|
{
|
|
String time = "";
|
|
time += hour();
|
|
time += ":";
|
|
if (minute() < 10)
|
|
time += "0";
|
|
time += minute();
|
|
time += ":";
|
|
if (second() < 10)
|
|
time += "0";
|
|
time += second();
|
|
time += " : ";
|
|
logFile.print(time);
|
|
logFile.println(line);
|
|
}
|
|
logFile.close();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Delayed reboot, in case of issues, do not reboot with high frequency as it might not help...
|
|
\*********************************************************************************************/
|
|
void delayedReboot(int rebootDelay)
|
|
{
|
|
// Direct Serial is allowed here, since this is only an emergency task.
|
|
while (rebootDelay != 0 )
|
|
{
|
|
Serial.print(F("Delayed Reset "));
|
|
Serial.println(rebootDelay);
|
|
rebootDelay--;
|
|
delay(1000);
|
|
}
|
|
Reboot();
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Convert a string like "Sun,12:30" into a 32 bit integer
|
|
\*********************************************************************************************/
|
|
unsigned long string2TimeLong(String &str)
|
|
{
|
|
// format 0000WWWWAAAABBBBCCCCDDDD
|
|
// WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes
|
|
|
|
char command[20];
|
|
char TmpStr1[10];
|
|
int w, x, y;
|
|
unsigned long a;
|
|
str.toLowerCase();
|
|
str.toCharArray(command, 20);
|
|
unsigned long lngTime = 0;
|
|
|
|
if (GetArgv(command, TmpStr1, 1))
|
|
{
|
|
String day = TmpStr1;
|
|
String weekDays = F("allsunmontuewedthufrisat");
|
|
y = weekDays.indexOf(TmpStr1) / 3;
|
|
if (y == 0)
|
|
y = 0xf; // wildcard is 0xf
|
|
lngTime |= (unsigned long)y << 16;
|
|
}
|
|
|
|
if (GetArgv(command, TmpStr1, 2))
|
|
{
|
|
y = 0;
|
|
for (x = strlen(TmpStr1) - 1; x >= 0; x--)
|
|
{
|
|
w = TmpStr1[x];
|
|
if (w >= '0' && w <= '9' || w == '*')
|
|
{
|
|
a = 0xffffffff ^ (0xfUL << y); // create mask to clean nibble position y
|
|
lngTime &= a; // maak nibble leeg
|
|
if (w == '*')
|
|
lngTime |= (0xFUL << y); // fill nibble with wildcard value
|
|
else
|
|
lngTime |= (w - '0') << y; // fill nibble with token
|
|
y += 4;
|
|
}
|
|
else if (w == ':');
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return lngTime;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Convert a 32 bit integer into a string like "Sun,12:30"
|
|
\*********************************************************************************************/
|
|
String timeLong2String(unsigned long lngTime)
|
|
{
|
|
unsigned long x = 0;
|
|
String time = "";
|
|
|
|
x = (lngTime >> 16) & 0xf;
|
|
if (x == 0x0f)
|
|
x = 0;
|
|
String weekDays = F("AllSunMonTueWedThuFriSat");
|
|
time = weekDays.substring(x * 3, x * 3 + 3);
|
|
time += ",";
|
|
|
|
x = (lngTime >> 12) & 0xf;
|
|
if (x == 0xf)
|
|
time += "*";
|
|
else if (x == 0xe)
|
|
time += "-";
|
|
else
|
|
time += x;
|
|
|
|
x = (lngTime >> 8) & 0xf;
|
|
if (x == 0xf)
|
|
time += "*";
|
|
else if (x == 0xe)
|
|
time += "-";
|
|
else
|
|
time += x;
|
|
|
|
time += ":";
|
|
|
|
x = (lngTime >> 4) & 0xf;
|
|
if (x == 0xf)
|
|
time += "*";
|
|
else if (x == 0xe)
|
|
time += "-";
|
|
else
|
|
time += x;
|
|
|
|
x = (lngTime) & 0xf;
|
|
if (x == 0xf)
|
|
time += "*";
|
|
else if (x == 0xe)
|
|
time += "-";
|
|
else
|
|
time += x;
|
|
|
|
return time;
|
|
}
|
|
|
|
|
|
String parseTemplate(String &tmpString, byte lineSize)
|
|
{
|
|
String newString = "";
|
|
String tmpStringMid = "";
|
|
|
|
// replace task template variables
|
|
int leftBracketIndex = tmpString.indexOf('[');
|
|
if (leftBracketIndex == -1)
|
|
newString = tmpString;
|
|
else
|
|
{
|
|
byte count = 0;
|
|
byte currentTaskIndex = ExtraTaskSettings.TaskIndex;
|
|
while (leftBracketIndex >= 0 && count < 10 - 1)
|
|
{
|
|
newString += tmpString.substring(0, leftBracketIndex);
|
|
tmpString = tmpString.substring(leftBracketIndex + 1);
|
|
int rightBracketIndex = tmpString.indexOf(']');
|
|
if (rightBracketIndex)
|
|
{
|
|
tmpStringMid = tmpString.substring(0, rightBracketIndex);
|
|
tmpString = tmpString.substring(rightBracketIndex + 1);
|
|
int hashtagIndex = tmpStringMid.indexOf('#');
|
|
String deviceName = tmpStringMid.substring(0, hashtagIndex);
|
|
String valueName = tmpStringMid.substring(hashtagIndex + 1);
|
|
String valueFormat = "";
|
|
hashtagIndex = valueName.indexOf('#');
|
|
if (hashtagIndex >= 0)
|
|
{
|
|
valueFormat = valueName.substring(hashtagIndex + 1);
|
|
valueName = valueName.substring(0, hashtagIndex);
|
|
}
|
|
for (byte y = 0; y < TASKS_MAX; y++)
|
|
{
|
|
LoadTaskSettings(y);
|
|
if (ExtraTaskSettings.TaskDeviceName[0] != 0)
|
|
{
|
|
if (deviceName.equalsIgnoreCase(ExtraTaskSettings.TaskDeviceName))
|
|
{
|
|
for (byte z = 0; z < VARS_PER_TASK; z++)
|
|
if (valueName.equalsIgnoreCase(ExtraTaskSettings.TaskDeviceValueNames[z]))
|
|
{
|
|
// here we know the task and value, so find the uservar
|
|
String value = "";
|
|
byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[y]);
|
|
if (Device[DeviceIndex].VType == SENSOR_TYPE_LONG)
|
|
value = (unsigned long)UserVar[y * VARS_PER_TASK + z] + ((unsigned long)UserVar[y * VARS_PER_TASK + z + 1] << 16);
|
|
else
|
|
value = toString(UserVar[y * VARS_PER_TASK + z], ExtraTaskSettings.TaskDeviceValueDecimals[z]);
|
|
|
|
if (valueFormat == "R")
|
|
{
|
|
int filler = lineSize - newString.length() - value.length() - tmpString.length() ;
|
|
for (byte f = 0; f < filler; f++)
|
|
newString += " ";
|
|
}
|
|
newString += String(value);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
leftBracketIndex = tmpString.indexOf('[');
|
|
count++;
|
|
}
|
|
newString += tmpString;
|
|
LoadTaskSettings(currentTaskIndex);
|
|
}
|
|
|
|
// replace other system variables like %sysname%, %systime%, %ip%
|
|
newString.replace("%sysname%", Settings.Name);
|
|
|
|
String strTime = "";
|
|
if (hour() < 10)
|
|
strTime += " ";
|
|
strTime += hour();
|
|
strTime += ":";
|
|
if (minute() < 10)
|
|
strTime += "0";
|
|
strTime += minute();
|
|
newString.replace("%systime%", strTime);
|
|
|
|
newString.replace("%uptime%", String(wdcounter / 2));
|
|
|
|
IPAddress ip = Ethernet.localIP();
|
|
char strIP[20];
|
|
sprintf_P(strIP, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]);
|
|
newString.replace("%ip%", strIP);
|
|
|
|
newString.replace("%sysload%", String(100 - (100 * loopCounterLast / loopCounterMax)));
|
|
|
|
// padding spaces
|
|
while (newString.length() < lineSize)
|
|
newString += " ";
|
|
|
|
return newString;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Calculate function for simple expressions
|
|
\*********************************************************************************************/
|
|
#define CALCULATE_OK 0
|
|
#define CALCULATE_ERROR_STACK_OVERFLOW 1
|
|
#define CALCULATE_ERROR_BAD_OPERATOR 2
|
|
#define CALCULATE_ERROR_PARENTHESES_MISMATCHED 3
|
|
#define CALCULATE_ERROR_UNKNOWN_TOKEN 4
|
|
#define STACK_SIZE 10 // was 50
|
|
#define TOKEN_MAX 20
|
|
|
|
float globalstack[STACK_SIZE];
|
|
float *sp = globalstack - 1;
|
|
float *sp_max = &globalstack[STACK_SIZE - 1];
|
|
|
|
#define is_operator(c) (c == '+' || c == '-' || c == '*' || c == '/' || c == '^')
|
|
|
|
int push(float value)
|
|
{
|
|
if (sp != sp_max) // Full
|
|
{
|
|
*(++sp) = value;
|
|
return 0;
|
|
}
|
|
else
|
|
return CALCULATE_ERROR_STACK_OVERFLOW;
|
|
}
|
|
|
|
float pop()
|
|
{
|
|
if (sp != (globalstack - 1)) // empty
|
|
return *(sp--);
|
|
}
|
|
|
|
float apply_operator(char op, float first, float second)
|
|
{
|
|
switch (op)
|
|
{
|
|
case '+':
|
|
return first + second;
|
|
case '-':
|
|
return first - second;
|
|
case '*':
|
|
return first * second;
|
|
case '/':
|
|
return first / second;
|
|
case '^':
|
|
return pow(first, second);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
char *next_token(char *linep)
|
|
{
|
|
while (isspace(*(linep++)));
|
|
while (*linep && !isspace(*(linep++)));
|
|
return linep;
|
|
}
|
|
|
|
int RPNCalculate(char* token)
|
|
{
|
|
if (token[0] == 0)
|
|
return 0; // geen moeite doen voor een lege string
|
|
|
|
if (is_operator(token[0]))
|
|
{
|
|
float second = pop();
|
|
float first = pop();
|
|
|
|
if (push(apply_operator(token[0], first, second)))
|
|
return CALCULATE_ERROR_STACK_OVERFLOW;
|
|
}
|
|
else // Als er nog een is, dan deze ophalen
|
|
if (push(atof(token))) // is het een waarde, dan op de stack plaatsen
|
|
return CALCULATE_ERROR_STACK_OVERFLOW;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// operators
|
|
// precedence operators associativity
|
|
// 3 ! right to left
|
|
// 2 * / % left to right
|
|
// 1 + - ^ left to right
|
|
int op_preced(const char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '^':
|
|
return 3;
|
|
case '*':
|
|
case '/':
|
|
return 2;
|
|
case '+':
|
|
case '-':
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool op_left_assoc(const char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '^':
|
|
case '*':
|
|
case '/':
|
|
case '+':
|
|
case '-':
|
|
return true; // left to right
|
|
//case '!': return false; // right to left
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unsigned int op_arg_count(const char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '^':
|
|
case '*':
|
|
case '/':
|
|
case '+':
|
|
case '-':
|
|
return 2;
|
|
//case '!': return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int Calculate(const char *input, float* result)
|
|
{
|
|
const char *strpos = input, *strend = input + strlen(input);
|
|
char token[25];
|
|
char c, *TokenPos = token;
|
|
char stack[32]; // operator stack
|
|
unsigned int sl = 0; // stack length
|
|
char sc; // used for record stack element
|
|
int error = 0;
|
|
|
|
//*sp=0; // bug, it stops calculating after 50 times
|
|
sp = globalstack - 1;
|
|
|
|
while (strpos < strend)
|
|
{
|
|
// read one token from the input stream
|
|
c = *strpos;
|
|
if (c != ' ')
|
|
{
|
|
// If the token is a number (identifier), then add it to the token queue.
|
|
if ((c >= '0' && c <= '9') || c == '.')
|
|
{
|
|
*TokenPos = c;
|
|
++TokenPos;
|
|
}
|
|
|
|
// If the token is an operator, op1, then:
|
|
else if (is_operator(c))
|
|
{
|
|
*(TokenPos) = 0;
|
|
error = RPNCalculate(token);
|
|
TokenPos = token;
|
|
if (error)return error;
|
|
while (sl > 0)
|
|
{
|
|
sc = stack[sl - 1];
|
|
// While there is an operator token, op2, at the top of the stack
|
|
// op1 is left-associative and its precedence is less than or equal to that of op2,
|
|
// or op1 has precedence less than that of op2,
|
|
// The differing operator priority decides pop / push
|
|
// If 2 operators have equal priority then associativity decides.
|
|
if (is_operator(sc) && ((op_left_assoc(c) && (op_preced(c) <= op_preced(sc))) || (op_preced(c) < op_preced(sc))))
|
|
{
|
|
// Pop op2 off the stack, onto the token queue;
|
|
*TokenPos = sc;
|
|
++TokenPos;
|
|
*(TokenPos) = 0;
|
|
error = RPNCalculate(token);
|
|
TokenPos = token;
|
|
if (error)return error;
|
|
sl--;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
// push op1 onto the stack.
|
|
stack[sl] = c;
|
|
++sl;
|
|
}
|
|
// If the token is a left parenthesis, then push it onto the stack.
|
|
else if (c == '(')
|
|
{
|
|
stack[sl] = c;
|
|
++sl;
|
|
}
|
|
// If the token is a right parenthesis:
|
|
else if (c == ')')
|
|
{
|
|
bool pe = false;
|
|
// Until the token at the top of the stack is a left parenthesis,
|
|
// pop operators off the stack onto the token queue
|
|
while (sl > 0)
|
|
{
|
|
*(TokenPos) = 0;
|
|
error = RPNCalculate(token);
|
|
TokenPos = token;
|
|
if (error)return error;
|
|
sc = stack[sl - 1];
|
|
if (sc == '(')
|
|
{
|
|
pe = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
*TokenPos = sc;
|
|
++TokenPos;
|
|
sl--;
|
|
}
|
|
}
|
|
// If the stack runs out without finding a left parenthesis, then there are mismatched parentheses.
|
|
if (!pe)
|
|
return CALCULATE_ERROR_PARENTHESES_MISMATCHED;
|
|
|
|
// Pop the left parenthesis from the stack, but not onto the token queue.
|
|
sl--;
|
|
|
|
// If the token at the top of the stack is a function token, pop it onto the token queue.
|
|
if (sl > 0)
|
|
sc = stack[sl - 1];
|
|
|
|
}
|
|
else
|
|
return CALCULATE_ERROR_UNKNOWN_TOKEN;
|
|
}
|
|
++strpos;
|
|
}
|
|
// When there are no more tokens to read:
|
|
// While there are still operator tokens in the stack:
|
|
while (sl > 0)
|
|
{
|
|
sc = stack[sl - 1];
|
|
if (sc == '(' || sc == ')')
|
|
return CALCULATE_ERROR_PARENTHESES_MISMATCHED;
|
|
|
|
*(TokenPos) = 0;
|
|
error = RPNCalculate(token);
|
|
TokenPos = token;
|
|
if (error)return error;
|
|
*TokenPos = sc;
|
|
++TokenPos;
|
|
--sl;
|
|
}
|
|
|
|
*(TokenPos) = 0;
|
|
error = RPNCalculate(token);
|
|
TokenPos = token;
|
|
if (error)
|
|
{
|
|
*result = 0;
|
|
return error;
|
|
}
|
|
*result = *sp;
|
|
return CALCULATE_OK;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Time stuff
|
|
\*********************************************************************************************/
|
|
|
|
#define SECS_PER_MIN (60UL)
|
|
#define SECS_PER_HOUR (3600UL)
|
|
#define SECS_PER_DAY (SECS_PER_HOUR * 24UL)
|
|
#define DAYS_PER_WEEK (7UL)
|
|
#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK)
|
|
#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL)
|
|
#define SECS_YR_2000 (946684800UL) // the time at the start of y2k
|
|
#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
|
|
|
|
struct timeStruct {
|
|
uint8_t Second;
|
|
uint8_t Minute;
|
|
uint8_t Hour;
|
|
uint8_t Wday; // day of week, sunday is day 1
|
|
uint8_t Day;
|
|
uint8_t Month;
|
|
uint8_t Year; // offset from 1970;
|
|
} tm;
|
|
|
|
uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds
|
|
uint32_t sysTime = 0;
|
|
uint32_t prevMillis = 0;
|
|
uint32_t nextSyncTime = 0;
|
|
|
|
byte PrevMinutes = 0;
|
|
|
|
void breakTime(unsigned long timeInput, struct timeStruct &tm) {
|
|
uint8_t year;
|
|
uint8_t month, monthLength;
|
|
uint32_t time;
|
|
unsigned long days;
|
|
const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
|
|
|
time = (uint32_t)timeInput;
|
|
tm.Second = time % 60;
|
|
time /= 60; // now it is minutes
|
|
tm.Minute = time % 60;
|
|
time /= 60; // now it is hours
|
|
tm.Hour = time % 24;
|
|
time /= 24; // now it is days
|
|
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
|
|
|
|
year = 0;
|
|
days = 0;
|
|
while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
|
|
year++;
|
|
}
|
|
tm.Year = year; // year is offset from 1970
|
|
|
|
days -= LEAP_YEAR(year) ? 366 : 365;
|
|
time -= days; // now it is days in this year, starting at 0
|
|
|
|
days = 0;
|
|
month = 0;
|
|
monthLength = 0;
|
|
for (month = 0; month < 12; month++) {
|
|
if (month == 1) { // february
|
|
if (LEAP_YEAR(year)) {
|
|
monthLength = 29;
|
|
} else {
|
|
monthLength = 28;
|
|
}
|
|
} else {
|
|
monthLength = monthDays[month];
|
|
}
|
|
|
|
if (time >= monthLength) {
|
|
time -= monthLength;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
tm.Month = month + 1; // jan is month 1
|
|
tm.Day = time + 1; // day of month
|
|
}
|
|
|
|
void setTime(unsigned long t) {
|
|
sysTime = (uint32_t)t;
|
|
nextSyncTime = (uint32_t)t + syncInterval;
|
|
prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
|
|
}
|
|
|
|
unsigned long now() {
|
|
// calculate number of seconds passed since last call to now()
|
|
while (millis() - prevMillis >= 1000) {
|
|
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
|
|
sysTime++;
|
|
prevMillis += 1000;
|
|
}
|
|
if (nextSyncTime <= sysTime) {
|
|
unsigned long t = getNtpTime();
|
|
if (t != 0) {
|
|
if (Settings.DST)
|
|
t += SECS_PER_HOUR; // add one hour if DST active
|
|
setTime(t);
|
|
} else {
|
|
nextSyncTime = sysTime + syncInterval;
|
|
}
|
|
}
|
|
breakTime(sysTime, tm);
|
|
return (unsigned long)sysTime;
|
|
}
|
|
|
|
int hour()
|
|
{
|
|
return tm.Hour;
|
|
}
|
|
|
|
int minute()
|
|
{
|
|
return tm.Minute;
|
|
}
|
|
|
|
int second()
|
|
{
|
|
return tm.Second;
|
|
}
|
|
|
|
int weekday()
|
|
{
|
|
return tm.Wday;
|
|
}
|
|
|
|
void initTime()
|
|
{
|
|
nextSyncTime = 0;
|
|
now();
|
|
}
|
|
|
|
void checkTime()
|
|
{
|
|
now();
|
|
if (tm.Minute != PrevMinutes)
|
|
{
|
|
PluginCall(PLUGIN_CLOCK_IN, 0, dummyString);
|
|
PrevMinutes = tm.Minute;
|
|
if (Settings.UseRules)
|
|
{
|
|
String weekDays = F("AllSunMonTueWedThuFriSat");
|
|
String event = F("Clock#Time=");
|
|
event += weekDays.substring(weekday() * 3, weekday() * 3 + 3);
|
|
event += ",";
|
|
if (hour() < 10)
|
|
event += "0";
|
|
event += hour();
|
|
event += ":";
|
|
if (minute() < 10)
|
|
event += "0";
|
|
event += minute();
|
|
rulesProcessing(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
unsigned long getNtpTime()
|
|
{
|
|
portUDP.begin(123); // reuse existing socket
|
|
|
|
for (byte x = 1; x < 4; x++)
|
|
{
|
|
String log = F("NTP : NTP sync request:");
|
|
log += x;
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
|
|
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
|
|
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
|
|
|
|
IPAddress timeServerIP;
|
|
const char* ntpServerName = "pool.ntp.org";
|
|
|
|
//if (Settings.NTPHost[0] != 0)
|
|
// WiFi.hostByName(Settings.NTPHost, timeServerIP);
|
|
//else
|
|
// WiFi.hostByName(ntpServerName, timeServerIP);
|
|
|
|
while (portUDP.parsePacket() > 0) ; // discard any previously received packets
|
|
|
|
memset(packetBuffer, 0, NTP_PACKET_SIZE);
|
|
packetBuffer[0] = 0b11100011; // LI, Version, Mode
|
|
packetBuffer[1] = 0; // Stratum, or type of clock
|
|
packetBuffer[2] = 6; // Polling Interval
|
|
packetBuffer[3] = 0xEC; // Peer Clock Precision
|
|
packetBuffer[12] = 49;
|
|
packetBuffer[13] = 0x4E;
|
|
packetBuffer[14] = 49;
|
|
packetBuffer[15] = 52;
|
|
portUDP.beginPacket(ntpServerName, 123); //NTP requests are to port 123
|
|
portUDP.write(packetBuffer, NTP_PACKET_SIZE);
|
|
portUDP.endPacket();
|
|
|
|
uint32_t beginWait = millis();
|
|
while (millis() - beginWait < 1000) {
|
|
int size = portUDP.parsePacket();
|
|
if (size >= NTP_PACKET_SIZE) {
|
|
portUDP.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
|
|
unsigned long secsSince1900;
|
|
// convert four bytes starting at location 40 to a long integer
|
|
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
|
|
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
|
|
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
|
|
secsSince1900 |= (unsigned long)packetBuffer[43];
|
|
log = F("NTP : NTP replied: ");
|
|
log += millis() - beginWait;
|
|
log += F(" mSec");
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
if (Settings.UDPPort != 0)
|
|
portUDP.begin(Settings.UDPPort);
|
|
return secsSince1900 - 2208988800UL + Settings.TimeZone * SECS_PER_MIN;
|
|
}
|
|
}
|
|
log = F("NTP : No reply");
|
|
addLog(LOG_LEVEL_DEBUG_MORE, log);
|
|
}
|
|
return 0;
|
|
if (Settings.UDPPort != 0)
|
|
portUDP.begin(Settings.UDPPort);
|
|
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Rules processing
|
|
\*********************************************************************************************/
|
|
void rulesProcessing(String& event)
|
|
{
|
|
static uint8_t* data;
|
|
static byte nestingLevel;
|
|
|
|
String log = "";
|
|
|
|
nestingLevel++;
|
|
if (nestingLevel > RULES_MAX_NESTING_LEVEL)
|
|
{
|
|
log = F("EVENT: Error: Nesting level exceeded!");
|
|
addLog(LOG_LEVEL_ERROR, log);
|
|
nestingLevel--;
|
|
return;
|
|
}
|
|
|
|
log = F("EVENT: ");
|
|
log += event;
|
|
addLog(LOG_LEVEL_INFO, log);
|
|
|
|
// load rules from flash memory, stored in offset block 10
|
|
if (data == NULL)
|
|
{
|
|
data = new uint8_t[RULES_MAX_SIZE];
|
|
File f = SD.open("rules.txt");
|
|
if (f)
|
|
{
|
|
byte *pointerToByteToRead = data;
|
|
for (int x = 0; x < f.size(); x++)
|
|
{
|
|
*pointerToByteToRead = f.read();
|
|
pointerToByteToRead++;// next byte
|
|
}
|
|
data[f.size()] = 0;
|
|
f.close();
|
|
}
|
|
data[RULES_MAX_SIZE - 1] = 0; // make sure it's terminated!
|
|
}
|
|
|
|
int pos = 0;
|
|
String line = "";
|
|
boolean match = false;
|
|
boolean codeBlock = false;
|
|
boolean isCommand = false;
|
|
boolean conditional = false;
|
|
boolean condition = false;
|
|
boolean ifBranche = false;
|
|
|
|
while (data[pos] != 0)
|
|
{
|
|
if (data[pos] != 0 && data[pos] != 10)
|
|
line += (char)data[pos];
|
|
|
|
if (data[pos] == 10) // if line complete, parse this rule
|
|
{
|
|
line.replace("\r", "");
|
|
if (line.substring(0, 2) != "//" && line.length() > 0)
|
|
{
|
|
isCommand = true;
|
|
|
|
int comment = line.indexOf("//");
|
|
if (comment > 0)
|
|
line = line.substring(0, comment);
|
|
|
|
line = parseTemplate(line, line.length());
|
|
line.trim();
|
|
|
|
String lineOrg = line; // store original line for future use
|
|
line.toLowerCase(); // convert all to lower case to make checks easier
|
|
|
|
String eventTrigger = "";
|
|
String action = "";
|
|
|
|
if (!codeBlock) // do not check "on" rules if a block of actions is to be processed
|
|
{
|
|
if (line.startsWith("on "))
|
|
{
|
|
line = line.substring(3);
|
|
int split = line.indexOf(" do");
|
|
if (split != -1)
|
|
{
|
|
eventTrigger = line.substring(0, split);
|
|
action = lineOrg.substring(split + 7);
|
|
action.trim();
|
|
}
|
|
match = ruleMatch(event, eventTrigger);
|
|
if (action.length() > 0) // single on/do/action line, no block
|
|
{
|
|
isCommand = true;
|
|
codeBlock = false;
|
|
}
|
|
else
|
|
{
|
|
isCommand = false;
|
|
codeBlock = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
action = lineOrg;
|
|
}
|
|
|
|
String lcAction = action;
|
|
lcAction.toLowerCase();
|
|
if (lcAction == "endon") // Check if action block has ended, then we will wait for a new "on" rule
|
|
{
|
|
isCommand = false;
|
|
codeBlock = false;
|
|
}
|
|
|
|
if (match) // rule matched for one action or a block of actions
|
|
{
|
|
int split = lcAction.indexOf("if "); // check for optional "if" condition
|
|
if (split != -1)
|
|
{
|
|
conditional = true;
|
|
String check = lcAction.substring(split + 3);
|
|
condition = conditionMatch(check);
|
|
ifBranche = true;
|
|
isCommand = false;
|
|
}
|
|
|
|
if (lcAction == "else") // in case of an "else" block of actions, set ifBranche to false
|
|
{
|
|
ifBranche = false;
|
|
isCommand = false;
|
|
}
|
|
|
|
if (lcAction == "endif") // conditional block ends here
|
|
{
|
|
conditional = false;
|
|
isCommand = false;
|
|
}
|
|
|
|
// process the action if it's a command and unconditional, or conditional and the condition matches the if or else block.
|
|
if (isCommand && ((!conditional) || (conditional && (condition == ifBranche))))
|
|
{
|
|
int equalsPos = event.indexOf("=");
|
|
if (equalsPos > 0)
|
|
{
|
|
String tmpString = event.substring(equalsPos + 1);
|
|
action.replace("%eventvalue%", tmpString); // substitute %eventvalue% in actions with the actual value from the event
|
|
}
|
|
log = F("ACT : ");
|
|
log += action;
|
|
addLog(LOG_LEVEL_INFO, log);
|
|
|
|
struct EventStruct TempEvent;
|
|
parseCommandString(&TempEvent, action);
|
|
yield();
|
|
if (!PluginCall(PLUGIN_WRITE, &TempEvent, action))
|
|
ExecuteCommand(VALUE_SOURCE_SYSTEM, action.c_str());
|
|
yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
line = "";
|
|
}
|
|
pos++;
|
|
}
|
|
|
|
nestingLevel--;
|
|
if (nestingLevel == 0)
|
|
{
|
|
delete [] data;
|
|
data = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Check if an event matches to a given rule
|
|
\*********************************************************************************************/
|
|
boolean ruleMatch(String& event, String& rule)
|
|
{
|
|
boolean match = false;
|
|
String tmpEvent = event;
|
|
String tmpRule = rule;
|
|
|
|
// Special handling of literal string events, they should start with '!'
|
|
if (event.charAt(0) == '!')
|
|
{
|
|
if (event.equalsIgnoreCase(rule))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
if (event.startsWith("Clock#Time")) // clock events need different handling...
|
|
{
|
|
int pos1 = event.indexOf("=");
|
|
int pos2 = rule.indexOf("=");
|
|
if (pos1 > 0 && pos2 > 0)
|
|
{
|
|
tmpEvent = event.substring(0, pos1);
|
|
tmpRule = rule.substring(0, pos2);
|
|
if (tmpRule.equalsIgnoreCase(tmpEvent)) // if this is a clock rule
|
|
{
|
|
tmpEvent = event.substring(pos1 + 1);
|
|
tmpRule = rule.substring(pos2 + 1);
|
|
unsigned long clockEvent = string2TimeLong(tmpEvent);
|
|
unsigned long clockSet = string2TimeLong(tmpRule);
|
|
unsigned long Mask;
|
|
for (byte y = 0; y < 8; y++)
|
|
{
|
|
if (((clockSet >> (y * 4)) & 0xf) == 0xf) // if nibble y has the wildcard value 0xf
|
|
{
|
|
Mask = 0xffffffff ^ (0xFUL << (y * 4)); // Mask to wipe nibble position y.
|
|
clockEvent &= Mask; // clear nibble
|
|
clockEvent |= (0xFUL << (y * 4)); // fill with wildcard value 0xf
|
|
}
|
|
}
|
|
if (clockEvent == clockSet)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// parse event into verb and value
|
|
float value = 0;
|
|
int pos = event.indexOf("=");
|
|
if (pos)
|
|
{
|
|
tmpEvent = event.substring(pos + 1);
|
|
value = tmpEvent.toFloat();
|
|
tmpEvent = event.substring(0, pos);
|
|
}
|
|
|
|
// parse rule
|
|
int comparePos = 0;
|
|
char compare = ' ';
|
|
comparePos = rule.indexOf(">");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '>';
|
|
}
|
|
else
|
|
{
|
|
comparePos = rule.indexOf("<");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '<';
|
|
}
|
|
else
|
|
{
|
|
comparePos = rule.indexOf("=");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '=';
|
|
}
|
|
}
|
|
}
|
|
|
|
float ruleValue = 0;
|
|
|
|
if (comparePos > 0)
|
|
{
|
|
tmpRule = rule.substring(comparePos + 1);
|
|
ruleValue = tmpRule.toFloat();
|
|
tmpRule = rule.substring(0, comparePos);
|
|
}
|
|
|
|
switch (compare)
|
|
{
|
|
case '>':
|
|
if (tmpRule.equalsIgnoreCase(tmpEvent) && value > ruleValue)
|
|
match = true;
|
|
break;
|
|
|
|
case '<':
|
|
if (tmpRule.equalsIgnoreCase(tmpEvent) && value < ruleValue)
|
|
match = true;
|
|
break;
|
|
|
|
case '=':
|
|
if (tmpRule.equalsIgnoreCase(tmpEvent) && value == ruleValue)
|
|
match = true;
|
|
break;
|
|
|
|
case ' ':
|
|
if (tmpRule.equalsIgnoreCase(tmpEvent))
|
|
match = true;
|
|
break;
|
|
}
|
|
|
|
return match;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Check expression
|
|
\*********************************************************************************************/
|
|
boolean conditionMatch(String& check)
|
|
{
|
|
boolean match = false;
|
|
|
|
int comparePos = 0;
|
|
char compare = ' ';
|
|
comparePos = check.indexOf(">");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '>';
|
|
}
|
|
else
|
|
{
|
|
comparePos = check.indexOf("<");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '<';
|
|
}
|
|
else
|
|
{
|
|
comparePos = check.indexOf("=");
|
|
if (comparePos > 0)
|
|
{
|
|
compare = '=';
|
|
}
|
|
}
|
|
}
|
|
|
|
float Value1 = 0;
|
|
float Value2 = 0;
|
|
|
|
if (comparePos > 0)
|
|
{
|
|
String tmpCheck = check.substring(comparePos + 1);
|
|
Value2 = tmpCheck.toFloat();
|
|
tmpCheck = check.substring(0, comparePos);
|
|
Value1 = tmpCheck.toFloat();
|
|
}
|
|
else
|
|
return false;
|
|
|
|
switch (compare)
|
|
{
|
|
case '>':
|
|
if (Value1 > Value2)
|
|
match = true;
|
|
break;
|
|
|
|
case '<':
|
|
if (Value1 < Value2)
|
|
match = true;
|
|
break;
|
|
|
|
case '=':
|
|
if (Value1 == Value2)
|
|
match = true;
|
|
break;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Check rule timers
|
|
\*********************************************************************************************/
|
|
void rulesTimers()
|
|
{
|
|
for (byte x = 0; x < RULES_TIMER_MAX; x++)
|
|
{
|
|
if (RulesTimer[x] != 0L) // timer active?
|
|
{
|
|
if (RulesTimer[x] < millis()) // timer finished?
|
|
{
|
|
RulesTimer[x] = 0L; // turn off this timer
|
|
String event = F("Rules#Timer=");
|
|
event += x + 1;
|
|
rulesProcessing(event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************************************\
|
|
Generate rule events based on task refresh
|
|
\*********************************************************************************************/
|
|
|
|
void createRuleEvents(byte TaskIndex)
|
|
{
|
|
LoadTaskSettings(TaskIndex);
|
|
byte BaseVarIndex = TaskIndex * VARS_PER_TASK;
|
|
byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[TaskIndex]);
|
|
byte sensorType = Device[DeviceIndex].VType;
|
|
for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++)
|
|
{
|
|
String eventString = ExtraTaskSettings.TaskDeviceName;
|
|
eventString += F("#");
|
|
eventString += ExtraTaskSettings.TaskDeviceValueNames[varNr];
|
|
eventString += F("=");
|
|
|
|
if (sensorType == SENSOR_TYPE_LONG)
|
|
eventString += (unsigned long)UserVar[BaseVarIndex] + ((unsigned long)UserVar[BaseVarIndex + 1] << 16);
|
|
else
|
|
eventString += UserVar[BaseVarIndex + varNr];
|
|
|
|
rulesProcessing(eventString);
|
|
}
|
|
}
|
|
|