commit 3a5eab5ac966ada536fb576037ecf3ad7aeb1698 Author: mvdbro Date: Thu Jan 5 08:44:36 2017 +0100 R147 diff --git a/ArduinoEasy.ino b/ArduinoEasy.ino new file mode 100644 index 0000000..83881f6 --- /dev/null +++ b/ArduinoEasy.ino @@ -0,0 +1,813 @@ +/****************************************************************************************************************************\ + * Arduino project "Arduino Easy" © Copyright www.letscontrolit.com + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You received a copy of the GNU General Public License along with this program in file 'License.txt'. + * + * IDE download : https://www.arduino.cc/en/Main/Software + * + * Source Code : https://github.com/ESP8266nu/ESPEasy + * Support : http://www.letscontrolit.com + * Discussion : http://www.letscontrolit.com/forum/ + * + * Additional information about licensing can be found at : http://www.gnu.org/licenses +\*************************************************************************************************************************/ + +// This file incorporates work covered by the following copyright and permission notice: + +/****************************************************************************************************************************\ +* Arduino project "Nodo" © Copyright 2010..2015 Paul Tonkes +* +* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +* You received a copy of the GNU General Public License along with this program in file 'License.txt'. +* +* Voor toelichting op de licentievoorwaarden zie : http://www.gnu.org/licenses +* Uitgebreide documentatie is te vinden op : http://www.nodo-domotica.nl +* Compiler voor deze programmacode te downloaden op : http://arduino.cc +\*************************************************************************************************************************/ + +// ******************************************************************************** +// User specific configuration +// ******************************************************************************** + +// Set default configuration settings if you want (not mandatory) +// You can always change these during runtime and save to eeprom +// After loading firmware, issue a 'reset' command to load the defaults. + +#define DEFAULT_NAME "newdevice" // Enter your device friendly name +#define DEFAULT_SERVER "192.168.0.8" // Enter your Domoticz Server IP address +#define DEFAULT_PORT 8080 // Enter your Domoticz Server port value +#define DEFAULT_DELAY 60 // Enter your Send delay in seconds + +#define DEFAULT_USE_STATIC_IP false // true or false enabled or disabled set static IP +#define DEFAULT_IP "192.168.0.50" // Enter your IP address +#define DEFAULT_DNS "192.168.0.1" // Enter your DNS +#define DEFAULT_GW "192.168.0.1" // Enter your gateway +#define DEFAULT_SUBNET "255.255.255.0" // Enter your subnet + +#define DEFAULT_PROTOCOL 1 // Protocol used for controller communications +#define UNIT 0 + +#define FEATURE_MQTT true +#define FEATURE_MQTT_DOM false // Not tested yet! + +// ******************************************************************************** +// DO NOT CHANGE ANYTHING BELOW THIS LINE +// ******************************************************************************** + +// Challenges on Arduino/W5100 ethernet platform: +// Only 4 ethernet sockets: +// 1: UPD traffic server/send +// 2: Webserver +// 3: MQTT client +// 4: Webclient, active when webserver serves an incoming client or outgoing webclient calls. + +#define socketdebug false +#define ARDUINO_PROJECT_PID 2016110201L +#define VERSION 2 +#define BUILD 147 +#define BUILD_NOTES "" + +#define NODE_TYPE_ID_ESP_EASY_STD 1 +#define NODE_TYPE_ID_ESP_EASY4M_STD 17 +#define NODE_TYPE_ID_ESP_EASY32_STD 33 +#define NODE_TYPE_ID_ARDUINO_EASY_STD 65 +#define NODE_TYPE_ID NODE_TYPE_ID_ARDUINO_EASY_STD + +#define CPLUGIN_PROTOCOL_ADD 1 +#define CPLUGIN_PROTOCOL_TEMPLATE 2 +#define CPLUGIN_PROTOCOL_SEND 3 +#define CPLUGIN_PROTOCOL_RECV 4 +#define CPLUGIN_GET_DEVICENAME 5 +#define CPLUGIN_WEBFORM_SAVE 6 +#define CPLUGIN_WEBFORM_LOAD 7 + +#define LOG_LEVEL_ERROR 1 +#define LOG_LEVEL_INFO 2 +#define LOG_LEVEL_DEBUG 3 +#define LOG_LEVEL_DEBUG_MORE 4 + +#define CMD_REBOOT 89 + +#define DEVICES_MAX 8 // ESP Easy 64 +#define TASKS_MAX 8 // ESP Easy 12 +#define VARS_PER_TASK 4 +#define PLUGIN_MAX 8 // ESP Easy 64 +#define PLUGIN_CONFIGVAR_MAX 8 +#define PLUGIN_CONFIGFLOATVAR_MAX 4 +#define PLUGIN_CONFIGLONGVAR_MAX 4 +#define PLUGIN_EXTRACONFIGVAR_MAX 16 +#define CPLUGIN_MAX 4 // ESP Easy 16 +#define UNIT_MAX 32 // Only relevant for UDP unicast message 'sweeps' and the nodelist. +#define RULES_TIMER_MAX 8 +#define SYSTEM_TIMER_MAX 2 // ESP Easy 8 +#define SYSTEM_CMD_TIMER_MAX 1 // ESP Easy 2 +#define PINSTATE_TABLE_MAX 16 // ESP Easy 32 +#define RULES_MAX_SIZE 512 // ESP Easy 2048 +#define RULES_MAX_NESTING_LEVEL 3 + +#define PIN_MODE_UNDEFINED 0 +#define PIN_MODE_INPUT 1 +#define PIN_MODE_OUTPUT 2 +#define PIN_MODE_PWM 3 +#define PIN_MODE_SERVO 4 + +#define SEARCH_PIN_STATE true +#define NO_SEARCH_PIN_STATE false + +#define DEVICE_TYPE_SINGLE 1 // connected through 1 datapin +#define DEVICE_TYPE_I2C 2 // connected through I2C +#define DEVICE_TYPE_ANALOG 3 // tout pin +#define DEVICE_TYPE_DUAL 4 // connected through 2 datapins +#define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection + +#define SENSOR_TYPE_SINGLE 1 +#define SENSOR_TYPE_TEMP_HUM 2 +#define SENSOR_TYPE_TEMP_BARO 3 +#define SENSOR_TYPE_TEMP_HUM_BARO 4 +#define SENSOR_TYPE_DUAL 5 +#define SENSOR_TYPE_TRIPLE 6 +#define SENSOR_TYPE_QUAD 7 +#define SENSOR_TYPE_SWITCH 10 +#define SENSOR_TYPE_DIMMER 11 +#define SENSOR_TYPE_LONG 20 + +#define PLUGIN_INIT_ALL 1 +#define PLUGIN_INIT 2 +#define PLUGIN_READ 3 +#define PLUGIN_ONCE_A_SECOND 4 +#define PLUGIN_TEN_PER_SECOND 5 +#define PLUGIN_DEVICE_ADD 6 +#define PLUGIN_EVENTLIST_ADD 7 +#define PLUGIN_WEBFORM_SAVE 8 +#define PLUGIN_WEBFORM_LOAD 9 +#define PLUGIN_WEBFORM_SHOW_VALUES 10 +#define PLUGIN_GET_DEVICENAME 11 +#define PLUGIN_GET_DEVICEVALUENAMES 12 +#define PLUGIN_WRITE 13 +#define PLUGIN_EVENT_OUT 14 +#define PLUGIN_WEBFORM_SHOW_CONFIG 15 +#define PLUGIN_SERIAL_IN 16 +#define PLUGIN_UDP_IN 17 +#define PLUGIN_CLOCK_IN 18 +#define PLUGIN_TIMER_IN 19 + +#define VALUE_SOURCE_SYSTEM 1 +#define VALUE_SOURCE_SERIAL 2 +#define VALUE_SOURCE_HTTP 3 +#define VALUE_SOURCE_MQTT 4 +#define VALUE_SOURCE_UDP 5 + +#include +#include +#include +#include +#include +#if FEATURE_MQTT +#include +#include +#endif + +void(*Reboot)(void)=0; + +byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; + +// WebServer +EthernetServer WebServer(80); + +#if FEATURE_MQTT +EthernetClient mqtt; +PubSubClient MQTTclient(mqtt); +#endif + +#define EthernetShield_CS_SDCard 4 +#define EthernetShield_CS_W5100 10 + +// syslog stuff +EthernetUDP portUDP; + +struct SecurityStruct +{ + char ControllerUser[26]; + char ControllerPassword[64]; + char Password[26]; +} SecuritySettings; + +struct SettingsStruct +{ + unsigned long PID; + int Version; + byte Unit; + int16_t Build; + byte IP[4]; + byte Gateway[4]; + byte Subnet[4]; + byte DNS[4]; + byte Controller_IP[4]; + unsigned int ControllerPort; + byte IP_Octet; + char NTPHost[64]; + unsigned long Delay; + byte Syslog_IP[4]; + unsigned int UDPPort; + byte Protocol; + char Name[26]; + byte SyslogLevel; + byte SerialLogLevel; + unsigned long BaudRate; + unsigned long MessageDelay; + boolean CustomCSS; + char ControllerHostName[64]; + boolean UseNTP; + boolean DST; + byte WDI2CAddress; + int8_t PinBootStates[17]; + byte UseDNS; + boolean UseRules; + int8_t Pin_status_led; + boolean UseSerial; + boolean GlobalSync; + unsigned long ConnectionFailuresThreshold; + int16_t TimeZone; + byte SDLogLevel; + byte TaskDeviceNumber[TASKS_MAX]; + unsigned int TaskDeviceID[TASKS_MAX]; + int8_t TaskDevicePin1[TASKS_MAX]; + int8_t TaskDevicePin2[TASKS_MAX]; + byte TaskDevicePort[TASKS_MAX]; + boolean TaskDevicePin1PullUp[TASKS_MAX]; + int16_t TaskDevicePluginConfig[TASKS_MAX][PLUGIN_CONFIGVAR_MAX]; + boolean TaskDevicePin1Inversed[TASKS_MAX]; + float TaskDevicePluginConfigFloat[TASKS_MAX][PLUGIN_CONFIGFLOATVAR_MAX]; + long TaskDevicePluginConfigLong[TASKS_MAX][PLUGIN_CONFIGLONGVAR_MAX]; + boolean TaskDeviceSendData[TASKS_MAX]; + boolean TaskDeviceGlobalSync[TASKS_MAX]; + int8_t TaskDevicePin3[TASKS_MAX]; + byte TaskDeviceDataFeed[TASKS_MAX]; + unsigned long TaskDeviceTimer[TASKS_MAX]; + boolean MQTTRetainFlag; + char MQTTpublish[81]; + char MQTTsubscribe[81]; +} Settings; + +struct ExtraTaskSettingsStruct +{ + byte TaskIndex; + char TaskDeviceName[41]; + char TaskDeviceFormula[VARS_PER_TASK][41]; + char TaskDeviceValueNames[VARS_PER_TASK][41]; + long TaskDevicePluginConfigLong[PLUGIN_EXTRACONFIGVAR_MAX]; + byte TaskDeviceValueDecimals[VARS_PER_TASK]; +} ExtraTaskSettings; + +struct EventStruct +{ + byte Source; + byte TaskIndex; + byte BaseVarIndex; + int idx; + byte sensorType; + int Par1; + int Par2; + int Par3; + byte OriginTaskIndex; + String String1; + String String2; + byte *Data; +}; + +struct DeviceStruct +{ + byte Number; + byte Type; + byte VType; + byte Ports; + boolean PullUpOption; + boolean InverseLogicOption; + boolean FormulaOption; + byte ValueCount; + boolean Custom; + boolean SendDataOption; + boolean GlobalSyncOption; + boolean TimerOption; + boolean TimerOptional; + boolean DecimalsOnly; +} Device[DEVICES_MAX + 1]; // 1 more because first device is empty device + +struct ProtocolStruct +{ + byte Number; + boolean usesMQTT; + boolean usesAccount; + boolean usesPassword; + int defaultPort; + boolean usesTemplate; +} Protocol[CPLUGIN_MAX]; + +struct NodeStruct +{ + byte ip[4]; + byte age; + uint16_t build; +} Nodes[UNIT_MAX]; + +struct systemTimerStruct +{ + unsigned long timer; + byte plugin; + byte Par1; + byte Par2; + byte Par3; +} systemTimers[SYSTEM_TIMER_MAX]; + +struct systemCMDTimerStruct +{ + unsigned long timer; + String action; +} systemCMDTimers[SYSTEM_CMD_TIMER_MAX]; + +struct pinStatesStruct +{ + byte plugin; + byte index; + byte mode; + uint16_t value; +} pinStates[PINSTATE_TABLE_MAX]; + +int deviceCount = -1; +int protocolCount = -1; + +boolean printToWeb = false; +String printWebString = ""; +boolean printToWebJSON = false; + +float UserVar[VARS_PER_TASK * TASKS_MAX]; +unsigned long RulesTimer[RULES_TIMER_MAX]; + +unsigned long timerSensor[TASKS_MAX]; +unsigned long timer; +unsigned long timer100ms; +unsigned long timer1s; +unsigned long timerwd; +unsigned long lastSend; +byte cmd_within_mainloop = 0; +unsigned long connectionFailures; +unsigned long wdcounter = 0; + +boolean WebLoggedIn = false; +int WebLoggedInTimer = 300; + +boolean (*Plugin_ptr[PLUGIN_MAX])(byte, struct EventStruct*, String&); +byte Plugin_id[PLUGIN_MAX]; + +boolean (*CPlugin_ptr[CPLUGIN_MAX])(byte, struct EventStruct*, String&); +byte CPlugin_id[CPLUGIN_MAX]; + +String dummyString = ""; + +boolean systemOK = false; + +unsigned long start = 0; +unsigned long elapsed = 0; +unsigned long loopCounter = 0; +unsigned long loopCounterLast = 0; +unsigned long loopCounterMax = 1; + +unsigned long flashWrites = 0; + +String eventBuffer = ""; + +/*********************************************************************************************\ + * SETUP +\*********************************************************************************************/ +void setup() +{ + Serial.begin(115200); + + fileSystemCheck(); + + emergencyReset(); + + LoadSettings(); + + ExtraTaskSettings.TaskIndex = 255; // make sure this is an unused nr to prevent cache load on boot + + // if different version, eeprom settings structure has changed. Full Reset needed + // on a fresh ESP module eeprom values are set to 255. Version results into -1 (signed int) + if (Settings.Version == VERSION && Settings.PID == ARDUINO_PROJECT_PID) + { + systemOK = true; + } + else + { + // Direct Serial is allowed here, since this is only an emergency task. + Serial.print(F("\nPID:")); + Serial.println(Settings.PID); + Serial.print(F("Version:")); + Serial.println(Settings.Version); + Serial.println(F("INIT : Incorrect PID or version!")); + delay(1000); + ResetFactory(); + } + + if (systemOK) + { + if (Settings.UseSerial) + Serial.begin(Settings.BaudRate); + + if (Settings.Build != BUILD) + BuildFixes(); + + String log = F("\nINIT : Booting Build nr:"); + log += BUILD; + addLog(LOG_LEVEL_INFO, log); + + hardwareInit(); + PluginInit(); + CPluginInit(); + + mac[5] = Settings.Unit; // make sure every unit has a unique mac address + if (Settings.IP[0] == 0) + Ethernet.begin(mac); + else + Ethernet.begin(mac, Settings.IP, Settings.DNS, Settings.Gateway, Settings.Subnet); + + // setup UDP + if (Settings.UDPPort != 0) + portUDP.begin(Settings.UDPPort); + +#if FEATURE_MQTT + // Setup MQTT Client + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + if (Protocol[ProtocolIndex].usesMQTT) + MQTTConnect(); +#endif + + sendSysInfoUDP(3); + + log = F("INIT : Boot OK"); + addLog(LOG_LEVEL_INFO, log); + + // Setup timers + byte bootMode = 0; + if (bootMode == 0) + { + for (byte x = 0; x < TASKS_MAX; x++) + if (Settings.TaskDeviceTimer[x] !=0) + timerSensor[x] = millis() + 30000 + (x * Settings.MessageDelay); + + timer = millis() + 30000; // startup delay 30 sec + } + else + { + for (byte x = 0; x < TASKS_MAX; x++) + timerSensor[x] = millis() + 0; + timer = millis() + 0; // no startup from deepsleep wake up + } + + timer100ms = millis() + 100; // timer for periodic actions 10 x per/sec + timer1s = millis() + 1000; // timer for periodic actions once per/sec + timerwd = millis() + 30000; // timer for watchdog once per 30 sec + + if (Settings.UseNTP) + initTime(); + + if (Settings.UseRules) + { + String event = F("System#Boot"); + rulesProcessing(event); + } + + } + else + { + Serial.println(F("Entered Rescue mode!")); + } +} + + +/*********************************************************************************************\ + * MAIN LOOP +\*********************************************************************************************/ +void loop() +{ + loopCounter++; + + if (Settings.UseSerial) + if (Serial.available()) + if (!PluginCall(PLUGIN_SERIAL_IN, 0, dummyString)) + serial(); + + if (systemOK) + { + if (millis() > timer100ms) + run10TimesPerSecond(); + + if (millis() > timer1s) + runOncePerSecond(); + + if (millis() > timerwd) + runEach30Seconds(); + + backgroundtasks(); + + } + else + delay(1); +} + + +/*********************************************************************************************\ + * Tasks that run 10 times per second +\*********************************************************************************************/ +void run10TimesPerSecond() +{ + start = micros(); + timer100ms = millis() + 100; + PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummyString); + checkUDP(); + if (Settings.UseRules && eventBuffer.length() > 0) + { + rulesProcessing(eventBuffer); + eventBuffer = ""; + } + elapsed = micros() - start; +} + + +/*********************************************************************************************\ + * Tasks each second +\*********************************************************************************************/ +void runOncePerSecond() +{ + timer1s = millis() + 1000; + + checkSensors(); + + if (Settings.ConnectionFailuresThreshold) + if (connectionFailures > Settings.ConnectionFailuresThreshold) + delayedReboot(60); + + if (cmd_within_mainloop != 0) + { + switch (cmd_within_mainloop) + { + case CMD_REBOOT: + { + Reboot(); + break; + } + } + cmd_within_mainloop = 0; + } + + // clock events + if (Settings.UseNTP) + checkTime(); + + unsigned long timer = micros(); + PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummyString); + + checkSystemTimers(); + + if (Settings.UseRules) + rulesTimers(); + + timer = micros() - timer; + + if (SecuritySettings.Password[0] != 0) + { + if (WebLoggedIn) + WebLoggedInTimer++; + if (WebLoggedInTimer > 300) + WebLoggedIn = false; + } + + // I2C Watchdog feed + if (Settings.WDI2CAddress != 0) + { + Wire.beginTransmission(Settings.WDI2CAddress); + Wire.write(0xA5); + Wire.endTransmission(); + } + + if (Settings.SerialLogLevel == 5) + { + Serial.print(F("10 ps:")); + Serial.print(elapsed); + Serial.print(F(" uS 1 ps:")); + Serial.println(timer); + } +} + +/*********************************************************************************************\ + * Tasks each 30 seconds +\*********************************************************************************************/ +void runEach30Seconds() +{ + wdcounter++; + timerwd = millis() + 30000; + String log = F("WD : Uptime "); + log += wdcounter / 2; + log += F(" ConnectFailures "); + log += connectionFailures; + log += F(" Freemem "); + log += FreeMem(); + addLog(LOG_LEVEL_INFO, log); + sendSysInfoUDP(1); + refreshNodeList(); + loopCounterLast = loopCounter; + loopCounter = 0; + if (loopCounterLast > loopCounterMax) + loopCounterMax = loopCounterLast; + +} + + +/*********************************************************************************************\ + * Check sensor timers +\*********************************************************************************************/ +void checkSensors() +{ + for (byte x = 0; x < TASKS_MAX; x++) + { + if ((Settings.TaskDeviceTimer[x] != 0) && (millis() > timerSensor[x])) + { + timerSensor[x] = millis() + Settings.TaskDeviceTimer[x] * 1000; + if (timerSensor[x] == 0) // small fix if result is 0, else timer will be stopped... + timerSensor[x] = 1; + SensorSendTask(x); + } + } +} + + +/*********************************************************************************************\ + * send all sensordata +\*********************************************************************************************/ +void SensorSend() +{ + for (byte x = 0; x < TASKS_MAX; x++) + { + SensorSendTask(x); + } +} + + +/*********************************************************************************************\ + * send specific sensor task data +\*********************************************************************************************/ +void SensorSendTask(byte TaskIndex) +{ + if (Settings.TaskDeviceID[TaskIndex] != 0) + { + byte varIndex = TaskIndex * VARS_PER_TASK; + + boolean success = false; + byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[TaskIndex]); + LoadTaskSettings(TaskIndex); + + struct EventStruct TempEvent; + TempEvent.TaskIndex = TaskIndex; + TempEvent.BaseVarIndex = varIndex; + TempEvent.idx = Settings.TaskDeviceID[TaskIndex]; + TempEvent.sensorType = Device[DeviceIndex].VType; + + float preValue[VARS_PER_TASK]; // store values before change, in case we need it in the formula + for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) + preValue[varNr] = UserVar[varIndex + varNr]; + + if(Settings.TaskDeviceDataFeed[TaskIndex] == 0) // only read local connected sensorsfeeds + success = PluginCall(PLUGIN_READ, &TempEvent, dummyString); + else + success = true; + + if (success) + { + for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) + { + if (ExtraTaskSettings.TaskDeviceFormula[varNr][0] != 0) + { + String spreValue = String(preValue[varNr]); + String formula = ExtraTaskSettings.TaskDeviceFormula[varNr]; + float value = UserVar[varIndex + varNr]; + float result = 0; + String svalue = String(value); + formula.replace("%pvalue%", spreValue); + formula.replace("%value%", svalue); + byte error = Calculate(formula.c_str(), &result); + if (error == 0) + UserVar[varIndex + varNr] = result; + } + } + sendData(&TempEvent); + } + } +} + + +/*********************************************************************************************\ + * set global system timer +\*********************************************************************************************/ +boolean setSystemTimer(unsigned long timer, byte plugin, byte Par1, byte Par2, byte Par3) +{ + // plugin number and par1 form a unique key that can be used to restart a timer + // first check if a timer is not already running for this request + boolean reUse = false; + for (byte x = 0; x < SYSTEM_TIMER_MAX; x++) + if (systemTimers[x].timer != 0) + { + if ((systemTimers[x].plugin == plugin) && (systemTimers[x].Par1 == Par1)) + { + systemTimers[x].timer = millis() + timer; + reUse = true; + break; + } + } + + if (!reUse) + { + // find a new free timer slot... + for (byte x = 0; x < SYSTEM_TIMER_MAX; x++) + if (systemTimers[x].timer == 0) + { + systemTimers[x].timer = millis() + timer; + systemTimers[x].plugin = plugin; + systemTimers[x].Par1 = Par1; + systemTimers[x].Par2 = Par2; + systemTimers[x].Par3 = Par3; + break; + } + } +} + + +/*********************************************************************************************\ + * set global system command timer +\*********************************************************************************************/ +boolean setSystemCMDTimer(unsigned long timer, String& action) +{ + for (byte x = 0; x < SYSTEM_CMD_TIMER_MAX; x++) + if (systemCMDTimers[x].timer == 0) + { + systemCMDTimers[x].timer = millis() + timer; + systemCMDTimers[x].action = action; + break; + } +} + + +/*********************************************************************************************\ + * check global system timers +\*********************************************************************************************/ +boolean checkSystemTimers() +{ + for (byte x = 0; x < SYSTEM_TIMER_MAX; x++) + if (systemTimers[x].timer != 0) + { + if (timeOut(systemTimers[x].timer)) + { + struct EventStruct TempEvent; + TempEvent.Par1 = systemTimers[x].Par1; + TempEvent.Par2 = systemTimers[x].Par2; + TempEvent.Par3 = systemTimers[x].Par3; + for (byte y = 0; y < PLUGIN_MAX; y++) + if (Plugin_id[y] == systemTimers[x].plugin) + Plugin_ptr[y](PLUGIN_TIMER_IN, &TempEvent, dummyString); + systemTimers[x].timer = 0; + } + } + + for (byte x = 0; x < SYSTEM_CMD_TIMER_MAX; x++) + if (systemCMDTimers[x].timer != 0) + if (timeOut(systemCMDTimers[x].timer)) + { + struct EventStruct TempEvent; + parseCommandString(&TempEvent, systemCMDTimers[x].action); + if (!PluginCall(PLUGIN_WRITE, &TempEvent, systemCMDTimers[x].action)) + ExecuteCommand(VALUE_SOURCE_SYSTEM, systemCMDTimers[x].action.c_str()); + systemCMDTimers[x].timer = 0; + systemCMDTimers[x].action = ""; + } +} + + +/*********************************************************************************************\ + * run background tasks +\*********************************************************************************************/ +void backgroundtasks() +{ + WebServerHandleClient(); +#if FEATURE_MQTT + MQTTclient.loop(); +#endif + statusLED(false); + checkUDP(); +} + diff --git a/Command.ino b/Command.ino new file mode 100644 index 0000000..afd4172 --- /dev/null +++ b/Command.ino @@ -0,0 +1,281 @@ +#define INPUT_COMMAND_SIZE 80 +void ExecuteCommand(byte source, const char *Line) +{ + String status = ""; + boolean success = false; + char TmpStr1[80]; + TmpStr1[0] = 0; + char Command[80]; + Command[0] = 0; + int Par1 = 0; + int Par2 = 0; + int Par3 = 0; + + GetArgv(Line, Command, 1); + if (GetArgv(Line, TmpStr1, 2)) Par1 = str2int(TmpStr1); + if (GetArgv(Line, TmpStr1, 3)) Par2 = str2int(TmpStr1); + if (GetArgv(Line, TmpStr1, 4)) Par3 = str2int(TmpStr1); + + // **************************************** + // commands for debugging + // **************************************** + + if (strcasecmp_P(Command, PSTR("w5100")) == 0) + { + success = true; + ShowSocketStatus(); + } + + if (strcasecmp_P(Command, PSTR("ntp")) == 0) + { + success = true; + getNtpTime(); + } + + if (strcasecmp_P(Command, PSTR("sdcard")) == 0) + { + success = true; + SelectSDCard(true); + File root = SD.open("/"); + root.rewindDirectory(); + printDirectory(root, 0); + root.close(); + } + + if (strcasecmp_P(Command, PSTR("sysload")) == 0) + { + success = true; + Serial.print(100 - (100 * loopCounterLast / loopCounterMax)); + Serial.print(F("% (LC=")); + Serial.print(int(loopCounterLast / 30)); + Serial.println(F(")")); + } + + if (strcasecmp_P(Command, PSTR("meminfo")) == 0) + { + success = true; + Serial.print(F("SecurityStruct : ")); + Serial.println(sizeof(SecuritySettings)); + Serial.print(F("SettingsStruct : ")); + Serial.println(sizeof(Settings)); + Serial.print(F("ExtraTaskSettingsStruct: ")); + Serial.println(sizeof(ExtraTaskSettings)); + } + + if (strcasecmp_P(Command, PSTR("TaskClear")) == 0) + { + success = true; + taskClear(Par1 - 1, true); + } + + if (strcasecmp_P(Command, PSTR("build")) == 0) + { + success = true; + Settings.Build = Par1; + SaveSettings(); + } + + // **************************************** + // commands for rules + // **************************************** + + if (strcasecmp_P(Command, PSTR("TaskValueSet")) == 0) + { + success = true; + if (GetArgv(Line, TmpStr1, 4)) + { + float result = 0; + byte error = Calculate(TmpStr1, &result); + UserVar[(VARS_PER_TASK * (Par1 - 1)) + Par2 - 1] = result; + } + } + + if (strcasecmp_P(Command, PSTR("TaskRun")) == 0) + { + success = true; + SensorSendTask(Par1 -1); + } + + if (strcasecmp_P(Command, PSTR("TimerSet")) == 0) + { + success = true; + RulesTimer[Par1 - 1] = millis() + (1000 * Par2); + } + + if (strcasecmp_P(Command, PSTR("Delay")) == 0) + { + success = true; + delayMillis(Par1); + } + + if (strcasecmp_P(Command, PSTR("Rules")) == 0) + { + success = true; + if (Par1 == 1) + Settings.UseRules = true; + else + Settings.UseRules = false; + } + + if (strcasecmp_P(Command, PSTR("Event")) == 0) + { + success = true; + String event = Line; + event = event.substring(6); + event.replace("$", "#"); + if (Settings.UseRules) + rulesProcessing(event); + } + + if (strcasecmp_P(Command, PSTR("SendTo")) == 0) + { + success = true; + String event = Line; + event = event.substring(7); + int index = event.indexOf(','); + if (index > 0) + { + event = event.substring(index+1); + SendUDPCommand(Par1, (char*)event.c_str(), event.length()); + } + } + + if (strcasecmp_P(Command, PSTR("SendToUDP")) == 0) + { + success = true; + String strLine = Line; + String ip = parseString(strLine,2); + String port = parseString(strLine,3); + int msgpos = getParamStartPos(strLine,4); + String message = strLine.substring(msgpos); + byte ipaddress[4]; + str2ip((char*)ip.c_str(), ipaddress); + IPAddress UDP_IP(ipaddress[0], ipaddress[1], ipaddress[2], ipaddress[3]); + portUDP.beginPacket(UDP_IP, port.toInt()); + portUDP.write(message.c_str(), message.length()); + portUDP.endPacket(); + } + + if (strcasecmp_P(Command, PSTR("SendToHTTP")) == 0) + { + success = true; + String strLine = Line; + String host = parseString(strLine,2); + String port = parseString(strLine,3); + int pathpos = getParamStartPos(strLine,4); + String path = strLine.substring(pathpos); + EthernetClient client; + if (client.connect(host.c_str(), port.toInt())) + { + client.print(String("GET ") + path + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "Connection: close\r\n\r\n"); + + unsigned long timer = millis() + 200; + while (!client.available() && millis() < timer) + delay(1); + + while (client.available()) { + String line = client.readStringUntil('\n'); + if (line.substring(0, 15) == "HTTP/1.1 200 OK") + addLog(LOG_LEVEL_DEBUG, line); + delay(1); + } + client.flush(); + client.stop(); + } + } + + // **************************************** + // configure settings commands + // **************************************** + + if (strcasecmp_P(Command, PSTR("Reboot")) == 0) + { + success = true; + Reboot(); + } + + if (strcasecmp_P(Command, PSTR("Restart")) == 0) + { + success = true; + Reboot(); + } + + if (strcasecmp_P(Command, PSTR("Reset")) == 0) + { + success = true; + ResetFactory(); + } + + if (strcasecmp_P(Command, PSTR("Save")) == 0) + { + success = true; + SaveSettings(); + } + + if (strcasecmp_P(Command, PSTR("Load")) == 0) + { + success = true; + LoadSettings(); + } + + if (strcasecmp_P(Command, PSTR("Debug")) == 0) + { + success = true; + Settings.SerialLogLevel = Par1; + } + + if (strcasecmp_P(Command, PSTR("IP")) == 0) + { + success = true; + if (GetArgv(Line, TmpStr1, 2)) + if (!str2ip(TmpStr1, Settings.IP)) + Serial.println("?"); + } + + if (strcasecmp_P(Command, PSTR("Settings")) == 0) + { + success = true; + char str[20]; + Serial.println(); + + Serial.println(F("System Info")); + IPAddress ip = Ethernet.localIP(); + sprintf_P(str, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]); + Serial.print(F(" IP Address : ")); Serial.println(str); + Serial.print(F(" Build : ")); Serial.println((int)BUILD); + Serial.print(F(" Unit : ")); Serial.println((int)Settings.Unit); + Serial.print(F(" Free mem : ")); Serial.println(FreeMem()); + } + + if (success) + status += F("\nOk"); + else + status += F("\nUnknown command!"); + SendStatus(source,status); +} + +void printDirectory(File dir, int numTabs) { + while(true) { + + File entry = dir.openNextFile(); + if (! entry) { + // no more files + break; + } + for (uint8_t i=0; iTaskIndex); + if (Settings.UseRules) + createRuleEvents(event->TaskIndex); + + if (Settings.GlobalSync && Settings.TaskDeviceGlobalSync[event->TaskIndex]) + SendUDPTaskData(0, event->TaskIndex, event->TaskIndex); + + if (!Settings.TaskDeviceSendData[event->TaskIndex]) + return false; + + if (Settings.MessageDelay != 0) + { + uint16_t dif = millis() - lastSend; + if (dif < Settings.MessageDelay) + { + uint16_t delayms = Settings.MessageDelay - dif; + char log[30]; + sprintf_P(log, PSTR("HTTP : Delay %u ms"), delayms); + addLog(LOG_LEVEL_DEBUG_MORE, log); + unsigned long timer = millis() + delayms; + while (millis() < timer) + backgroundtasks(); + } + } + + LoadTaskSettings(event->TaskIndex); // could have changed during background tasks. + + if (Settings.Protocol) + { + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + CPlugin_ptr[ProtocolIndex](CPLUGIN_PROTOCOL_SEND, event, dummyString); + } + PluginCall(PLUGIN_EVENT_OUT, event, dummyString); + lastSend = millis(); +} + +/*********************************************************************************************\ + * Send status info to request source +\*********************************************************************************************/ + +void SendStatus(byte source, String status) +{ + switch(source) + { + case VALUE_SOURCE_HTTP: + if (printToWeb) + printWebString += status; + break; + case VALUE_SOURCE_SERIAL: + Serial.println(status); + break; + } +} + + +#if FEATURE_MQTT +/*********************************************************************************************\ + * Handle incoming MQTT messages +\*********************************************************************************************/ +// handle MQTT messages +void callback(char* c_topic, byte* b_payload, unsigned int length) { + char log[256]; + char c_payload[256]; + strncpy(c_payload,(char*)b_payload,length); + c_payload[length] = 0; + statusLED(true); + + sprintf_P(log, PSTR("%s%s"), "MQTT : Topic: ", c_topic); + addLog(LOG_LEVEL_DEBUG, log); + sprintf_P(log, PSTR("%s%s"), "MQTT : Payload: ", c_payload); + addLog(LOG_LEVEL_DEBUG, log); + + struct EventStruct TempEvent; + TempEvent.String1 = c_topic; + TempEvent.String2 = c_payload; + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + CPlugin_ptr[ProtocolIndex](CPLUGIN_PROTOCOL_RECV, &TempEvent, dummyString); +} + + +/*********************************************************************************************\ + * Connect to MQTT message broker +\*********************************************************************************************/ +void MQTTConnect() +{ + IPAddress MQTTBrokerIP(Settings.Controller_IP); + MQTTclient.setServer(MQTTBrokerIP, Settings.ControllerPort); + MQTTclient.setCallback(callback); + + // MQTT needs a unique clientname to subscribe to broker + String clientid = "ESPClient"; + clientid += Settings.Unit; + String subscribeTo = ""; + + String LWTTopic = Settings.MQTTsubscribe; + LWTTopic.replace("/#", "/status"); + LWTTopic.replace("%sysname%", Settings.Name); + + for (byte x = 1; x < 3; x++) + { + String log = ""; + boolean MQTTresult = false; + + if ((SecuritySettings.ControllerUser[0] != 0) && (SecuritySettings.ControllerPassword[0] != 0)) + MQTTresult = MQTTclient.connect(clientid.c_str(), SecuritySettings.ControllerUser, SecuritySettings.ControllerPassword, LWTTopic.c_str(), 0, 0, "Connection Lost"); + else + MQTTresult = MQTTclient.connect(clientid.c_str(), LWTTopic.c_str(), 0, 0, "Connection Lost"); + + if (MQTTresult) + { + log = F("MQTT : Connected to broker"); + addLog(LOG_LEVEL_INFO, log); + subscribeTo = Settings.MQTTsubscribe; + subscribeTo.replace("%sysname%", Settings.Name); + MQTTclient.subscribe(subscribeTo.c_str()); + log = F("Subscribed to: "); + log += subscribeTo; + addLog(LOG_LEVEL_INFO, log); + break; // end loop if succesfull + } + else + { + log = F("MQTT : Failed to connected to broker"); + addLog(LOG_LEVEL_ERROR, log); + } + + delay(500); + } +} + + +/*********************************************************************************************\ + * Check connection MQTT message broker +\*********************************************************************************************/ +void MQTTCheck() +{ + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + if (Protocol[ProtocolIndex].usesMQTT) + if (!MQTTclient.connected()) + { + String log = F("MQTT : Connection lost"); + addLog(LOG_LEVEL_ERROR, log); + connectionFailures += 2; + MQTTclient.disconnect(); + delay(1000); + MQTTConnect(); + } + else if (connectionFailures) + connectionFailures--; +} + + +/*********************************************************************************************\ + * Send status info back to channel where request came from +\*********************************************************************************************/ +void MQTTStatus(String& status) +{ + String pubname = Settings.MQTTsubscribe; + pubname.replace("/#", "/status"); + pubname.replace("%sysname%", Settings.Name); + MQTTclient.publish(pubname.c_str(), status.c_str(),Settings.MQTTRetainFlag); +} +#endif + diff --git a/Hardware.ino b/Hardware.ino new file mode 100644 index 0000000..d4e2a84 --- /dev/null +++ b/Hardware.ino @@ -0,0 +1,55 @@ +/********************************************************************************************\ +* Initialize specific hardware setings (only global ones, others are set through devices) +\*********************************************************************************************/ + +void hardwareInit() +{ + + // set GPIO pins state if not set to default + for (byte x=0; x < 17; x++) + if (Settings.PinBootStates[x] != 0) + switch(Settings.PinBootStates[x]) + { + case 1: + pinMode(x,OUTPUT); + digitalWrite(x,LOW); + setPinState(1, x, PIN_MODE_OUTPUT, LOW); + break; + case 2: + pinMode(x,OUTPUT); + digitalWrite(x,HIGH); + setPinState(1, x, PIN_MODE_OUTPUT, HIGH); + break; + case 3: + pinMode(x,INPUT_PULLUP); + setPinState(1, x, PIN_MODE_INPUT, 0); + break; + } + + String log = F("INIT : I2C"); + addLog(LOG_LEVEL_INFO, log); + Wire.begin(); + + // I2C Watchdog boot status check + if (Settings.WDI2CAddress != 0) + { + delay(500); + Wire.beginTransmission(Settings.WDI2CAddress); + Wire.write(0x83); // command to set pointer + Wire.write(17); // pointer value to status byte + Wire.endTransmission(); + + Wire.requestFrom(Settings.WDI2CAddress, (uint8_t)1); + if (Wire.available()) + { + byte status = Wire.read(); + if (status & 0x1) + { + String log = F("INIT : Reset by WD!"); + addLog(LOG_LEVEL_ERROR, log); + //lastBootCause = BOOT_CAUSE_EXT_WD; + } + } + } +} + diff --git a/Misc.ino b/Misc.ino new file mode 100644 index 0000000..1f96926 --- /dev/null +++ b/Misc.ino @@ -0,0 +1,1995 @@ +#include +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); + } +} + diff --git a/Networking.ino b/Networking.ino new file mode 100644 index 0000000..a57d4ed --- /dev/null +++ b/Networking.ino @@ -0,0 +1,369 @@ +/*********************************************************************************************\ + Syslog client + \*********************************************************************************************/ +void syslog(const char *message) +{ + if (Settings.Syslog_IP[0] != 0) + { + IPAddress broadcastIP(Settings.Syslog_IP[0], Settings.Syslog_IP[1], Settings.Syslog_IP[2], Settings.Syslog_IP[3]); + portUDP.beginPacket(broadcastIP, 514); + char str[256]; + str[0] = 0; + snprintf_P(str, sizeof(str), PSTR("<7>ESP Unit: %u : %s"), Settings.Unit, message); + portUDP.write(str); + portUDP.endPacket(); + } +} + + +/*********************************************************************************************\ + Structs for UDP messaging + \*********************************************************************************************/ +struct infoStruct +{ + byte header = 255; + byte ID = 3; + byte sourcelUnit; + byte destUnit; + byte sourceTaskIndex; + byte destTaskIndex; + byte deviceNumber; + char taskName[26]; + char ValueNames[VARS_PER_TASK][26]; +}; + +struct dataStruct +{ + byte header = 255; + byte ID = 5; + byte sourcelUnit; + byte destUnit; + byte sourceTaskIndex; + byte destTaskIndex; + float Values[VARS_PER_TASK]; +}; + +/*********************************************************************************************\ + Check UDP messages + \*********************************************************************************************/ +void checkUDP() +{ + if (Settings.UDPPort == 0) + return; + + // UDP events + int packetSize = portUDP.parsePacket(); + if (packetSize) + { + statusLED(true); + + IPAddress remoteIP = portUDP.remoteIP(); + if (portUDP.remotePort() == 123) + { + // unexpected NTP reply, drop for now... + return; + } + byte packetBuffer[128]; + int len = portUDP.read(packetBuffer, 128); + if (packetBuffer[0] != 255) + { + packetBuffer[len] = 0; + addLog(LOG_LEVEL_DEBUG, (char*)packetBuffer); + struct EventStruct TempEvent; + String request = (char*)packetBuffer; + parseCommandString(&TempEvent, request); + TempEvent.Source = VALUE_SOURCE_SYSTEM; + if (!PluginCall(PLUGIN_WRITE, &TempEvent, request)) + ExecuteCommand(VALUE_SOURCE_SYSTEM, (char*)packetBuffer); + } + else + { + if (packetBuffer[1] > 1 && packetBuffer[1] < 6) + { + String log = (F("UDP : Sensor msg ")); + for (byte x = 1; x < 6; x++) + { + log += " "; + log += (int)packetBuffer[x]; + } + addLog(LOG_LEVEL_DEBUG_MORE, log); + } + + // binary data! + switch (packetBuffer[1]) + { + case 1: // sysinfo message + { + byte mac[6]; + byte ip[4]; + byte unit = packetBuffer[12]; + for (byte x = 0; x < 6; x++) + mac[x] = packetBuffer[x + 2]; + for (byte x = 0; x < 4; x++) + ip[x] = packetBuffer[x + 8]; + + if (unit < UNIT_MAX) + { + for (byte x = 0; x < 4; x++) + Nodes[unit].ip[x] = packetBuffer[x + 8]; + Nodes[unit].age = 0; // reset 'age counter' + if (len >20) // extended packet size + { + Nodes[unit].build = packetBuffer[13] + 256*packetBuffer[14]; + } + } + + char macaddress[20]; + sprintf_P(macaddress, PSTR("%02x:%02x:%02x:%02x:%02x:%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + char ipaddress[16]; + sprintf_P(ipaddress, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]); + char log[80]; + sprintf_P(log, PSTR("UDP : %s,%s,%u"), macaddress, ipaddress, unit); + addLog(LOG_LEVEL_DEBUG_MORE, log); + break; + } + + case 2: // sensor info pull request + { + SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 3: // sensor info + { + if (Settings.GlobalSync) + { + struct infoStruct infoReply; + memcpy((byte*)&infoReply, (byte*)&packetBuffer, sizeof(infoStruct)); + + // to prevent flash wear out (bugs in communication?) we can only write to an empty task + // so it will write only once and has to be cleared manually through webgui + if (Settings.TaskDeviceNumber[infoReply.destTaskIndex] == 0) + { + Settings.TaskDeviceNumber[infoReply.destTaskIndex] = infoReply.deviceNumber; + Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] = 1; // remote feed + Settings.TaskDeviceSendData[infoReply.destTaskIndex] = false; + strcpy(ExtraTaskSettings.TaskDeviceName, infoReply.taskName); + for (byte x = 0; x < VARS_PER_TASK; x++) + strcpy( ExtraTaskSettings.TaskDeviceValueNames[x], infoReply.ValueNames[x]); + SaveTaskSettings(infoReply.destTaskIndex); + SaveSettings(); + } + } + break; + } + + case 4: // sensor data pull request + { + SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 5: // sensor data + { + if (Settings.GlobalSync) + { + struct dataStruct dataReply; + memcpy((byte*)&dataReply, (byte*)&packetBuffer, sizeof(dataStruct)); + + // only if this task has a remote feed, update values + if (Settings.TaskDeviceDataFeed[dataReply.destTaskIndex] != 0) + { + for (byte x = 0; x < VARS_PER_TASK; x++) + { + UserVar[dataReply.destTaskIndex * VARS_PER_TASK + x] = dataReply.Values[x]; + } + if (Settings.UseRules) + createRuleEvents(dataReply.destTaskIndex); + } + } + break; + } + + default: + { + struct EventStruct TempEvent; + TempEvent.Data = (byte*)packetBuffer; + TempEvent.Par1 = remoteIP[3]; + PluginCall(PLUGIN_UDP_IN, &TempEvent, dummyString); + break; + } + } + } + } +} + + +/*********************************************************************************************\ + Send task info using UDP message + \*********************************************************************************************/ +void SendUDPTaskInfo(byte destUnit, byte sourceTaskIndex, byte destTaskIndex) +{ + struct infoStruct infoReply; + infoReply.sourcelUnit = Settings.Unit; + infoReply.sourceTaskIndex = sourceTaskIndex; + infoReply.destTaskIndex = destTaskIndex; + LoadTaskSettings(infoReply.sourceTaskIndex); + infoReply.deviceNumber = Settings.TaskDeviceNumber[infoReply.sourceTaskIndex]; + strcpy(infoReply.taskName, ExtraTaskSettings.TaskDeviceName); + for (byte x = 0; x < VARS_PER_TASK; x++) + strcpy(infoReply.ValueNames[x], ExtraTaskSettings.TaskDeviceValueNames[x]); + + byte firstUnit = 1; + byte lastUnit = UNIT_MAX - 1; + if (destUnit != 0) + { + firstUnit = destUnit; + lastUnit = destUnit; + } + for (byte x = firstUnit; x <= lastUnit; x++) + { + infoReply.destUnit = x; + sendUDP(x, (byte*)&infoReply, sizeof(infoStruct)); + delay(10); + } + delay(50); +} + + +/*********************************************************************************************\ + Send task data using UDP message + \*********************************************************************************************/ +void SendUDPTaskData(byte destUnit, byte sourceTaskIndex, byte destTaskIndex) +{ + struct dataStruct dataReply; + dataReply.sourcelUnit = Settings.Unit; + dataReply.sourceTaskIndex = sourceTaskIndex; + dataReply.destTaskIndex = destTaskIndex; + for (byte x = 0; x < VARS_PER_TASK; x++) + dataReply.Values[x] = UserVar[dataReply.sourceTaskIndex * VARS_PER_TASK + x]; + + byte firstUnit = 1; + byte lastUnit = UNIT_MAX - 1; + if (destUnit != 0) + { + firstUnit = destUnit; + lastUnit = destUnit; + } + for (byte x = firstUnit; x <= lastUnit; x++) + { + dataReply.destUnit = x; + sendUDP(x, (byte*) &dataReply, sizeof(dataStruct)); + delay(10); + } + delay(50); +} + + +/*********************************************************************************************\ + Send event using UDP message + \*********************************************************************************************/ +void SendUDPCommand(byte destUnit, char* data, byte dataLength) +{ + byte firstUnit = 1; + byte lastUnit = UNIT_MAX - 1; + if (destUnit != 0) + { + firstUnit = destUnit; + lastUnit = destUnit; + } + for (byte x = firstUnit; x <= lastUnit; x++) + { + sendUDP(x, (byte*)data, dataLength); + delay(10); + } + delay(50); +} + + +/*********************************************************************************************\ + Send UDP message + \*********************************************************************************************/ +void sendUDP(byte unit, byte* data, byte size) +{ + if (Nodes[unit].ip[0] == 0) + return; + String log = "UDP : Send UDP message to "; + log += unit; + addLog(LOG_LEVEL_DEBUG_MORE, log); + + statusLED(true); + + IPAddress remoteNodeIP(Nodes[unit].ip[0], Nodes[unit].ip[1], Nodes[unit].ip[2], Nodes[unit].ip[3]); + portUDP.beginPacket(remoteNodeIP, Settings.UDPPort); + portUDP.write(data, size); + portUDP.endPacket(); +} + + +/*********************************************************************************************\ + Refresh aging for remote units, drop if too old... + \*********************************************************************************************/ +void refreshNodeList() +{ + for (byte counter = 0; counter < UNIT_MAX; counter++) + { + if (Nodes[counter].ip[0] != 0) + { + Nodes[counter].age++; // increment age counter + if (Nodes[counter].age > 10) // if entry to old, clear this node ip from the list. + for (byte x = 0; x < 4; x++) + Nodes[counter].ip[x] = 0; + } + } +} + +void sendSysInfoUDP(byte repeats) +{ + char log[80]; + if (Settings.UDPPort == 0) + return; + + // 1 byte 'binary token 255' + // 1 byte id '1' + // 6 byte mac + // 4 byte ip + // 1 byte unit + // 2 byte build + // 25 char name + // 1 byte node type id + + // send my info to the world... + strcpy_P(log, PSTR("UDP : Send Sysinfo message")); + addLog(LOG_LEVEL_DEBUG_MORE, log); + for (byte counter = 0; counter < repeats; counter++) + { + byte data[80]; + data[0] = 255; + data[1] = 1; + for (byte x = 0; x < 6; x++) + data[x + 2] = mac[x]; + IPAddress ip = Ethernet.localIP(); + for (byte x = 0; x < 4; x++) + data[x + 8] = ip[x]; + data[12] = Settings.Unit; + data[13] = Settings.Build & 0xff; + data[14] = Settings.Build >> 8; + memcpy((byte*)data+15,Settings.Name,25); + data[40] = NODE_TYPE_ID; + statusLED(true); + + IPAddress broadcastIP(255, 255, 255, 255); + portUDP.beginPacket(broadcastIP, Settings.UDPPort); + portUDP.write(data, 80); + portUDP.endPacket(); + if (counter < (repeats - 1)) + delay(500); + } + + // store my own info also in the list... + if (Settings.Unit < UNIT_MAX) + { + IPAddress ip = Ethernet.localIP(); + for (byte x = 0; x < 4; x++) + Nodes[Settings.Unit].ip[x] = ip[x]; + Nodes[Settings.Unit].age = 0; + Nodes[Settings.Unit].build = Settings.Build; + } +} + diff --git a/Serial.ino b/Serial.ino new file mode 100644 index 0000000..75850f2 --- /dev/null +++ b/Serial.ino @@ -0,0 +1,43 @@ +/********************************************************************************************\ +* Get data from Serial Interface +\*********************************************************************************************/ +#define INPUT_BUFFER_SIZE 128 + +byte SerialInByte; +int SerialInByteCounter = 0; +char InputBuffer_Serial[INPUT_BUFFER_SIZE + 2]; + +void serial() +{ + while (Serial.available()) + { + SerialInByte = Serial.read(); + if (SerialInByte == 255) // binary data... + { + Serial.flush(); + return; + } + + if (isprint(SerialInByte)) + { + if (SerialInByteCounter < INPUT_BUFFER_SIZE) // add char to string if it still fits + InputBuffer_Serial[SerialInByteCounter++] = SerialInByte; + } + + if (SerialInByte == '\n') + { + InputBuffer_Serial[SerialInByteCounter] = 0; // serial data completed + Serial.write('>'); + Serial.println(InputBuffer_Serial); + String action = InputBuffer_Serial; + struct EventStruct TempEvent; + parseCommandString(&TempEvent, action); + TempEvent.Source = VALUE_SOURCE_SERIAL; + if (!PluginCall(PLUGIN_WRITE, &TempEvent, action)) + ExecuteCommand(VALUE_SOURCE_SERIAL, InputBuffer_Serial); + SerialInByteCounter = 0; + InputBuffer_Serial[0] = 0; // serial data processed, clear buffer + } + } +} + diff --git a/WebServer.ino b/WebServer.ino new file mode 100644 index 0000000..c953ff8 --- /dev/null +++ b/WebServer.ino @@ -0,0 +1,2085 @@ +String webdata = ""; + +void WebServerHandleClient() { + EthernetClient client = WebServer.available(); + if (client) { +#if socketdebug + ShowSocketStatus(); +#endif + // an http request ends with a blank line + boolean currentLineIsBlank = true; + String request = ""; + boolean getrequest = true; + webdata = ""; + webdata.reserve(500); + while (client.connected()) { + if (client.available()) { + char c = client.read(); + if (getrequest) + request += c; + //Serial.write(c); + // if you've gotten to the end of the line (received a newline + // character) and the line is blank, the http request has ended, + // so you can send a reply + if (c == '\n' && currentLineIsBlank) { + + // request is known here, in case of 'rules' we could handle this smarter (save index+6)? + while (client.available()) { // post data... + char c = client.read(); + webdata += c; + } + + int pos = request.indexOf("/"); + if (pos > 0) + request = request.substring(pos + 1); + pos = request.indexOf(" "); + if (pos > 0) + request = request.substring(0, pos); + pos = request.indexOf("?"); + if (pos >= 0) + { + String args = request.substring(pos + 1); + webdata += "&" + args; + request = request.substring(0, pos); + } + + webdata = URLDecode(webdata.c_str()); + if (request.startsWith(F(" HTTP")) or request.length() == 0) // root page + { + addHeader(true, client); + handle_root(client, webdata); + } + else if (request.equals(F("control"))) + { + handle_control(client, webdata); + } + else if (request.equals(F("config"))) + { + addHeader(true, client); + handle_config(client, webdata); + } + else if (request.equals(F("hardware"))) + { + addHeader(true, client); + handle_hardware(client, webdata); + } + else if (request.equals(F("devices"))) + { + addHeader(true, client); + handle_devices(client, webdata); + } + else if (request.equals(F("rules"))) + { + addHeader(true, client); + handle_rules(client, webdata); + } + else if (request.equals(F("tools"))) + { + addHeader(true, client); + handle_tools(client, webdata); + } + else if (request.equals(F("advanced"))) + { + addHeader(true, client); + handle_advanced(client, webdata); + } + else if (request.equals(F("SDfilelist"))) + { + addHeader(true, client); + handle_SDfilelist(client, webdata); + } + else if (request.equals(F("i2cscanner"))) + { + addHeader(true, client); + handle_i2cscanner(client, webdata); + } + else if (request.equals(F("log"))) + { + addHeader(true, client); + handle_log(client, webdata); + } + else if (request.equals(F("sysinfo"))) + { + addHeader(true, client); + handle_sysinfo(client, webdata); + } + else + handle_unknown(client, request); + break; + } + + if (c == '\n') { + // you're starting a new line + currentLineIsBlank = true; + getrequest = false; + } else if (c != '\r') { + // you've gotten a character on the current line + currentLineIsBlank = false; + } + } + } + // give the web browser time to receive the data + delay(1); + client.stop(); + webdata = ""; + } +} + +String WebServerarg(String arg) +{ + arg = "&" + arg; + String returnarg = ""; + int pos = webdata.indexOf(arg); + if (pos >= 0) + { + returnarg = webdata.substring(pos+1,pos+81); // max field content 80 ? + pos = returnarg.indexOf("&"); + if (pos > 0) + returnarg = returnarg.substring(0, pos); + pos = returnarg.indexOf("="); + if (pos > 0) + returnarg = returnarg.substring(pos + 1); + } + return returnarg; +} + + +//******************************************************************************** +// Add top menu +//******************************************************************************** +void addHeader(boolean showMenu, EthernetClient client) +{ + client.println(F("HTTP/1.1 200 OK")); + client.println(F("Content-Type: text/html")); + client.println(F("Connection: close")); // the connection will be closed after completion of the response + client.println(); + + String str = ""; + boolean cssfile = false; + + str += F(""); + str += F(""); + str += Settings.Name; + str += F(""); + + if (!cssfile) + { + str += F(""); + } + else + str += F(""); + + str += F(""); + + str += F("

Welcome to Arduino Easy: "); + str += Settings.Name; + + str += F("

"); + + if (showMenu) + { + str += F("
Main"); + str += F("Config"); + str += F("Hardware"); + str += F("Devices"); + if (Settings.UseRules) + str += F("Rules"); + str += F("Tools

"); + } + client.print(str); +} + + +//******************************************************************************** +// Add footer to web page +//******************************************************************************** +void addFooter(String& str) +{ + str += F("
Powered by www.letscontrolit.com
"); +} + +//******************************************************************************** +// Web Interface root page +//******************************************************************************** +void handle_root(EthernetClient client, String &post) { + + //if (!isLoggedIn()) return; + + String sCommand = WebServerarg(F("cmd")); + + if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) != 0) + { + String reply = ""; + reply.reserve(1000); + + printToWeb = true; + printWebString = ""; + if (sCommand.length() > 0) + ExecuteCommand(VALUE_SOURCE_HTTP, sCommand.c_str()); + + IPAddress ip = Ethernet.localIP(); + IPAddress gw = Ethernet.gatewayIP(); + + reply += printWebString; + reply += F("
"); + reply += F("
System InfoValueSystem InfoValue"); + + reply += F("
Unit:"); + reply += Settings.Unit; + + reply += F("Build:"); + reply += BUILD; + reply += F(" "); + reply += F(BUILD_NOTES); + + if (Settings.UseNTP) + { + reply += F("
System Time:"); + reply += hour(); + reply += ":"; + if (minute() < 10) + reply += "0"; + reply += minute(); + } + + reply += F("Uptime:"); + char strUpTime[40]; + int minutes = wdcounter / 2; + int days = minutes / 1440; + minutes = minutes % 1440; + int hrs = minutes / 60; + minutes = minutes % 60; + sprintf_P(strUpTime, PSTR("%d days %d hours %d minutes"), days, hrs, minutes); + reply += strUpTime; + + reply += F("
Load:"); + if (wdcounter > 0) + { + reply += 100 - (100 * loopCounterLast / loopCounterMax); + reply += F("% (LC="); + reply += int(loopCounterLast / 30); + reply += F(")"); + } + + reply += F("Free Mem:"); + reply += FreeMem(); + + char str[20]; + sprintf_P(str, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]); + reply += F("
IP:"); + reply += str; + + sprintf_P(str, PSTR("%u.%u.%u.%u"), gw[0], gw[1], gw[2], gw[3]); + reply += F("GW:"); + reply += str; + + client.print(reply); + reply = ""; + + reply += F("
Node List:BuildIPAge
"); + for (byte x = 0; x < UNIT_MAX; x++) + { + if (Nodes[x].ip[0] != 0) + { + char url[80]; + sprintf_P(url, PSTR("%u.%u.%u.%u"), Nodes[x].ip[0], Nodes[x].ip[1], Nodes[x].ip[2], Nodes[x].ip[3], Nodes[x].ip[0], Nodes[x].ip[1], Nodes[x].ip[2], Nodes[x].ip[3]); + reply += F("
Unit "); + reply += x; + reply += F(""); + if (Nodes[x].build) + reply += Nodes[x].build; + reply += F(""); + reply += url; + reply += F(""); + reply += Nodes[x].age; + client.print(reply); + reply = ""; + } + } + + reply += F("
"); + addFooter(reply); + client.print(reply); + printWebString = ""; + printToWeb = false; + } + else + { + // have to disconnect or reboot from within the main loop + // because the webconnection is still active at this point + // disconnect here could result into a crash/reboot... + if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) + { + String log = F(" : Rebooting..."); + addLog(LOG_LEVEL_INFO, log); + cmd_within_mainloop = CMD_REBOOT; + } + client.print("ok"); + } +} + + +//******************************************************************************** +// Web Interface config page +//******************************************************************************** +void handle_config(EthernetClient client, String &post) { + //if (!isLoggedIn()) return; + + update_config(); + + webdata = ""; + String reply = ""; + reply.reserve(500); + + reply += F("
"); + reply += F("
Main Settings
Name:
Admin Password:
Unit nr:
Protocol:"); + byte choice = Settings.Protocol; + reply += F(""); + reply += F("?"); + + client.print(reply); + reply = ""; + + char str[20]; + + if (Settings.Protocol) + { + byte choice = Settings.UseDNS; + String options[2]; + options[0] = F("Use IP address"); + options[1] = F("Use Hostname"); + int optionValues[2]; + optionValues[0] = 0; + optionValues[1] = 1; + reply += F("
Locate Controller:"); + + client.print(reply); + reply = ""; + + if (Settings.UseDNS) + { + reply += F("
Controller Hostname:
Controller Port:
Controller User:
Controller Password:"); + + CPlugin_ptr[ProtocolIndex](CPLUGIN_WEBFORM_LOAD, 0, reply); + + } + + client.print(reply); + reply = ""; + + reply += F("
Sensor Delay:"); + + reply += F("
Optional Settings"); + + reply += F("
ESP IP:
ESP GW:
ESP Subnet:
ESP DNS:
"); + reply += F("
"); + addFooter(reply); + // debugRAM(3,reply.length()); + client.print(reply); +} + + +void update_config() +{ + char tmpString[64]; + + String arg = ""; + arg = WebServerarg(F("name")); + if (arg[0] != 0) + { + strncpy(Settings.Name, arg.c_str(), sizeof(Settings.Name)); + arg = WebServerarg(F("password")); + strncpy(SecuritySettings.Password, arg.c_str(), sizeof(SecuritySettings.Password)); + + arg = WebServerarg(F("protocol")); + if (Settings.Protocol != arg.toInt()) + { + Settings.Protocol = arg.toInt(); + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + Settings.ControllerPort = Protocol[ProtocolIndex].defaultPort; + if (Protocol[ProtocolIndex].usesTemplate) + CPlugin_ptr[ProtocolIndex](CPLUGIN_PROTOCOL_TEMPLATE, 0, dummyString); + } + else + { + if (Settings.Protocol != 0) + { + byte ProtocolIndex = getProtocolIndex(Settings.Protocol); + CPlugin_ptr[ProtocolIndex](CPLUGIN_WEBFORM_SAVE, 0, dummyString); + arg = WebServerarg(F("usedns")); + Settings.UseDNS = arg.toInt(); + if (Settings.UseDNS) + { + arg = WebServerarg(F("controllerhostname")); + strncpy(Settings.ControllerHostName, arg.c_str(), sizeof(Settings.ControllerHostName)); + getIPfromHostName(); + } + else + { + arg = WebServerarg(F("controllerip")); + if (arg.length() != 0) + { + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.Controller_IP); + } + } + + arg = WebServerarg(F("controllerport")); + Settings.ControllerPort = arg.toInt(); + arg = WebServerarg(F("controlleruser")); + strncpy(SecuritySettings.ControllerUser, arg.c_str(), sizeof(SecuritySettings.ControllerUser)); + arg = WebServerarg(F("controllerpassword")); + strncpy(SecuritySettings.ControllerPassword, arg.c_str(), sizeof(SecuritySettings.ControllerPassword)); + } + } + + arg = WebServerarg(F("delay")); + Settings.Delay = arg.toInt(); + + arg = WebServerarg(F("espip")); + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.IP); + arg = WebServerarg(F("espgateway")); + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.Gateway); + arg = WebServerarg(F("espsubnet")); + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.Subnet); + arg = WebServerarg(F("espdns")); + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.DNS); + arg = WebServerarg(F("unit")); + Settings.Unit = arg.toInt(); + SaveSettings(); + } +} + +//******************************************************************************** +// Web Interface hardware page +//******************************************************************************** +void handle_hardware(EthernetClient client, String &post) { + //if (!isLoggedIn()) return; + + String edit = WebServerarg(F("edit")); + + if (edit.length() != 0) + { + Settings.PinBootStates[2] = WebServerarg(F("p2")).toInt(); + Settings.PinBootStates[3] = WebServerarg(F("p3")).toInt(); + Settings.PinBootStates[4] = WebServerarg(F("p4")).toInt(); + Settings.PinBootStates[5] = WebServerarg(F("p5")).toInt(); + Settings.PinBootStates[6] = WebServerarg(F("p6")).toInt(); + Settings.PinBootStates[7] = WebServerarg(F("p7")).toInt(); + Settings.PinBootStates[8] = WebServerarg(F("p8")).toInt(); + Settings.PinBootStates[9] = WebServerarg(F("p9")).toInt(); + Settings.PinBootStates[11] = WebServerarg(F("p11")).toInt(); + Settings.PinBootStates[12] = WebServerarg(F("p12")).toInt(); + SaveSettings(); + } + + String reply = ""; + reply.reserve(1000); + + reply += F("
Hardware Settings
"); + reply += F("
GPIO boot states:"); + + reply += F("
D2:"); + addPinStateSelect(reply, "p2", Settings.PinBootStates[2]); + reply += F("
D3:"); + addPinStateSelect(reply, "p3", Settings.PinBootStates[3]); + reply += F("
D4:"); + addPinStateSelect(reply, "p4", Settings.PinBootStates[4]); + reply += F("
D5:"); + + client.print(reply); + reply = ""; + + addPinStateSelect(reply, "p5", Settings.PinBootStates[5]); + reply += F("
D6:"); + addPinStateSelect(reply, "p6", Settings.PinBootStates[6]); + reply += F("
D7:"); + addPinStateSelect(reply, "p7", Settings.PinBootStates[7]); + + client.print(reply); + reply = ""; + + reply += F("
D8:"); + addPinStateSelect(reply, "p8", Settings.PinBootStates[8]); + reply += F("
D9:"); + addPinStateSelect(reply, "p9", Settings.PinBootStates[9]); + reply += F("
D11:"); + addPinStateSelect(reply, "p11", Settings.PinBootStates[11]); + + client.print(reply); + reply = ""; + + reply += F("
D12:"); + addPinStateSelect(reply, "p12", Settings.PinBootStates[12]); + + reply += F(""); + reply += F("
"); + + reply += F("
"); + client.print(reply); +} + + +//******************************************************************************** +// Add a GPIO pin select dropdown list +//******************************************************************************** +void addPinStateSelect(String& str, String name, int choice) +{ + String options[4]; + options[0] = F("Default"); + options[1] = F("Output Low"); + options[2] = F("Output High"); + options[3] = F("Input"); + int optionValues[4]; + optionValues[0] = 0; + optionValues[1] = 1; + optionValues[2] = 2; + optionValues[3] = 3; + + str += F(""); +} + + +//******************************************************************************** +// Web Interface device page +//******************************************************************************** +void handle_devices(EthernetClient client, String &post) { + //if (!isLoggedIn()) return; + + update_device(); + + // debugRAM(2,0); + + struct EventStruct TempEvent; + + String taskindex = WebServerarg(F("index")); + byte index = taskindex.toInt(); + byte page = WebServerarg(F("page")).toInt(); + if (page == 0) + page = 1; + byte setpage = WebServerarg(F("setpage")).toInt(); + if (setpage > 0) + { + if (setpage <= (TASKS_MAX / 4)) + page = setpage; + else + page = TASKS_MAX / 4; + } + + byte DeviceIndex = 0; + + webdata = ""; + String reply = ""; + reply.reserve(1000); + + // show all tasks as table + if (index == 0) + { + reply += F("
"); + reply += F(" 1) + reply += page - 1; + else + reply += page; + reply += F("\"><"); + reply += F(">"); + + reply += F("TaskDeviceNamePortIDX/VariableGPIOValues"); + + String deviceName; + + for (byte x = (page - 1) * 4; x < ((page) * 4); x++) + { + reply += F("
"); + reply += F("Edit"); + reply += F(""); + reply += x + 1; + reply += F(""); + + if (Settings.TaskDeviceNumber[x] != 0) + { + LoadTaskSettings(x); + DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[x]); + TempEvent.TaskIndex = x; + + deviceName = ""; + if (Settings.TaskDeviceNumber[x] != 0) + Plugin_ptr[DeviceIndex](PLUGIN_GET_DEVICENAME, &TempEvent, deviceName); + + reply += deviceName; + reply += F(""); + reply += ExtraTaskSettings.TaskDeviceName; + reply += F(""); + + byte customConfig = false; + customConfig = PluginCall(PLUGIN_WEBFORM_SHOW_CONFIG, &TempEvent, reply); + if (!customConfig) + if (Device[DeviceIndex].Ports != 0) + reply += Settings.TaskDevicePort[x]; + + reply += F(""); + + if (Settings.TaskDeviceID[x] != 0) + reply += Settings.TaskDeviceID[x]; + + reply += F(""); + + if (Settings.TaskDeviceDataFeed[x] == 0) + { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) + { + reply += F("GPIO-"); + //reply += Settings.Pin_i2c_sda; // todo + reply += F("
GPIO-"); + //reply += Settings.Pin_i2c_scl; // todo + } + if (Device[DeviceIndex].Type == DEVICE_TYPE_ANALOG) + reply += F("ADC (TOUT)"); + + if (Settings.TaskDevicePin1[x] != -1) + { + reply += F("GPIO-"); + reply += Settings.TaskDevicePin1[x]; + } + + if (Settings.TaskDevicePin2[x] != -1) + { + reply += F("
GPIO-"); + reply += Settings.TaskDevicePin2[x]; + } + + if (Settings.TaskDevicePin3[x] != -1) + { + reply += F("
GPIO-"); + reply += Settings.TaskDevicePin3[x]; + } + } + + reply += F("
"); + byte customValues = false; + customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, reply); + if (!customValues) + { + if (Device[DeviceIndex].VType == SENSOR_TYPE_LONG) + { + reply += F("
"); + reply += ExtraTaskSettings.TaskDeviceValueNames[0]; + reply += F(":
"); + reply += (unsigned long)UserVar[x * VARS_PER_TASK] + ((unsigned long)UserVar[x * VARS_PER_TASK + 1] << 16); + reply += F("
"); + } + else + { + for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) + { + if ((Settings.TaskDeviceNumber[x] != 0) and (varNr < Device[DeviceIndex].ValueCount)) + { + if (varNr > 0) + reply += F("
"); + reply += F("
"); + reply += ExtraTaskSettings.TaskDeviceValueNames[varNr]; + reply += F(":
"); + reply += String(UserVar[x * VARS_PER_TASK + varNr], ExtraTaskSettings.TaskDeviceValueDecimals[varNr]); + reply += "
"; + } + } + } + } + } + else + reply += F("
"); + + client.print(reply); + reply = ""; + + } // next + reply += F("
"); + } + // Show edit form if a specific entry is chosen with the edit button + else + { + LoadTaskSettings(index - 1); + DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[index - 1]); + TempEvent.TaskIndex = index - 1; + + reply += F("

Task SettingsValue"); + + reply += F("
Device:"); + addDeviceSelect(reply, "taskdevicenumber", Settings.TaskDeviceNumber[index - 1]); + + if (Settings.TaskDeviceNumber[index - 1] != 0 ) + { + reply += F("?"); + + reply += F("
Name:"); + + if (Device[DeviceIndex].TimerOption) + { + reply += F("
Delay:"); + if (Device[DeviceIndex].TimerOptional) + reply += F(" (Optional for this device)"); + } + + if (!Device[DeviceIndex].Custom) + { + reply += F("
IDX / Var:"); + } + + if (!Device[DeviceIndex].Custom && Settings.TaskDeviceDataFeed[index - 1] == 0) + { + if (Device[DeviceIndex].Ports != 0) + { + reply += F("
Port:"); + } + + client.print(reply); + reply = ""; + + if (Device[DeviceIndex].Type == DEVICE_TYPE_SINGLE || Device[DeviceIndex].Type == DEVICE_TYPE_DUAL) + { + reply += F("
1st GPIO:"); + addPinSelect(false, reply, "taskdevicepin1", Settings.TaskDevicePin1[index - 1]); + } + if (Device[DeviceIndex].Type == DEVICE_TYPE_DUAL) + { + reply += F("
2nd GPIO:"); + addPinSelect(false, reply, "taskdevicepin2", Settings.TaskDevicePin2[index - 1]); + } + + if (Device[DeviceIndex].PullUpOption) + { + reply += F("
Pull UP:"); + if (Settings.TaskDevicePin1PullUp[index - 1]) + reply += F(""); + else + reply += F(""); + } + + if (Device[DeviceIndex].InverseLogicOption) + { + reply += F("
Inversed:"); + if (Settings.TaskDevicePin1Inversed[index - 1]) + reply += F(""); + else + reply += F(""); + } + } + + client.print(reply); + reply = ""; + + if (Settings.TaskDeviceDataFeed[index - 1] == 0) // only show additional config for local connected sensors + PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, reply); + + if (Device[DeviceIndex].SendDataOption) + { + reply += F("
Send Data:"); + if (Settings.TaskDeviceSendData[index - 1]) + reply += F(""); + else + reply += F(""); + } + + if (Settings.GlobalSync && Device[DeviceIndex].GlobalSyncOption && Settings.TaskDeviceDataFeed[index - 1] == 0 && Settings.UDPPort != 0) + { + reply += F("
Global Sync:"); + if (Settings.TaskDeviceGlobalSync[index - 1]) + reply += F(""); + else + reply += F(""); + } + + client.print(reply); + reply = ""; + + if (!Device[DeviceIndex].Custom) + { + reply += F("
Optional SettingsValue"); + + if (Device[DeviceIndex].FormulaOption) + { + for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) + { + reply += F("
Formula "); + reply += ExtraTaskSettings.TaskDeviceValueNames[varNr]; + reply += F(":"); + + reply += F(" Decimals: "); + + if (varNr == 0) + reply += F("?"); + + client.print(reply); + reply = ""; + + } + } + else + { + if (Device[DeviceIndex].DecimalsOnly) + for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) + { + reply += F("
Decimals "); + reply += ExtraTaskSettings.TaskDeviceValueNames[varNr]; + reply += F(":"); + client.print(reply); + reply = ""; + } + } + + for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) + { + reply += F("
Value Name "); + reply += varNr + 1; + reply += F(":"); + client.print(reply); + reply = ""; + } + } + + } + reply += F("
Close"); + reply += F(""); + reply += F(""); + reply += F(""); + reply += F("
"); + } + client.print(reply); +} + +void update_device() +{ + char tmpString[41]; + struct EventStruct TempEvent; + + String arg = ""; + String taskindex = WebServerarg(F("index")); + String taskdevicenumber = WebServerarg(F("taskdevicenumber")); + String taskdeviceformula[VARS_PER_TASK]; + String taskdevicevaluename[VARS_PER_TASK]; + String taskdevicevaluedecimals[VARS_PER_TASK]; + + for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) + { + char argc[25]; + String arg = F("taskdeviceformula"); + arg += varNr + 1; + arg.toCharArray(argc, 25); + taskdeviceformula[varNr] = WebServerarg(argc); + + arg = F("taskdevicevaluename"); + arg += varNr + 1; + arg.toCharArray(argc, 25); + taskdevicevaluename[varNr] = WebServerarg(argc); + + arg = F("taskdevicevaluedecimals"); + arg += varNr + 1; + arg.toCharArray(argc, 25); + taskdevicevaluedecimals[varNr] = WebServerarg(argc); + } + + String edit = WebServerarg(F("edit")); + + byte index = taskindex.toInt(); + byte DeviceIndex = 0; + + if (edit.toInt() != 0) + { + if (Settings.TaskDeviceNumber[index - 1] != taskdevicenumber.toInt()) // change of device, clear all other values + { + taskClear(index - 1, false); // clear settings, but do not save + Settings.TaskDeviceNumber[index - 1] = taskdevicenumber.toInt(); + if (taskdevicenumber.toInt() != 0) // preload valuenames + { + TempEvent.TaskIndex = index - 1; + if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0) // if field set empty, reload defaults + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummyString); + } + } + else if (taskdevicenumber.toInt() != 0) + { + Settings.TaskDeviceNumber[index - 1] = taskdevicenumber.toInt(); + DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[index - 1]); + + arg = WebServerarg(F("taskdevicetimer")); + if (arg.toInt() > 0) + Settings.TaskDeviceTimer[index - 1] = arg.toInt(); + else + { + if (!Device[DeviceIndex].TimerOptional) // Set default delay, unless it's optional... + Settings.TaskDeviceTimer[index - 1] = Settings.Delay; + else + Settings.TaskDeviceTimer[index - 1] = 0; + } + + arg = WebServerarg(F("taskdevicename")); + arg.toCharArray(tmpString, 41); + strcpy(ExtraTaskSettings.TaskDeviceName, tmpString); + + arg = WebServerarg(F("taskdeviceport")); + Settings.TaskDevicePort[index - 1] = arg.toInt(); + + arg = WebServerarg(F("taskdeviceid")); + if (Settings.TaskDeviceNumber[index - 1] != 0) + Settings.TaskDeviceID[index - 1] = arg.toInt(); + else + Settings.TaskDeviceID[index - 1] = 0; + + if (Device[DeviceIndex].Type == DEVICE_TYPE_SINGLE) + { + arg = WebServerarg(F("taskdevicepin1")); + Settings.TaskDevicePin1[index - 1] = arg.toInt(); + } + if (Device[DeviceIndex].Type == DEVICE_TYPE_DUAL) + { + arg = WebServerarg(F("taskdevicepin1")); + Settings.TaskDevicePin1[index - 1] = arg.toInt(); + arg = WebServerarg(F("taskdevicepin2")); + Settings.TaskDevicePin2[index - 1] = arg.toInt(); + } + + arg = WebServerarg(F("taskdevicepin3")); + if (arg.length() != 0) + Settings.TaskDevicePin3[index - 1] = arg.toInt(); + + if (Device[DeviceIndex].PullUpOption) + { + arg = WebServerarg(F("taskdevicepin1pullup")); + Settings.TaskDevicePin1PullUp[index - 1] = (arg == "on"); + } + + if (Device[DeviceIndex].InverseLogicOption) + { + arg = WebServerarg(F("taskdevicepin1inversed")); + Settings.TaskDevicePin1Inversed[index - 1] = (arg == "on"); + } + + if (Device[DeviceIndex].SendDataOption) + { + arg = WebServerarg(F("taskdevicesenddata")); + Settings.TaskDeviceSendData[index - 1] = (arg == "on"); + } + + if (Settings.GlobalSync) + { + if (Device[DeviceIndex].GlobalSyncOption) + { + arg = WebServerarg(F("taskdeviceglobalsync")); + Settings.TaskDeviceGlobalSync[index - 1] = (arg == "on"); + } + + // Send task info if set global + if (Settings.TaskDeviceGlobalSync[index - 1]) + { + SendUDPTaskInfo(0, index - 1, index - 1); + } + } + + for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) + { + taskdeviceformula[varNr].toCharArray(tmpString, 41); + strcpy(ExtraTaskSettings.TaskDeviceFormula[varNr], tmpString); + ExtraTaskSettings.TaskDeviceValueDecimals[varNr] = taskdevicevaluedecimals[varNr].toInt(); + } + + // task value names handling. + for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) + { + taskdevicevaluename[varNr].toCharArray(tmpString, 41); + strcpy(ExtraTaskSettings.TaskDeviceValueNames[varNr], tmpString); + } + + TempEvent.TaskIndex = index - 1; + if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0) // if field set empty, reload defaults + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummyString); + + PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummyString); + } + SaveTaskSettings(index - 1); + SaveSettings(); + if (taskdevicenumber.toInt() != 0) + PluginCall(PLUGIN_INIT, &TempEvent, dummyString); + } +} + + +byte sortedIndex[DEVICES_MAX + 1]; +//******************************************************************************** +// Add a device select dropdown list +//******************************************************************************** +void addDeviceSelect(String& str, String name, int choice) +{ + // first get the list in alphabetic order + for (byte x = 0; x <= deviceCount; x++) + sortedIndex[x] = x; + sortDeviceArray(); + + String deviceName; + + str += F(""); +} + + +//******************************************************************************** +// Device Sort routine, switch array entries +//******************************************************************************** +void switchArray(byte value) +{ + byte temp; + temp = sortedIndex[value - 1]; + sortedIndex[value - 1] = sortedIndex[value]; + sortedIndex[value] = temp; +} + + +//******************************************************************************** +// Device Sort routine, compare two array entries +//******************************************************************************** +byte arrayLessThan(char *ptr_1, char *ptr_2) +{ + char check1; + char check2; + + int i = 0; + while (i < strlen(ptr_1)) // For each character in string 1, starting with the first: + { + check1 = (char)ptr_1[i]; // get the same char from string 1 and string 2 + + if (strlen(ptr_2) < i) // If string 2 is shorter, then switch them + { + return 1; + } + else + { + check2 = (char)ptr_2[i]; + + if (check2 > check1) + { + return 1; // String 2 is greater; so switch them + } + if (check2 < check1) + { + return 0; // String 2 is LESS; so DONT switch them + } + // OTHERWISE they're equal so far; check the next char !! + i++; + } + } + + return 0; +} + + +//******************************************************************************** +// Device Sort routine, actual sorting +//******************************************************************************** +void sortDeviceArray() +{ + String deviceName; + char deviceName1[41]; + char deviceName2[41]; + int innerLoop ; + int mainLoop ; + + for ( mainLoop = 1; mainLoop <= deviceCount; mainLoop++) + { + innerLoop = mainLoop; + while (innerLoop >= 1) + { + Plugin_ptr[sortedIndex[innerLoop]](PLUGIN_GET_DEVICENAME, 0, deviceName); + deviceName.toCharArray(deviceName1, 26); + Plugin_ptr[sortedIndex[innerLoop - 1]](PLUGIN_GET_DEVICENAME, 0, deviceName); + deviceName.toCharArray(deviceName2, 26); + + if (arrayLessThan(deviceName1, deviceName2) == 1) + { + switchArray(innerLoop); + } + innerLoop--; + } + } +} + + +//******************************************************************************** +// Add a GPIO pin select dropdown list +//******************************************************************************** +void addPinSelect(boolean forI2C, String& str, String name, int choice) +{ + String options[11]; + options[0] = F(" "); + options[1] = F("D2"); + options[2] = F("D3"); + options[3] = F("D4"); + options[4] = F("D5"); + options[5] = F("D6"); + options[6] = F("D7"); + options[7] = F("D8"); + options[8] = F("D9"); + options[9] = F("D11"); + options[10] = F("D12"); + int optionValues[11]; + optionValues[0] = -1; + optionValues[1] = 2; + optionValues[2] = 3; + optionValues[3] = 4; + optionValues[4] = 5; + optionValues[5] = 6; + optionValues[6] = 7; + optionValues[7] = 8; + optionValues[8] = 9; + optionValues[9] = 11; + optionValues[10] = 12; + str += F(""); +} + + +//******************************************************************************** +// Add a task select dropdown list +//******************************************************************************** +void addTaskSelect(String& str, String name, int choice) +{ + struct EventStruct TempEvent; + String deviceName; + + str += F(""); + + reply += F("Current size: "); + reply += filesize; + reply += F(" characters (Max "); + reply += RULES_MAX_SIZE; + reply += F(")"); + + reply += F(""); + reply += F(""); + addFooter(reply); + client.print(reply); +} + + +//******************************************************************************** +// Web Interface debug page +//******************************************************************************** +void handle_tools(EthernetClient client, String &post) { + //if (!isLoggedIn()) return; + + String webrequest = WebServerarg(F("cmd")); + + String reply = ""; + + reply += F("
"); + reply += F("
Tools"); + reply += F("
SystemReboot"); + reply += F("Log"); + reply += F("Info"); + reply += F("Advanced

"); + reply += F("
InterfacesI2C Scan

"); + reply += F("
FilesystemSD Card

"); + + reply += F("
Command"); + reply += F("
"); + + printToWeb = true; + printWebString = ""; + + if (webrequest.length() > 0) + { + struct EventStruct TempEvent; + parseCommandString(&TempEvent, webrequest); + TempEvent.Source = VALUE_SOURCE_HTTP; + if (!PluginCall(PLUGIN_WRITE, &TempEvent, webrequest)) + ExecuteCommand(VALUE_SOURCE_HTTP, webrequest.c_str()); + } + + if (printWebString.length() > 0) + { + reply += F("
Command Output"); + } + reply += F("
"); + addFooter(reply); + client.print(reply); + printWebString = ""; + printToWeb = false; +} + + +//******************************************************************************** +// Web Interface config page +//******************************************************************************** +void handle_advanced(EthernetClient client, String &post) { + //if (!isLoggedIn()) return; + + update_advanced(); + + String reply = ""; + + char str[20]; + + reply += F("
"); + reply += F("
Advanced SettingsValue"); + + reply += F("
Subscribe Template:
Publish Template:
Message Delay (ms):
Fixed IP Octet:"); + + client.print(reply); + reply = ""; + + reply += F("
Use NTP:"); + if (Settings.UseNTP) + reply += F(""); + else + reply += F(""); + + reply += F("
NTP Hostname:
Timezone Offset: (Minutes)"); + + reply += F("
DST:"); + if (Settings.DST) + reply += F(""); + else + reply += F(""); + + client.print(reply); + reply = ""; + + reply += F("
Syslog IP:
Syslog Level:
UDP port:"); + + reply += F("
Enable Serial port:"); + if (Settings.UseSerial) + reply += F(""); + else + reply += F(""); + + reply += F("
Serial log Level:
SD Card log Level:
Baud Rate:"); + + reply += F("
Rules:"); + if (Settings.UseRules) + reply += F(""); + else + reply += F(""); + + reply += F("
Global Sync:"); + if (Settings.GlobalSync) + reply += F(""); + else + reply += F(""); + + reply += F("
"); + reply += F(""); + reply += F("
"); + addFooter(reply); + client.print(reply); +} + +void update_advanced() +{ + char tmpString[81]; + String arg = ""; + String edit = WebServerarg(F("edit")); + + if (edit.length() != 0) + { + arg = WebServerarg(F("mqttsubscribe")); + arg.toCharArray(tmpString, 81); + arg = WebServerarg(F("mqttpublish")); + strcpy(Settings.MQTTsubscribe, tmpString); + arg.toCharArray(tmpString, 81); + strcpy(Settings.MQTTpublish, tmpString); + arg = WebServerarg(F("messagedelay")); + Settings.MessageDelay = arg.toInt(); + arg = WebServerarg(F("ip")); + Settings.IP_Octet = arg.toInt(); + arg = WebServerarg(F("ntphost")); + arg.toCharArray(tmpString, 64); + strcpy(Settings.NTPHost, tmpString); + arg = WebServerarg(F("timezone")); + Settings.TimeZone = arg.toInt(); + arg = WebServerarg(F("syslogip")); + arg.toCharArray(tmpString, 26); + str2ip(tmpString, Settings.Syslog_IP); + arg = WebServerarg(F("udpport")); + Settings.UDPPort = arg.toInt(); + arg = WebServerarg(F("sysloglevel")); + Settings.SyslogLevel = arg.toInt(); + arg = WebServerarg(F("useserial")); + Settings.UseSerial = (arg == "on"); + arg = WebServerarg(F("serialloglevel")); + Settings.SerialLogLevel = arg.toInt(); + arg = WebServerarg(F("sdloglevel")); + Settings.SDLogLevel = arg.toInt(); + arg = WebServerarg(F("baudrate")); + Settings.BaudRate = arg.toInt(); + arg = WebServerarg(F("usentp")); + Settings.UseNTP = (arg == "on"); + arg = WebServerarg(F("dst")); + Settings.DST = (arg == "on"); + arg = WebServerarg(F("wdi2caddress")); + Settings.WDI2CAddress = arg.toInt(); + arg = WebServerarg(F("userules")); + Settings.UseRules = (arg == "on"); + arg = WebServerarg(F("globalsync")); + Settings.GlobalSync = (arg == "on"); + arg = WebServerarg(F("cft")); + Settings.ConnectionFailuresThreshold = arg.toInt(); + SaveSettings(); + } +} + + +//******************************************************************************** +// Web Interface control page (no password!) +//******************************************************************************** +void handle_control(EthernetClient client, String &post) { + + String webrequest = WebServerarg(F("cmd")); + + // in case of event, store to buffer and return... + String command = parseString(webrequest, 1); + if (command == F("event")) + { + eventBuffer = webrequest.substring(6); + client.print("OK"); + } + + struct EventStruct TempEvent; + parseCommandString(&TempEvent, webrequest); + TempEvent.Source = VALUE_SOURCE_HTTP; + + printToWeb = true; + printWebString = ""; + String reply = ""; + + if (!PluginCall(PLUGIN_WRITE, &TempEvent, webrequest)) + reply += F("Unknown or restricted command!"); + + reply += printWebString; + + client.println(F("HTTP/1.1 200 OK")); + client.print(F("Content-Type:")); + if (printToWebJSON) + client.print("Content-Type: application/json"); + else + client.print("Content-Type: text/html"); + client.println(F("Connection: close")); + client.println(""); + client.print(reply); + printWebString = ""; + printToWeb = false; +} + + +//******************************************************************************** +// Web Interface SD card file list +//******************************************************************************** +void handle_SDfilelist(EthernetClient client, String &post) { + + String fdelete = WebServerarg(F("delete")); + + if (fdelete.length() > 0) + { + SD.remove((char*)fdelete.c_str()); + } + + String reply = ""; + reply += F("
Filename:Size"); + + File root = SD.open("/"); + root.rewindDirectory(); + File entry = root.openNextFile(); + while (entry) + { + if (!entry.isDirectory()) + { + reply += F("
"); + if (entry.name() != "CONFIG.TXT" && entry.name() != "SECURITY.TXT") + { + reply += F("Del"); + } + reply += F(""); + reply += entry.name(); + reply += F(""); + reply += F(""); + reply += entry.size(); + } + entry.close(); + entry = root.openNextFile(); + } + //entry.close(); + root.close(); + reply += F("
"); + //reply += F("
Upload"); + addFooter(reply); + client.print(reply); +} + +//******************************************************************************** +// URNEncode char string to string object +//******************************************************************************** +String URLEncode(const char* msg) +{ + const char *hex = "0123456789abcdef"; + String encodedMsg = ""; + + while (*msg != '\0') { + if ( ('a' <= *msg && *msg <= 'z') + || ('A' <= *msg && *msg <= 'Z') + || ('0' <= *msg && *msg <= '9') ) { + encodedMsg += *msg; + } else { + encodedMsg += '%'; + encodedMsg += hex[*msg >> 4]; + encodedMsg += hex[*msg & 15]; + } + msg++; + } + return encodedMsg; +} + + +//******************************************************************************** +// Web Interface server web file from SPIFFS +//******************************************************************************** +bool handle_unknown(EthernetClient client, String path) { + //if (!isLoggedIn()) return false; + + String dataType = F("text/plain"); + if (path.endsWith("/")) path += F("index.htm"); + + if (path.endsWith(".src")) path = path.substring(0, path.lastIndexOf(".")); + else if (path.endsWith(F(".htm"))) dataType = F("text/html"); + else if (path.endsWith(F(".css"))) dataType = F("text/css"); + else if (path.endsWith(F(".js"))) dataType = F("application/javascript"); + else if (path.endsWith(F(".png"))) dataType = F("image/png"); + else if (path.endsWith(F(".gif"))) dataType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) dataType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) dataType = F("image/x-icon"); + else if (path.endsWith(F(".txt"))) dataType = F("application/octet-stream"); + else if (path.endsWith(F(".dat"))) dataType = F("application/octet-stream"); + + File dataFile = SD.open(path.c_str()); + if (!dataFile) + return false; + + client.println(F("HTTP/1.1 200 OK")); + client.print(F("Content-Type:")); + client.println(dataType); + client.println(F("Connection: close")); + + if (path.endsWith(F(".TXT"))) + { + client.println(F("Content-Disposition:attachment")); + } + + client.println(); + + while (dataFile.available()) { + client.write(dataFile.read()); + } + dataFile.close(); + return true; +} + + +//******************************************************************************** +// Web Interface I2C scanner +//******************************************************************************** +void handle_i2cscanner(EthernetClient client, String path) { + //if (!isLoggedIn()) return; + + String reply = ""; + reply += F("No I2C devices found"); + + reply += F("
I2C Addresses in useSupported devices"); + + byte error, address; + int nDevices; + nDevices = 0; + for (address = 1; address <= 127; address++ ) + { + Wire.beginTransmission(address); + error = Wire.endTransmission(); + if (error == 0) + { + reply += "
0x"; + reply += String(address, HEX); + reply += ""; + switch (address) + { + case 0x20: + case 0x21: + case 0x22: + case 0x25: + case 0x26: + case 0x27: + reply += F("PCF8574
MCP23017
LCD"); + break; + case 0x23: + reply += F("PCF8574
MCP23017
LCD
BH1750"); + break; + case 0x24: + reply += F("PCF8574
MCP23017
LCD
PN532"); + break; + case 0x29: + reply += F("TLS2561"); + break; + case 0x38: + case 0x3A: + case 0x3B: + case 0x3E: + case 0x3F: + reply += F("PCF8574A"); + break; + case 0x39: + reply += F("PCF8574A
TLS2561"); + break; + case 0x3C: + case 0x3D: + reply += F("PCF8574A
OLED"); + break; + case 0x40: + reply += F("SI7021
INA219
PCA9685"); + break; + case 0x41: + case 0x42: + case 0x43: + reply += F("INA219"); + break; + case 0x48: + case 0x4A: + case 0x4B: + reply += F("PCF8591
ADS1115"); + break; + case 0x49: + reply += F("PCF8591
ADS1115
TLS2561"); + break; + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + reply += F("PCF8591"); + break; + case 0x5A: + reply += F("MLX90614"); + break; + case 0x5C: + reply += F("DHT12
BH1750"); + break; + case 0x76: + reply += F("BME280
BMP280
MS5607
MS5611"); + break; + case 0x77: + reply += F("BMP085
BMP180
BME280
BMP280
MS5607
MS5611"); + break; + case 0x7f: + reply += F("Arduino PME"); + break; + } + nDevices++; + } + else if (error == 4) + { + reply += F("
Unknown error at address 0x"); + reply += String(address, HEX); + } + } + + if (nDevices == 0) + reply += F("
"); + addFooter(reply); + client.print(reply); +} + +//******************************************************************************** +// Web Interface log page +//******************************************************************************** +void handle_log(EthernetClient client, String path) { + //if (!isLoggedIn()) return; + + String reply = ""; + reply += F(""); + reply += F("
Log
"); + + File dataFile = SD.open("log.txt"); + if (!dataFile) + return; + unsigned long filesize = dataFile.size(); + unsigned long position = 0; + if (filesize > 1000) + { + position = filesize - 500; + dataFile.seek(position); + while (dataFile.available()) { // read until first complete line from this position + char data = dataFile.read(); + if (data == '\n') + break; + } + } + while (dataFile.available()) { + char data = dataFile.read(); + if (data == '\n') + client.print("
"); + else + client.write(data); + } + reply += F("
"); + addFooter(reply); + client.print(reply); +} + + +//******************************************************************************** +// Web Interface root page +//******************************************************************************** +void handle_sysinfo(EthernetClient client, String path) { + //if (!isLoggedIn()) return; + + int freeMem = FreeMem(); + String reply = ""; + + IPAddress ip = Ethernet.localIP(); + IPAddress gw = Ethernet.gatewayIP(); + + reply += printWebString; + reply += F("
"); + reply += F("
System Info"); + + reply += F("
Unit:"); + reply += Settings.Unit; + + if (Settings.UseNTP) + { + reply += F("
System Time:"); + reply += hour(); + reply += ":"; + if (minute() < 10) + reply += "0"; + reply += minute(); + } + + reply += F("
Uptime:"); + char strUpTime[40]; + int minutes = wdcounter / 2; + int days = minutes / 1440; + minutes = minutes % 1440; + int hrs = minutes / 60; + minutes = minutes % 60; + sprintf_P(strUpTime, PSTR("%d days %d hours %d minutes"), days, hrs, minutes); + reply += strUpTime; + + reply += F("
Load:"); + if (wdcounter > 0) + { + reply += 100 - (100 * loopCounterLast / loopCounterMax); + reply += F("% (LC="); + reply += int(loopCounterLast / 30); + reply += F(")"); + } + + reply += F("
Free Mem:"); + reply += freeMem; + //reply += F(" ("); + //reply += lowestRAM; + //reply += F(")"); + + char str[20]; + sprintf_P(str, PSTR("%u.%u.%u.%u"), ip[0], ip[1], ip[2], ip[3]); + reply += F("
IP:"); + reply += str; + + sprintf_P(str, PSTR("%u.%u.%u.%u"), gw[0], gw[1], gw[2], gw[3]); + reply += F("
GW:"); + reply += str; + + reply += F("
Build:"); + reply += BUILD; + reply += F(" "); + reply += F(BUILD_NOTES); + + reply += F("
Devices:"); + reply += deviceCount + 1; + + reply += F("
"); + addFooter(reply); + client.print(reply); +} + + +//******************************************************************************** +// Decode special characters in URL of get/post data +//******************************************************************************** +String URLDecode(const char *src) +{ + String rString; + const char* dst = src; + char a, b; + + while (*src) { + + if (*src == '+') + { + rString += ' '; + src++; + } + else + { + if ((*src == '%') && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) { + if (a >= 'a') + a -= 'a' - 'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a' - 'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + rString += (char)(16 * a + b); + src += 3; + } + else { + rString += *src++; + } + } + } + return rString; +} + diff --git a/_C001.ino b/_C001.ino new file mode 100644 index 0000000..ee43b4f --- /dev/null +++ b/_C001.ino @@ -0,0 +1,170 @@ +//####################################################################################################### +//########################### Controller Plugin 001: Domoticz HTTP ###################################### +//####################################################################################################### + +#define CPLUGIN_001 +#define CPLUGIN_ID_001 1 +#define CPLUGIN_NAME_001 "Domoticz HTTP" + +boolean CPlugin_001(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case CPLUGIN_PROTOCOL_ADD: + { + Protocol[++protocolCount].Number = CPLUGIN_ID_001; + Protocol[protocolCount].usesMQTT = false; + Protocol[protocolCount].usesAccount = true; + Protocol[protocolCount].usesPassword = true; + Protocol[protocolCount].defaultPort = 8080; + break; + } + + case CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_001); + break; + } + + case CPLUGIN_PROTOCOL_SEND: + { + String authHeader = ""; + if ((SecuritySettings.ControllerUser[0] != 0) && (SecuritySettings.ControllerPassword[0] != 0)) + { +// todo base64 encoder; +// String auth = SecuritySettings.ControllerUser; +// auth += ":"; +// auth += SecuritySettings.ControllerPassword; +// authHeader = "Authorization: Basic " + encoder.encode(auth) + " \r\n"; + } + + char log[80]; + boolean success = false; + char host[20]; + sprintf_P(host, PSTR("%u.%u.%u.%u"), Settings.Controller_IP[0], Settings.Controller_IP[1], Settings.Controller_IP[2], Settings.Controller_IP[3]); + + sprintf_P(log, PSTR("%s%s using port %u"), "HTTP : connecting to ", host,Settings.ControllerPort); + addLog(LOG_LEVEL_DEBUG, log); + + // Use WiFiClient class to create TCP connections + EthernetClient client; + if (!client.connect(host, Settings.ControllerPort)) + { + connectionFailures++; + strcpy_P(log, PSTR("HTTP : connection failed")); + addLog(LOG_LEVEL_ERROR, log); + return false; + } + #if socketdebug + ShowSocketStatus(); + #endif + statusLED(true); + if (connectionFailures) + connectionFailures--; + + // We now create a URI for the request + String url = F("/json.htm?type=command¶m=udevice&idx="); + url += event->idx; + + switch (event->sensorType) + { + case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc + url += F("&svalue="); + url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + break; + case SENSOR_TYPE_LONG: // single LONG value, stored in two floats (rfid tags) + url += F("&svalue="); + url += (unsigned long)UserVar[event->BaseVarIndex] + ((unsigned long)UserVar[event->BaseVarIndex + 1] << 16); + break; + case SENSOR_TYPE_DUAL: // any sensor that uses two simple values + url += F("&svalue="); + url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + url += ";"; + url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + break; + case SENSOR_TYPE_TEMP_HUM: // temp + hum + hum_stat, used for DHT11 + url += F("&svalue="); + url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + url += ";"; + url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + url += ";0"; + break; + case SENSOR_TYPE_TEMP_BARO: // temp + hum + hum_stat + bar + bar_fore, used for BMP085 + url += F("&svalue="); + url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + url += ";0;0;"; + url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + url += ";0"; + break; + case SENSOR_TYPE_TEMP_HUM_BARO: // temp + hum + hum_stat + bar + bar_fore, used for BME280 + url += F("&svalue="); + url += toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + url += ";"; + url += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + url += ";0;"; + url += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]); + url += ";0"; + break; + case SENSOR_TYPE_SWITCH: + url = F("/json.htm?type=command¶m=switchlight&idx="); + url += event->idx; + url += F("&switchcmd="); + if (UserVar[event->BaseVarIndex] == 0) + url += "Off"; + else + url += "On"; + break; + case SENSOR_TYPE_DIMMER: + url = F("/json.htm?type=command¶m=switchlight&idx="); + url += event->idx; + url += F("&switchcmd="); + if (UserVar[event->BaseVarIndex] == 0) + url += "Off"; + else + { + url += F("Set%20Level&level="); + url += UserVar[event->BaseVarIndex]; + } + break; + } + + url.toCharArray(log, 80); + addLog(LOG_LEVEL_DEBUG_MORE, log); + + // This will send the request to the server + client.print(String("GET ") + url + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + authHeader + + "Connection: close\r\n\r\n"); + + unsigned long timer = millis() + 200; + while (!client.available() && millis() < timer) + delay(1); + + // Read all the lines of the reply from server and print them to Serial + while (client.available()) { + String line = client.readStringUntil('\n'); + line.toCharArray(log, 80); + addLog(LOG_LEVEL_DEBUG_MORE, log); + if (line.substring(0, 15) == "HTTP/1.1 200 OK") + { + strcpy_P(log, PSTR("HTTP : Success")); + addLog(LOG_LEVEL_DEBUG, log); + success = true; + } + delay(1); + } + strcpy_P(log, PSTR("HTTP : closing connection")); + addLog(LOG_LEVEL_DEBUG, log); + + client.flush(); + client.stop(); + + break; + } + + } + return success; +} + diff --git a/_C002.ino b/_C002.ino new file mode 100644 index 0000000..6f6fea9 --- /dev/null +++ b/_C002.ino @@ -0,0 +1,227 @@ +//####################################################################################################### +//########################### Controller Plugin 002: Domoticz MQTT ###################################### +//####################################################################################################### + +#if FEATURE_MQTT_DOM +#define CPLUGIN_002 +#define CPLUGIN_ID_002 2 +#define CPLUGIN_NAME_002 "Domoticz MQTT" + +boolean CPlugin_002(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case CPLUGIN_PROTOCOL_ADD: + { + Protocol[++protocolCount].Number = CPLUGIN_ID_002; + Protocol[protocolCount].usesMQTT = true; + Protocol[protocolCount].usesTemplate = true; + Protocol[protocolCount].usesAccount = true; + Protocol[protocolCount].usesPassword = true; + Protocol[protocolCount].defaultPort = 1883; + break; + } + + case CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_002); + break; + } + + case CPLUGIN_PROTOCOL_TEMPLATE: + { + strcpy_P(Settings.MQTTsubscribe, PSTR("domoticz/out")); + strcpy_P(Settings.MQTTpublish, PSTR("domoticz/in")); + break; + } + + case CPLUGIN_PROTOCOL_RECV: + { + char json[512]; + json[0] = 0; + event->String2.toCharArray(json, 512); + + StaticJsonBuffer<512> jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(json); + + if (root.success()) + { + long idx = root["idx"]; + float nvalue = root["nvalue"]; + long nvaluealt = root["nvalue"]; + //const char* name = root["name"]; // Not used + //const char* svalue = root["svalue"]; // Not used + const char* svalue1 = root["svalue1"]; + //const char* svalue2 = root["svalue2"]; // Not used + //const char* svalue3 = root["svalue3"]; // Not used + const char* switchtype = root["switchType"]; // Expect "On/Off" or "dimmer" + if (nvalue == 0) + nvalue = nvaluealt; + if ((int)switchtype == 0) + switchtype = "?"; + + for (byte x = 0; x < TASKS_MAX; x++) + { + if (Settings.TaskDeviceID[x] == idx) + { + if (Settings.TaskDeviceNumber[x] == 1) // temp solution, if input switch, update state + { + String action = F("inputSwitchState,"); + action += x; + action += ","; + action += nvalue; + struct EventStruct TempEvent; + parseCommandString(&TempEvent, action); + PluginCall(PLUGIN_WRITE, &TempEvent, action); + } + if (Settings.TaskDeviceNumber[x] == 29) // temp solution, if plugin 029, set gpio + { + String action = ""; + int baseVar = x * VARS_PER_TASK; + struct EventStruct TempEvent; + if (strcasecmp_P(switchtype, PSTR("dimmer")) == 0) + { + int pwmValue = UserVar[baseVar]; + action = F("pwm,"); + action += Settings.TaskDevicePin1[x]; + action += ","; + switch ((int)nvalue) + { + case 0: + pwmValue = 0; + break; + case 1: + pwmValue = UserVar[baseVar]; + break; + case 2: + pwmValue = 10 * atol(svalue1); + UserVar[baseVar] = pwmValue; + break; + } + action += pwmValue; + } + else + { + UserVar[baseVar] = nvalue; + action = F("gpio,"); + action += Settings.TaskDevicePin1[x]; + action += ","; + action += nvalue; + } + parseCommandString(&TempEvent, action); + PluginCall(PLUGIN_WRITE, &TempEvent, action); + } + } + } + } + break; + } + + case CPLUGIN_PROTOCOL_SEND: + { + StaticJsonBuffer<200> jsonBuffer; + + JsonObject& root = jsonBuffer.createObject(); + + root["idx"] = event->idx; + + String values; + char str[80]; + + switch (event->sensorType) + { + case SENSOR_TYPE_SINGLE: // single value sensor, used for Dallas, BH1750, etc + root["nvalue"] = 0; + values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_LONG: // single LONG value, stored in two floats (rfid tags) + root["nvalue"] = 0; + values = (unsigned long)UserVar[event->BaseVarIndex] + ((unsigned long)UserVar[event->BaseVarIndex + 1] << 16); + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_DUAL: // any sensor that uses two simple values + root["nvalue"] = 0; + values = toString(UserVar[event->BaseVarIndex ],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + values += ";"; + values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_TEMP_HUM: // temp + hum + hum_stat, used for DHT11 + root["nvalue"] = 0; + values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + values += ";"; + values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + values += ";0"; + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_TEMP_BARO: // temp + hum + hum_stat + bar + bar_fore, used for BMP085 + root["nvalue"] = 0; + values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + values += ";0;0;"; + values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + values += ";0"; + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_TEMP_HUM_BARO: // temp + hum + hum_stat + bar + bar_fore, used for BME280 + root["nvalue"] = 0; + values = toString(UserVar[event->BaseVarIndex],ExtraTaskSettings.TaskDeviceValueDecimals[0]); + values += ";"; + values += toString(UserVar[event->BaseVarIndex + 1],ExtraTaskSettings.TaskDeviceValueDecimals[1]); + values += ";0;"; + values += toString(UserVar[event->BaseVarIndex + 2],ExtraTaskSettings.TaskDeviceValueDecimals[2]); + values += ";0"; + values.toCharArray(str, 80); + root["svalue"] = str; + break; + case SENSOR_TYPE_SWITCH: + root["command"] = "switchlight"; + if (UserVar[event->BaseVarIndex] == 0) + root["switchcmd"] = "Off"; + else + root["switchcmd"] = "On"; + break; + case SENSOR_TYPE_DIMMER: + root["command"] = "switchlight"; + if (UserVar[event->BaseVarIndex] == 0) + root["switchcmd"] = "Off"; + else + root["Set%20Level"] = UserVar[event->BaseVarIndex]; + break; + } + + char json[256]; + root.printTo(json, sizeof(json)); + String log = F("MQTT : "); + log += json; + addLog(LOG_LEVEL_DEBUG, json); + + String pubname = Settings.MQTTpublish; + pubname.replace("%sysname%", Settings.Name); + pubname.replace("%tskname%", ExtraTaskSettings.TaskDeviceName); + pubname.replace("%id%", String(event->idx)); + + if (!MQTTclient.publish(pubname.c_str(), json, Settings.MQTTRetainFlag)) + { + log = F("MQTT publish failed"); + addLog(LOG_LEVEL_DEBUG, json); + MQTTConnect(); + connectionFailures++; + } + else if (connectionFailures) + connectionFailures--; + break; + } + + } + return success; +} +#endif + diff --git a/_C005.ino b/_C005.ino new file mode 100644 index 0000000..57c7f15 --- /dev/null +++ b/_C005.ino @@ -0,0 +1,110 @@ +//####################################################################################################### +//########################### Controller Plugin 005: OpenHAB MQTT ####################################### +//####################################################################################################### + +#define CPLUGIN_005 +#define CPLUGIN_ID_005 5 +#define CPLUGIN_NAME_005 "OpenHAB MQTT" + +boolean CPlugin_005(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case CPLUGIN_PROTOCOL_ADD: + { + Protocol[++protocolCount].Number = CPLUGIN_ID_005; + Protocol[protocolCount].usesMQTT = true; + Protocol[protocolCount].usesTemplate = true; + Protocol[protocolCount].usesAccount = true; + Protocol[protocolCount].usesPassword = true; + Protocol[protocolCount].defaultPort = 1883; + break; + } + + case CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_005); + break; + } + + case CPLUGIN_PROTOCOL_TEMPLATE: + { + strcpy_P(Settings.MQTTsubscribe, PSTR("/%sysname%/#")); + strcpy_P(Settings.MQTTpublish, PSTR("/%sysname%/%tskname%/%valname%")); + break; + } + + case CPLUGIN_PROTOCOL_RECV: + { + // Split topic into array + String tmpTopic = event->String1.substring(1); + String topicSplit[10]; + int SlashIndex = tmpTopic.indexOf('/'); + byte count = 0; + while (SlashIndex > 0 && count < 10 - 1) + { + topicSplit[count] = tmpTopic.substring(0, SlashIndex); + tmpTopic = tmpTopic.substring(SlashIndex + 1); + SlashIndex = tmpTopic.indexOf('/'); + count++; + } + topicSplit[count] = tmpTopic; + + String cmd = ""; + struct EventStruct TempEvent; + + if (topicSplit[count] == "cmd") + { + cmd = event->String2; + parseCommandString(&TempEvent, cmd); + TempEvent.Source = VALUE_SOURCE_MQTT; + } + else + { + cmd = topicSplit[count - 1]; + TempEvent.Par1 = topicSplit[count].toInt(); + TempEvent.Par2 = event->String2.toFloat(); + TempEvent.Par3 = 0; + } + // in case of event, store to buffer and return... + String command = parseString(cmd, 1); + if (command == F("event")) + eventBuffer = cmd.substring(6); + else + PluginCall(PLUGIN_WRITE, &TempEvent, cmd); + break; + } + + case CPLUGIN_PROTOCOL_SEND: + { + statusLED(true); + + if (ExtraTaskSettings.TaskDeviceValueNames[0][0] == 0) + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, event, dummyString); + + String pubname = Settings.MQTTpublish; + pubname.replace("%sysname%", Settings.Name); + pubname.replace("%tskname%", ExtraTaskSettings.TaskDeviceName); + pubname.replace("%id%", String(event->idx)); + + String value = ""; + byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[event->TaskIndex]); + byte valueCount = getValueCountFromSensorType(event->sensorType); + for (byte x = 0; x < valueCount; x++) + { + String tmppubname = pubname; + tmppubname.replace("%valname%", ExtraTaskSettings.TaskDeviceValueNames[x]); + if (event->sensorType == SENSOR_TYPE_LONG) + value = (unsigned long)UserVar[event->BaseVarIndex] + ((unsigned long)UserVar[event->BaseVarIndex + 1] << 16); + else + value = toString(UserVar[event->BaseVarIndex + x], ExtraTaskSettings.TaskDeviceValueDecimals[x]); + MQTTclient.publish(tmppubname.c_str(), value.c_str(), Settings.MQTTRetainFlag); + } + break; + } + return success; + } +} + diff --git a/_P001_Switch.ino b/_P001_Switch.ino new file mode 100644 index 0000000..0fd1f36 --- /dev/null +++ b/_P001_Switch.ino @@ -0,0 +1,346 @@ +//####################################################################################################### +//#################################### Plugin 001: Input Switch ######################################### +//####################################################################################################### + +// Adapted from ESP Easy, changes: +// WebServer.arg() -> WebServerarg() +// Changed pin limit from 0-16 to 2-13 + +#define PLUGIN_001 +#define PLUGIN_ID_001 1 +#define PLUGIN_NAME_001 "Switch input" +#define PLUGIN_VALUENAME1_001 "Switch" + +boolean Plugin_001(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + static byte switchstate[TASKS_MAX]; + static byte outputstate[TASKS_MAX]; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_001; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = true; + Device[deviceCount].InverseLogicOption = true; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_001); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_001)); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0]; + String options[2]; + options[0] = F("Switch"); + options[1] = F("Dimmer"); + int optionValues[2]; + optionValues[0] = 1; + optionValues[1] = 2; + string += F("Switch Type:"); + + if (Settings.TaskDevicePluginConfig[event->TaskIndex][0] == 2) + { + char tmpString[128]; + sprintf_P(tmpString, PSTR("Dim value:"), Settings.TaskDevicePluginConfig[event->TaskIndex][1]); + string += tmpString; + } + + choice = Settings.TaskDevicePluginConfig[event->TaskIndex][2]; + String buttonOptions[3]; + buttonOptions[0] = F("Normal Switch"); + buttonOptions[1] = F("Push Button Active Low"); + buttonOptions[2] = F("Push Button Active High"); + int buttonOptionValues[3]; + buttonOptionValues[0] = 0; + buttonOptionValues[1] = 1; + buttonOptionValues[2] = 2; + string += F("Switch Button Type:"); + + string += F("Send Boot state:"); + if (Settings.TaskDevicePluginConfig[event->TaskIndex][3]) + string += F(""); + else + string += F(""); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + String plugin1 = WebServerarg(F("plugin_001_type")); + Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt(); + if (Settings.TaskDevicePluginConfig[event->TaskIndex][0] == 2) + { + String plugin2 = WebServerarg(F("plugin_001_dimvalue")); + Settings.TaskDevicePluginConfig[event->TaskIndex][1] = plugin2.toInt(); + } + String plugin3 = WebServerarg(F("plugin_001_button")); + Settings.TaskDevicePluginConfig[event->TaskIndex][2] = plugin3.toInt(); + + String plugin4 = WebServerarg(F("plugin_001_boot")); + Settings.TaskDevicePluginConfig[event->TaskIndex][3] = (plugin4 == "on"); + + success = true; + break; + } + + case PLUGIN_INIT: + { + if (Settings.TaskDevicePin1PullUp[event->TaskIndex]) + pinMode(Settings.TaskDevicePin1[event->TaskIndex], INPUT_PULLUP); + else + pinMode(Settings.TaskDevicePin1[event->TaskIndex], INPUT); + + setPinState(PLUGIN_ID_001, Settings.TaskDevicePin1[event->TaskIndex], PIN_MODE_INPUT, 0); + + switchstate[event->TaskIndex] = digitalRead(Settings.TaskDevicePin1[event->TaskIndex]); + outputstate[event->TaskIndex] = switchstate[event->TaskIndex]; + + // if boot state must be send, inverse default state + if (Settings.TaskDevicePluginConfig[event->TaskIndex][3]) + { + switchstate[event->TaskIndex] = !switchstate[event->TaskIndex]; + outputstate[event->TaskIndex] = !outputstate[event->TaskIndex]; + } + success = true; + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + byte state = digitalRead(Settings.TaskDevicePin1[event->TaskIndex]); + if (state != switchstate[event->TaskIndex]) + { + switchstate[event->TaskIndex] = state; + byte currentOutputState = outputstate[event->TaskIndex]; + + if (Settings.TaskDevicePluginConfig[event->TaskIndex][2] == 0) //normal switch + outputstate[event->TaskIndex] = state; + else + { + if (Settings.TaskDevicePluginConfig[event->TaskIndex][2] == 1) // active low push button + { + if (state == 0) + outputstate[event->TaskIndex] = !outputstate[event->TaskIndex]; + } + else // active high push button + { + if (state == 1) + outputstate[event->TaskIndex] = !outputstate[event->TaskIndex]; + } + } + + // send if output needs to be changed + if (currentOutputState != outputstate[event->TaskIndex]) + { + byte sendState = outputstate[event->TaskIndex]; + if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) + sendState = !outputstate[event->TaskIndex]; + UserVar[event->BaseVarIndex] = sendState; + event->sensorType = SENSOR_TYPE_SWITCH; + if ((sendState == 1) && (Settings.TaskDevicePluginConfig[event->TaskIndex][0] == 2)) + { + event->sensorType = SENSOR_TYPE_DIMMER; + UserVar[event->BaseVarIndex] = Settings.TaskDevicePluginConfig[event->TaskIndex][1]; + } + String log = F("SW : State "); + log += sendState; + addLog(LOG_LEVEL_INFO, log); + sendData(event); + } + } + success = true; + break; + } + + case PLUGIN_READ: + { + // We do not actually read the pin state as this is already done 10x/second + // Instead we just send the last known state stored in Uservar + String log = F("SW : State "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } + + case PLUGIN_WRITE: + { + String log = ""; + String command = parseString(string, 1); + + if (command == F("gpio")) + { + success = true; + if (event->Par1 >= 2 && event->Par1 <= 13) + { + pinMode(event->Par1, OUTPUT); + digitalWrite(event->Par1, event->Par2); + setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = String(F("SW : GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); + addLog(LOG_LEVEL_INFO, log); + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0)); + } + } + + if (command == F("pwm")) + { + success = true; + if (event->Par1 >= 2 && event->Par1 <= 13) + { + pinMode(event->Par1, OUTPUT); + + if(event->Par3 != 0) + { + byte prev_mode; + uint16_t prev_value; + getPinState(PLUGIN_ID_001, event->Par1, &prev_mode, &prev_value); + if(prev_mode != PIN_MODE_PWM) + prev_value = 0; + + int32_t step_value = ((event->Par2 - prev_value) << 12) / event->Par3; + int32_t curr_value = prev_value << 12; + int16_t new_value; + int i = event->Par3; + while(i--){ + curr_value += step_value; + new_value = (uint16_t)(curr_value >> 12); + analogWrite(event->Par1, new_value); + delay(1); + } + } + + analogWrite(event->Par1, event->Par2); + setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_PWM, event->Par2); + log = String(F("SW : GPIO ")) + String(event->Par1) + String(F(" Set PWM to ")) + String(event->Par2); + addLog(LOG_LEVEL_INFO, log); + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0)); + } + } + + if (command == F("pulse")) + { + success = true; + if (event->Par1 >= 2 && event->Par1 <= 13) + { + pinMode(event->Par1, OUTPUT); + digitalWrite(event->Par1, event->Par2); + delay(event->Par3); + digitalWrite(event->Par1, !event->Par2); + setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = String(F("SW : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); + addLog(LOG_LEVEL_INFO, log); + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0)); + } + } + + if (command == F("longpulse")) + { + success = true; + if (event->Par1 >= 2 && event->Par1 <= 13) + { + pinMode(event->Par1, OUTPUT); + digitalWrite(event->Par1, event->Par2); + setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2); + setSystemTimer(event->Par3 * 1000, PLUGIN_ID_001, event->Par1, !event->Par2, 0); + log = String(F("SW : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); + addLog(LOG_LEVEL_INFO, log); + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par1, log, 0)); + } + } + + if (command == F("servo")) + { + success = true; + if (event->Par1 >= 0 && event->Par1 <= 2) + switch (event->Par1) + { + case 1: + // todo myservo1.attach(event->Par2); + // todo myservo1.write(event->Par3); + break; + case 2: + // todo myservo2.attach(event->Par2); + // todo myservo2.write(event->Par3); + break; + } + setPinState(PLUGIN_ID_001, event->Par2, PIN_MODE_SERVO, event->Par3); + log = String(F("SW : GPIO ")) + String(event->Par2) + String(F(" Servo set to ")) + String(event->Par3); + addLog(LOG_LEVEL_INFO, log); + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par2, log, 0)); + } + + if (command == F("status")) + { + if (parseString(string, 2) == F("gpio")) + { + success = true; + SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_001, event->Par2, dummyString, 0)); + } + } + + if (command == F("inputswitchstate")) + { + success = true; + UserVar[event->Par1 * VARS_PER_TASK] = event->Par2; + outputstate[event->Par1] = event->Par2; + } + + break; + } + + case PLUGIN_TIMER_IN: + { + digitalWrite(event->Par1, event->Par2); + setPinState(PLUGIN_ID_001, event->Par1, PIN_MODE_OUTPUT, event->Par2); + break; + } + } + return success; +} diff --git a/_P002_ADC.ino b/_P002_ADC.ino new file mode 100644 index 0000000..09aa1f6 --- /dev/null +++ b/_P002_ADC.ino @@ -0,0 +1,62 @@ +//####################################################################################################### +//#################################### Plugin 002: Analog ############################################### +//####################################################################################################### + +// Adapted from ESP Easy, changes: +// WebServer.arg() -> WebServerarg() +// port selection as we have a lot of analog ports here... + +#define PLUGIN_002 +#define PLUGIN_ID_002 2 +#define PLUGIN_NAME_002 "Analog input" +#define PLUGIN_VALUENAME1_002 "Analog" +boolean Plugin_002(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_002; + Device[deviceCount].Type = DEVICE_TYPE_ANALOG; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 1; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_002); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_002)); + break; + } + + case PLUGIN_READ: + { + int value = analogRead(Settings.TaskDevicePort[event->TaskIndex]); + UserVar[event->BaseVarIndex] = (float)value; + String log = F("ADC : Analog port "); + log += Settings.TaskDevicePort[event->TaskIndex]; + log += F(" value: "); + log += value; + addLog(LOG_LEVEL_INFO,log); + success = true; + break; + } + } + return success; +} diff --git a/_P003_Pulse.ino b/_P003_Pulse.ino new file mode 100644 index 0000000..ee00122 --- /dev/null +++ b/_P003_Pulse.ino @@ -0,0 +1,260 @@ +//####################################################################################################### +//#################################### Plugin 003: Pulse ############################################### +//####################################################################################################### + +// Adapted from ESP Easy, changes: +// WebServer.arg() -> WebServerarg() + +#define PLUGIN_003 +#define PLUGIN_ID_003 3 +#define PLUGIN_NAME_003 "Pulse Counter" +#define PLUGIN_VALUENAME1_003 "Count" +#define PLUGIN_VALUENAME2_003 "Total" +#define PLUGIN_VALUENAME3_003 "Time" + +unsigned long Plugin_003_pulseCounter[TASKS_MAX]; +unsigned long Plugin_003_pulseTotalCounter[TASKS_MAX]; +unsigned long Plugin_003_pulseTime[TASKS_MAX]; +unsigned long Plugin_003_pulseTimePrevious[TASKS_MAX]; + +boolean Plugin_003(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_003; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_003); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_003)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_003)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_003)); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + char tmpString[128]; + sprintf_P(tmpString, PSTR("Debounce Time (mSec):"), Settings.TaskDevicePluginConfig[event->TaskIndex][0]); + string += tmpString; + + byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][1]; + String options[3]; + options[0] = F("Delta"); + options[1] = F("Delta/Total/Time"); + options[2] = F("Total"); + int optionValues[3]; + optionValues[0] = 0; + optionValues[1] = 1; + optionValues[2] = 2; + string += F("Counter Type:"); + + if (choice !=0) + string += F("Total count is not persistent!"); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + String plugin1 = WebServerarg(F("plugin_003")); + Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt(); + String plugin2 = WebServerarg(F("plugin_003_countertype")); + Settings.TaskDevicePluginConfig[event->TaskIndex][1] = plugin2.toInt(); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_VALUES: + { + string += F("
"); + string += ExtraTaskSettings.TaskDeviceValueNames[0]; + string += F(":
"); + string += Plugin_003_pulseCounter[event->TaskIndex]; + string += F("
"); + string += ExtraTaskSettings.TaskDeviceValueNames[1]; + string += F(":
"); + string += Plugin_003_pulseTotalCounter[event->TaskIndex]; + string += F("
"); + string += ExtraTaskSettings.TaskDeviceValueNames[2]; + string += F(":
"); + string += Plugin_003_pulseTime[event->TaskIndex]; + string += F("
"); + success = true; + break; + } + + case PLUGIN_INIT: + { + String log = F("INIT : Pulse "); + log += Settings.TaskDevicePin1[event->TaskIndex]; + addLog(LOG_LEVEL_INFO,log); + pinMode(Settings.TaskDevicePin1[event->TaskIndex], INPUT_PULLUP); + Plugin_003_pulseinit(Settings.TaskDevicePin1[event->TaskIndex], event->TaskIndex); + success = true; + break; + } + + case PLUGIN_READ: + { + UserVar[event->BaseVarIndex] = Plugin_003_pulseCounter[event->TaskIndex]; + UserVar[event->BaseVarIndex+1] = Plugin_003_pulseTotalCounter[event->TaskIndex]; + UserVar[event->BaseVarIndex+2] = Plugin_003_pulseTime[event->TaskIndex]; + + switch (Settings.TaskDevicePluginConfig[event->TaskIndex][1]) + { + case 0: + { + event->sensorType = SENSOR_TYPE_SINGLE; + UserVar[event->BaseVarIndex] = Plugin_003_pulseCounter[event->TaskIndex]; + break; + } + case 1: + { + event->sensorType = SENSOR_TYPE_TRIPLE; + UserVar[event->BaseVarIndex] = Plugin_003_pulseCounter[event->TaskIndex]; + UserVar[event->BaseVarIndex+1] = Plugin_003_pulseTotalCounter[event->TaskIndex]; + UserVar[event->BaseVarIndex+2] = Plugin_003_pulseTime[event->TaskIndex]; + break; + } + case 2: + { + event->sensorType = SENSOR_TYPE_SINGLE; + UserVar[event->BaseVarIndex] = Plugin_003_pulseTotalCounter[event->TaskIndex]; + break; + } + } + Plugin_003_pulseCounter[event->TaskIndex] = 0; + success = true; + break; + } + } + return success; +} + + +/*********************************************************************************************\ + * Check Pulse Counters (called from irq handler) +\*********************************************************************************************/ +void Plugin_003_pulsecheck(byte Index) +{ + unsigned long PulseTime=millis() - Plugin_003_pulseTimePrevious[Index]; + if(PulseTime > Settings.TaskDevicePluginConfig[Index][0]) // check with debounce time for this task + { + Plugin_003_pulseCounter[Index]++; + Plugin_003_pulseTotalCounter[Index]++; + Plugin_003_pulseTime[Index] = PulseTime; + Plugin_003_pulseTimePrevious[Index]=millis(); + } +} + + +/*********************************************************************************************\ + * Pulse Counter IRQ handlers +\*********************************************************************************************/ +void Plugin_003_pulse_interrupt1() +{ + Plugin_003_pulsecheck(0); +} +void Plugin_003_pulse_interrupt2() +{ + Plugin_003_pulsecheck(1); +} +void Plugin_003_pulse_interrupt3() +{ + Plugin_003_pulsecheck(2); +} +void Plugin_003_pulse_interrupt4() +{ + Plugin_003_pulsecheck(3); +} +void Plugin_003_pulse_interrupt5() +{ + Plugin_003_pulsecheck(4); +} +void Plugin_003_pulse_interrupt6() +{ + Plugin_003_pulsecheck(5); +} +void Plugin_003_pulse_interrupt7() +{ + Plugin_003_pulsecheck(6); +} +void Plugin_003_pulse_interrupt8() +{ + Plugin_003_pulsecheck(7); +} + + +/*********************************************************************************************\ + * Init Pulse Counters +\*********************************************************************************************/ +void Plugin_003_pulseinit(byte Par1, byte Index) +{ + // Init IO pins + String log = F("PULSE: Init"); + addLog(LOG_LEVEL_INFO,log); + + switch (Index) + { + case 0: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt1, FALLING); + break; + case 1: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt2, FALLING); + break; + case 2: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt3, FALLING); + break; + case 3: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt4, FALLING); + break; + case 4: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt5, FALLING); + break; + case 5: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt6, FALLING); + break; + case 6: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt7, FALLING); + break; + case 7: + attachInterrupt(digitalPinToInterrupt(Par1), Plugin_003_pulse_interrupt8, FALLING); + break; + } +} diff --git a/_P033_Dummy.ino b/_P033_Dummy.ino new file mode 100644 index 0000000..c5d4153 --- /dev/null +++ b/_P033_Dummy.ino @@ -0,0 +1,115 @@ +//####################################################################################################### +//#################################### Plugin 033: Dummy ################################################ +//####################################################################################################### + +// Adapted from ESP Easy, changes: +// WebServer.arg() -> WebServerarg() + +#define PLUGIN_033 +#define PLUGIN_ID_033 33 +#define PLUGIN_NAME_033 "Dummy Device" +#define PLUGIN_VALUENAME1_033 "Dummy" + +boolean Plugin_033(byte function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_033; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_033); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_033)); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + byte choice = Settings.TaskDevicePluginConfig[event->TaskIndex][0]; + String options[9]; + options[0] = F("SENSOR_TYPE_SINGLE"); + options[1] = F("SENSOR_TYPE_TEMP_HUM"); + options[2] = F("SENSOR_TYPE_TEMP_BARO"); + options[3] = F("SENSOR_TYPE_TEMP_HUM_BARO"); + options[4] = F("SENSOR_TYPE_DUAL"); + options[5] = F("SENSOR_TYPE_TRIPLE"); + options[6] = F("SENSOR_TYPE_QUAD"); + options[7] = F("SENSOR_TYPE_SWITCH"); + options[8] = F("SENSOR_TYPE_DIMMER"); + int optionValues[9]; + optionValues[0] = SENSOR_TYPE_SINGLE; + optionValues[1] = SENSOR_TYPE_TEMP_HUM; + optionValues[2] = SENSOR_TYPE_TEMP_BARO; + optionValues[3] = SENSOR_TYPE_TEMP_HUM_BARO; + optionValues[4] = SENSOR_TYPE_DUAL; + optionValues[5] = SENSOR_TYPE_TRIPLE; + optionValues[6] = SENSOR_TYPE_QUAD; + optionValues[7] = SENSOR_TYPE_SWITCH; + optionValues[8] = SENSOR_TYPE_DIMMER; + string += F("Simulate Data Type:"); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + String plugin1 = WebServerarg(F("plugin_033_sensortype")); + Settings.TaskDevicePluginConfig[event->TaskIndex][0] = plugin1.toInt(); + success = true; + break; + } + + case PLUGIN_READ: + { + event->sensorType =Settings.TaskDevicePluginConfig[event->TaskIndex][0]; + for (byte x=0; x<4;x++) + { + String log = F("Dummy: value "); + log += x+1; + log += F(": "); + log += UserVar[event->BaseVarIndex+x]; + addLog(LOG_LEVEL_INFO,log); + } + success = true; + break; + } + } + return success; +} + diff --git a/__CPlugin.ino b/__CPlugin.ino new file mode 100644 index 0000000..92fb6ff --- /dev/null +++ b/__CPlugin.ino @@ -0,0 +1,142 @@ +//******************************************************************************** +// Initialize all Controller CPlugins that where defined earlier +// and initialize the function call pointer into the CCPlugin array +//******************************************************************************** +void CPluginInit(void) +{ + byte x; + + // Clear pointer table for all plugins + for (x = 0; x < CPLUGIN_MAX; x++) + { + CPlugin_ptr[x] = 0; + CPlugin_id[x] = 0; + } + + x = 0; + +#ifdef CPLUGIN_001 + CPlugin_id[x] = 1; CPlugin_ptr[x++] = &CPlugin_001; +#endif + +#ifdef CPLUGIN_002 + CPlugin_id[x] = 2; CPlugin_ptr[x++] = &CPlugin_002; +#endif + +#ifdef CPLUGIN_003 + CPlugin_id[x] = 3; CPlugin_ptr[x++] = &CPlugin_003; +#endif + +#ifdef CPLUGIN_004 + CPlugin_id[x] = 4; CPlugin_ptr[x++] = &CPlugin_004; +#endif + +#ifdef CPLUGIN_005 + CPlugin_id[x] = 5; CPlugin_ptr[x++] = &CPlugin_005; +#endif + +#ifdef CPLUGIN_006 + CPlugin_id[x] = 6; CPlugin_ptr[x++] = &CPlugin_006; +#endif + +#ifdef CPLUGIN_007 + CPlugin_id[x] = 7; CPlugin_ptr[x++] = &CPlugin_007; +#endif + +#ifdef CPLUGIN_008 + CPlugin_id[x] = 8; CPlugin_ptr[x++] = &CPlugin_008; +#endif + +#ifdef CPLUGIN_009 + CPlugin_id[x] = 9; CPlugin_ptr[x++] = &CPlugin_009; +#endif + +#ifdef CPLUGIN_010 + CPlugin_id[x] = 10; CPlugin_ptr[x++] = &CPlugin_010; +#endif + +#ifdef CPLUGIN_011 + CPlugin_id[x] = 11; CPlugin_ptr[x++] = &CPlugin_011; +#endif + +#ifdef CPLUGIN_012 + CPlugin_id[x] = 12; CPlugin_ptr[x++] = &CPlugin_012; +#endif + +#ifdef CPLUGIN_013 + CPlugin_id[x] = 13; CPlugin_ptr[x++] = &CPlugin_013; +#endif + +#ifdef CPLUGIN_014 + CPlugin_id[x] = 14; CPlugin_ptr[x++] = &CPlugin_014; +#endif + +#ifdef CPLUGIN_015 + CPlugin_id[x] = 15; CPlugin_ptr[x++] = &CPlugin_015; +#endif + +#ifdef CPLUGIN_016 + CPlugin_id[x] = 16; CPlugin_ptr[x++] = &CPlugin_016; +#endif + +#ifdef CPLUGIN_017 + CPlugin_id[x] = 17; CPlugin_ptr[x++] = &CPlugin_017; +#endif + +#ifdef CPLUGIN_018 + CPlugin_id[x] = 18; CPlugin_ptr[x++] = &CPlugin_018; +#endif + +#ifdef CPLUGIN_019 + CPlugin_id[x] = 19; CPlugin_ptr[x++] = &CPlugin_019; +#endif + +#ifdef CPLUGIN_020 + CPlugin_id[x] = 20; CPlugin_ptr[x++] = &CPlugin_020; +#endif + +#ifdef CPLUGIN_021 + CPlugin_id[x] = 21; CPlugin_ptr[x++] = &CPlugin_021; +#endif + +#ifdef CPLUGIN_022 + CPlugin_id[x] = 22; CPlugin_ptr[x++] = &CPlugin_022; +#endif + +#ifdef CPLUGIN_023 + CPlugin_id[x] = 23; CPlugin_ptr[x++] = &CPlugin_023; +#endif + +#ifdef CPLUGIN_024 + CPlugin_id[x] = 24; CPlugin_ptr[x++] = &CPlugin_024; +#endif + +#ifdef CPLUGIN_025 + CPlugin_id[x] = 25; CPlugin_ptr[x++] = &CPlugin_025; +#endif + + CPluginCall(CPLUGIN_PROTOCOL_ADD, 0); +} + +byte CPluginCall(byte Function, struct EventStruct *event) +{ + int x; + struct EventStruct TempEvent; + + if (event == 0) + event=&TempEvent; + + switch (Function) + { + // Unconditional calls to all plugins + case CPLUGIN_PROTOCOL_ADD: + for (x = 0; x < CPLUGIN_MAX; x++) + if (CPlugin_id[x] != 0) + CPlugin_ptr[x](Function, event, dummyString); + return true; + break; + } + + return false; +} + diff --git a/__Plugin.ino b/__Plugin.ino new file mode 100644 index 0000000..ead5796 --- /dev/null +++ b/__Plugin.ino @@ -0,0 +1,1156 @@ +//******************************************************************************** +// Initialize all plugins that where defined earlier +// and initialize the function call pointer into the plugin array +//******************************************************************************** +void PluginInit(void) +{ + byte x; + + // Clear pointer table for all plugins + for (x = 0; x < PLUGIN_MAX; x++) + { + Plugin_ptr[x] = 0; + Plugin_id[x] = 0; + } + + x = 0; + +#ifdef PLUGIN_001 + Plugin_id[x] = 1; Plugin_ptr[x++] = &Plugin_001; +#endif + +#ifdef PLUGIN_002 + Plugin_id[x] = 2; Plugin_ptr[x++] = &Plugin_002; +#endif + +#ifdef PLUGIN_003 + Plugin_id[x] = 3; Plugin_ptr[x++] = &Plugin_003; +#endif + +#ifdef PLUGIN_004 + Plugin_id[x] = 4; Plugin_ptr[x++] = &Plugin_004; +#endif + +#ifdef PLUGIN_005 + Plugin_id[x] = 5; Plugin_ptr[x++] = &Plugin_005; +#endif + +#ifdef PLUGIN_006 + Plugin_id[x] = 6; Plugin_ptr[x++] = &Plugin_006; +#endif + +#ifdef PLUGIN_007 + Plugin_id[x] = 7; Plugin_ptr[x++] = &Plugin_007; +#endif + +#ifdef PLUGIN_008 + Plugin_id[x] = 8; Plugin_ptr[x++] = &Plugin_008; +#endif + +#ifdef PLUGIN_009 + Plugin_id[x] = 9; Plugin_ptr[x++] = &Plugin_009; +#endif + +#ifdef PLUGIN_010 + Plugin_id[x] = 10; Plugin_ptr[x++] = &Plugin_010; +#endif + +#ifdef PLUGIN_011 + Plugin_id[x] = 11; Plugin_ptr[x++] = &Plugin_011; +#endif + +#ifdef PLUGIN_012 + Plugin_id[x] = 12; Plugin_ptr[x++] = &Plugin_012; +#endif + +#ifdef PLUGIN_013 + Plugin_id[x] = 13; Plugin_ptr[x++] = &Plugin_013; +#endif + +#ifdef PLUGIN_014 + Plugin_id[x] = 14; Plugin_ptr[x++] = &Plugin_014; +#endif + +#ifdef PLUGIN_015 + Plugin_id[x] = 15; Plugin_ptr[x++] = &Plugin_015; +#endif + +#ifdef PLUGIN_016 + Plugin_id[x] = 16; Plugin_ptr[x++] = &Plugin_016; +#endif + +#ifdef PLUGIN_017 + Plugin_id[x] = 17; Plugin_ptr[x++] = &Plugin_017; +#endif + +#ifdef PLUGIN_018 + Plugin_id[x] = 18; Plugin_ptr[x++] = &Plugin_018; +#endif + +#ifdef PLUGIN_019 + Plugin_id[x] = 19; Plugin_ptr[x++] = &Plugin_019; +#endif + +#ifdef PLUGIN_020 + Plugin_id[x] = 20; Plugin_ptr[x++] = &Plugin_020; +#endif + +#ifdef PLUGIN_021 + Plugin_id[x] = 21; Plugin_ptr[x++] = &Plugin_021; +#endif + +#ifdef PLUGIN_022 + Plugin_id[x] = 22; Plugin_ptr[x++] = &Plugin_022; +#endif + +#ifdef PLUGIN_023 + Plugin_id[x] = 23; Plugin_ptr[x++] = &Plugin_023; +#endif + +#ifdef PLUGIN_024 + Plugin_id[x] = 24; Plugin_ptr[x++] = &Plugin_024; +#endif + +#ifdef PLUGIN_025 + Plugin_id[x] = 25; Plugin_ptr[x++] = &Plugin_025; +#endif + +#ifdef PLUGIN_026 + Plugin_id[x] = 26; Plugin_ptr[x++] = &Plugin_026; +#endif + +#ifdef PLUGIN_027 + Plugin_id[x] = 27; Plugin_ptr[x++] = &Plugin_027; +#endif + +#ifdef PLUGIN_028 + Plugin_id[x] = 28; Plugin_ptr[x++] = &Plugin_028; +#endif + +#ifdef PLUGIN_029 + Plugin_id[x] = 29; Plugin_ptr[x++] = &Plugin_029; +#endif + +#ifdef PLUGIN_030 + Plugin_id[x] = 30; Plugin_ptr[x++] = &Plugin_030; +#endif + +#ifdef PLUGIN_031 + Plugin_id[x] = 31; Plugin_ptr[x++] = &Plugin_031; +#endif + +#ifdef PLUGIN_032 + Plugin_id[x] = 32; Plugin_ptr[x++] = &Plugin_032; +#endif + +#ifdef PLUGIN_033 + Plugin_id[x] = 33; Plugin_ptr[x++] = &Plugin_033; +#endif + +#ifdef PLUGIN_034 + Plugin_id[x] = 34; Plugin_ptr[x++] = &Plugin_034; +#endif + +#ifdef PLUGIN_035 + Plugin_id[x] = 35; Plugin_ptr[x++] = &Plugin_035; +#endif + +#ifdef PLUGIN_036 + Plugin_id[x] = 36; Plugin_ptr[x++] = &Plugin_036; +#endif + +#ifdef PLUGIN_037 + Plugin_id[x] = 37; Plugin_ptr[x++] = &Plugin_037; +#endif + +#ifdef PLUGIN_038 + Plugin_id[x] = 38; Plugin_ptr[x++] = &Plugin_038; +#endif + +#ifdef PLUGIN_039 + Plugin_id[x] = 39; Plugin_ptr[x++] = &Plugin_039; +#endif + +#ifdef PLUGIN_040 + Plugin_id[x] = 40; Plugin_ptr[x++] = &Plugin_040; +#endif + +#ifdef PLUGIN_041 + Plugin_id[x] = 41; Plugin_ptr[x++] = &Plugin_041; +#endif + +#ifdef PLUGIN_042 + Plugin_id[x] = 42; Plugin_ptr[x++] = &Plugin_042; +#endif + +#ifdef PLUGIN_043 + Plugin_id[x] = 43; Plugin_ptr[x++] = &Plugin_043; +#endif + +#ifdef PLUGIN_044 + Plugin_id[x] = 44; Plugin_ptr[x++] = &Plugin_044; +#endif + +#ifdef PLUGIN_045 + Plugin_id[x] = 45; Plugin_ptr[x++] = &Plugin_045; +#endif + +#ifdef PLUGIN_046 + Plugin_id[x] = 46; Plugin_ptr[x++] = &Plugin_046; +#endif + +#ifdef PLUGIN_047 + Plugin_id[x] = 47; Plugin_ptr[x++] = &Plugin_047; +#endif + +#ifdef PLUGIN_048 + Plugin_id[x] = 48; Plugin_ptr[x++] = &Plugin_048; +#endif + +#ifdef PLUGIN_049 + Plugin_id[x] = 49; Plugin_ptr[x++] = &Plugin_049; +#endif + +#ifdef PLUGIN_050 + Plugin_id[x] = 50; Plugin_ptr[x++] = &Plugin_050; +#endif + +#ifdef PLUGIN_051 + Plugin_id[x] = 51; Plugin_ptr[x++] = &Plugin_051; +#endif + +#ifdef PLUGIN_052 + Plugin_id[x] = 52; Plugin_ptr[x++] = &Plugin_052; +#endif + +#ifdef PLUGIN_053 + Plugin_id[x] = 53; Plugin_ptr[x++] = &Plugin_053; +#endif + +#ifdef PLUGIN_054 + Plugin_id[x] = 54; Plugin_ptr[x++] = &Plugin_054; +#endif + +#ifdef PLUGIN_055 + Plugin_id[x] = 55; Plugin_ptr[x++] = &Plugin_055; +#endif + +#ifdef PLUGIN_056 + Plugin_id[x] = 56; Plugin_ptr[x++] = &Plugin_056; +#endif + +#ifdef PLUGIN_057 + Plugin_id[x] = 57; Plugin_ptr[x++] = &Plugin_057; +#endif + +#ifdef PLUGIN_058 + Plugin_id[x] = 58; Plugin_ptr[x++] = &Plugin_058; +#endif + +#ifdef PLUGIN_059 + Plugin_id[x] = 59; Plugin_ptr[x++] = &Plugin_059; +#endif + +#ifdef PLUGIN_060 + Plugin_id[x] = 60; Plugin_ptr[x++] = &Plugin_060; +#endif + +#ifdef PLUGIN_061 + Plugin_id[x] = 61; Plugin_ptr[x++] = &Plugin_061; +#endif + +#ifdef PLUGIN_062 + Plugin_id[x] = 62; Plugin_ptr[x++] = &Plugin_062; +#endif + +#ifdef PLUGIN_063 + Plugin_id[x] = 63; Plugin_ptr[x++] = &Plugin_063; +#endif + +#ifdef PLUGIN_064 + Plugin_id[x] = 64; Plugin_ptr[x++] = &Plugin_064; +#endif + +#ifdef PLUGIN_065 + Plugin_id[x] = 65; Plugin_ptr[x++] = &Plugin_065; +#endif + +#ifdef PLUGIN_066 + Plugin_id[x] = 66; Plugin_ptr[x++] = &Plugin_066; +#endif + +#ifdef PLUGIN_067 + Plugin_id[x] = 67; Plugin_ptr[x++] = &Plugin_067; +#endif + +#ifdef PLUGIN_068 + Plugin_id[x] = 68; Plugin_ptr[x++] = &Plugin_068; +#endif + +#ifdef PLUGIN_069 + Plugin_id[x] = 69; Plugin_ptr[x++] = &Plugin_069; +#endif + +#ifdef PLUGIN_070 + Plugin_id[x] = 70; Plugin_ptr[x++] = &Plugin_070; +#endif + +#ifdef PLUGIN_071 + Plugin_id[x] = 71; Plugin_ptr[x++] = &Plugin_071; +#endif + +#ifdef PLUGIN_072 + Plugin_id[x] = 72; Plugin_ptr[x++] = &Plugin_072; +#endif + +#ifdef PLUGIN_073 + Plugin_id[x] = 73; Plugin_ptr[x++] = &Plugin_073; +#endif + +#ifdef PLUGIN_074 + Plugin_id[x] = 74; Plugin_ptr[x++] = &Plugin_074; +#endif + +#ifdef PLUGIN_075 + Plugin_id[x] = 75; Plugin_ptr[x++] = &Plugin_075; +#endif + +#ifdef PLUGIN_076 + Plugin_id[x] = 76; Plugin_ptr[x++] = &Plugin_076; +#endif + +#ifdef PLUGIN_077 + Plugin_id[x] = 77; Plugin_ptr[x++] = &Plugin_077; +#endif + +#ifdef PLUGIN_078 + Plugin_id[x] = 78; Plugin_ptr[x++] = &Plugin_078; +#endif + +#ifdef PLUGIN_079 + Plugin_id[x] = 79; Plugin_ptr[x++] = &Plugin_079; +#endif + +#ifdef PLUGIN_080 + Plugin_id[x] = 80; Plugin_ptr[x++] = &Plugin_080; +#endif + +#ifdef PLUGIN_081 + Plugin_id[x] = 81; Plugin_ptr[x++] = &Plugin_081; +#endif + +#ifdef PLUGIN_082 + Plugin_id[x] = 82; Plugin_ptr[x++] = &Plugin_082; +#endif + +#ifdef PLUGIN_083 + Plugin_id[x] = 83; Plugin_ptr[x++] = &Plugin_083; +#endif + +#ifdef PLUGIN_084 + Plugin_id[x] = 84; Plugin_ptr[x++] = &Plugin_084; +#endif + +#ifdef PLUGIN_085 + Plugin_id[x] = 85; Plugin_ptr[x++] = &Plugin_085; +#endif + +#ifdef PLUGIN_086 + Plugin_id[x] = 86; Plugin_ptr[x++] = &Plugin_086; +#endif + +#ifdef PLUGIN_087 + Plugin_id[x] = 87; Plugin_ptr[x++] = &Plugin_087; +#endif + +#ifdef PLUGIN_088 + Plugin_id[x] = 88; Plugin_ptr[x++] = &Plugin_088; +#endif + +#ifdef PLUGIN_089 + Plugin_id[x] = 89; Plugin_ptr[x++] = &Plugin_089; +#endif + +#ifdef PLUGIN_090 + Plugin_id[x] = 90; Plugin_ptr[x++] = &Plugin_090; +#endif + +#ifdef PLUGIN_091 + Plugin_id[x] = 91; Plugin_ptr[x++] = &Plugin_091; +#endif + +#ifdef PLUGIN_092 + Plugin_id[x] = 92; Plugin_ptr[x++] = &Plugin_092; +#endif + +#ifdef PLUGIN_093 + Plugin_id[x] = 93; Plugin_ptr[x++] = &Plugin_093; +#endif + +#ifdef PLUGIN_094 + Plugin_id[x] = 94; Plugin_ptr[x++] = &Plugin_094; +#endif + +#ifdef PLUGIN_095 + Plugin_id[x] = 95; Plugin_ptr[x++] = &Plugin_095; +#endif + +#ifdef PLUGIN_096 + Plugin_id[x] = 96; Plugin_ptr[x++] = &Plugin_096; +#endif + +#ifdef PLUGIN_097 + Plugin_id[x] = 97; Plugin_ptr[x++] = &Plugin_097; +#endif + +#ifdef PLUGIN_098 + Plugin_id[x] = 98; Plugin_ptr[x++] = &Plugin_098; +#endif + +#ifdef PLUGIN_099 + Plugin_id[x] = 99; Plugin_ptr[x++] = &Plugin_099; +#endif + +#ifdef PLUGIN_100 + Plugin_id[x] = 100; Plugin_ptr[x++] = &Plugin_100; +#endif + +#ifdef PLUGIN_101 + Plugin_id[x] = 101; Plugin_ptr[x++] = &Plugin_101; +#endif + +#ifdef PLUGIN_102 + Plugin_id[x] = 102; Plugin_ptr[x++] = &Plugin_102; +#endif + +#ifdef PLUGIN_103 + Plugin_id[x] = 103; Plugin_ptr[x++] = &Plugin_103; +#endif + +#ifdef PLUGIN_104 + Plugin_id[x] = 104; Plugin_ptr[x++] = &Plugin_104; +#endif + +#ifdef PLUGIN_105 + Plugin_id[x] = 105; Plugin_ptr[x++] = &Plugin_105; +#endif + +#ifdef PLUGIN_106 + Plugin_id[x] = 106; Plugin_ptr[x++] = &Plugin_106; +#endif + +#ifdef PLUGIN_107 + Plugin_id[x] = 107; Plugin_ptr[x++] = &Plugin_107; +#endif + +#ifdef PLUGIN_108 + Plugin_id[x] = 108; Plugin_ptr[x++] = &Plugin_108; +#endif + +#ifdef PLUGIN_109 + Plugin_id[x] = 109; Plugin_ptr[x++] = &Plugin_109; +#endif + +#ifdef PLUGIN_110 + Plugin_id[x] = 110; Plugin_ptr[x++] = &Plugin_110; +#endif + +#ifdef PLUGIN_111 + Plugin_id[x] = 111; Plugin_ptr[x++] = &Plugin_111; +#endif + +#ifdef PLUGIN_112 + Plugin_id[x] = 112; Plugin_ptr[x++] = &Plugin_112; +#endif + +#ifdef PLUGIN_113 + Plugin_id[x] = 113; Plugin_ptr[x++] = &Plugin_113; +#endif + +#ifdef PLUGIN_114 + Plugin_id[x] = 114; Plugin_ptr[x++] = &Plugin_114; +#endif + +#ifdef PLUGIN_115 + Plugin_id[x] = 115; Plugin_ptr[x++] = &Plugin_115; +#endif + +#ifdef PLUGIN_116 + Plugin_id[x] = 116; Plugin_ptr[x++] = &Plugin_116; +#endif + +#ifdef PLUGIN_117 + Plugin_id[x] = 117; Plugin_ptr[x++] = &Plugin_117; +#endif + +#ifdef PLUGIN_118 + Plugin_id[x] = 118; Plugin_ptr[x++] = &Plugin_118; +#endif + +#ifdef PLUGIN_119 + Plugin_id[x] = 119; Plugin_ptr[x++] = &Plugin_119; +#endif + +#ifdef PLUGIN_120 + Plugin_id[x] = 120; Plugin_ptr[x++] = &Plugin_120; +#endif + +#ifdef PLUGIN_121 + Plugin_id[x] = 121; Plugin_ptr[x++] = &Plugin_121; +#endif + +#ifdef PLUGIN_122 + Plugin_id[x] = 122; Plugin_ptr[x++] = &Plugin_122; +#endif + +#ifdef PLUGIN_123 + Plugin_id[x] = 123; Plugin_ptr[x++] = &Plugin_123; +#endif + +#ifdef PLUGIN_124 + Plugin_id[x] = 124; Plugin_ptr[x++] = &Plugin_124; +#endif + +#ifdef PLUGIN_125 + Plugin_id[x] = 125; Plugin_ptr[x++] = &Plugin_125; +#endif + +#ifdef PLUGIN_126 + Plugin_id[x] = 126; Plugin_ptr[x++] = &Plugin_126; +#endif + +#ifdef PLUGIN_127 + Plugin_id[x] = 127; Plugin_ptr[x++] = &Plugin_127; +#endif + +#ifdef PLUGIN_128 + Plugin_id[x] = 128; Plugin_ptr[x++] = &Plugin_128; +#endif + +#ifdef PLUGIN_129 + Plugin_id[x] = 129; Plugin_ptr[x++] = &Plugin_129; +#endif + +#ifdef PLUGIN_130 + Plugin_id[x] = 130; Plugin_ptr[x++] = &Plugin_130; +#endif + +#ifdef PLUGIN_131 + Plugin_id[x] = 131; Plugin_ptr[x++] = &Plugin_131; +#endif + +#ifdef PLUGIN_132 + Plugin_id[x] = 132; Plugin_ptr[x++] = &Plugin_132; +#endif + +#ifdef PLUGIN_133 + Plugin_id[x] = 133; Plugin_ptr[x++] = &Plugin_133; +#endif + +#ifdef PLUGIN_134 + Plugin_id[x] = 134; Plugin_ptr[x++] = &Plugin_134; +#endif + +#ifdef PLUGIN_135 + Plugin_id[x] = 135; Plugin_ptr[x++] = &Plugin_135; +#endif + +#ifdef PLUGIN_136 + Plugin_id[x] = 136; Plugin_ptr[x++] = &Plugin_136; +#endif + +#ifdef PLUGIN_137 + Plugin_id[x] = 137; Plugin_ptr[x++] = &Plugin_137; +#endif + +#ifdef PLUGIN_138 + Plugin_id[x] = 138; Plugin_ptr[x++] = &Plugin_138; +#endif + +#ifdef PLUGIN_139 + Plugin_id[x] = 139; Plugin_ptr[x++] = &Plugin_139; +#endif + +#ifdef PLUGIN_140 + Plugin_id[x] = 140; Plugin_ptr[x++] = &Plugin_140; +#endif + +#ifdef PLUGIN_141 + Plugin_id[x] = 141; Plugin_ptr[x++] = &Plugin_141; +#endif + +#ifdef PLUGIN_142 + Plugin_id[x] = 142; Plugin_ptr[x++] = &Plugin_142; +#endif + +#ifdef PLUGIN_143 + Plugin_id[x] = 143; Plugin_ptr[x++] = &Plugin_143; +#endif + +#ifdef PLUGIN_144 + Plugin_id[x] = 144; Plugin_ptr[x++] = &Plugin_144; +#endif + +#ifdef PLUGIN_145 + Plugin_id[x] = 145; Plugin_ptr[x++] = &Plugin_145; +#endif + +#ifdef PLUGIN_146 + Plugin_id[x] = 146; Plugin_ptr[x++] = &Plugin_146; +#endif + +#ifdef PLUGIN_147 + Plugin_id[x] = 147; Plugin_ptr[x++] = &Plugin_147; +#endif + +#ifdef PLUGIN_148 + Plugin_id[x] = 148; Plugin_ptr[x++] = &Plugin_148; +#endif + +#ifdef PLUGIN_149 + Plugin_id[x] = 149; Plugin_ptr[x++] = &Plugin_149; +#endif + +#ifdef PLUGIN_150 + Plugin_id[x] = 150; Plugin_ptr[x++] = &Plugin_150; +#endif + +#ifdef PLUGIN_151 + Plugin_id[x] = 151; Plugin_ptr[x++] = &Plugin_151; +#endif + +#ifdef PLUGIN_152 + Plugin_id[x] = 152; Plugin_ptr[x++] = &Plugin_152; +#endif + +#ifdef PLUGIN_153 + Plugin_id[x] = 153; Plugin_ptr[x++] = &Plugin_153; +#endif + +#ifdef PLUGIN_154 + Plugin_id[x] = 154; Plugin_ptr[x++] = &Plugin_154; +#endif + +#ifdef PLUGIN_155 + Plugin_id[x] = 155; Plugin_ptr[x++] = &Plugin_155; +#endif + +#ifdef PLUGIN_156 + Plugin_id[x] = 156; Plugin_ptr[x++] = &Plugin_156; +#endif + +#ifdef PLUGIN_157 + Plugin_id[x] = 157; Plugin_ptr[x++] = &Plugin_157; +#endif + +#ifdef PLUGIN_158 + Plugin_id[x] = 158; Plugin_ptr[x++] = &Plugin_158; +#endif + +#ifdef PLUGIN_159 + Plugin_id[x] = 159; Plugin_ptr[x++] = &Plugin_159; +#endif + +#ifdef PLUGIN_160 + Plugin_id[x] = 160; Plugin_ptr[x++] = &Plugin_160; +#endif + +#ifdef PLUGIN_161 + Plugin_id[x] = 161; Plugin_ptr[x++] = &Plugin_161; +#endif + +#ifdef PLUGIN_162 + Plugin_id[x] = 162; Plugin_ptr[x++] = &Plugin_162; +#endif + +#ifdef PLUGIN_163 + Plugin_id[x] = 163; Plugin_ptr[x++] = &Plugin_163; +#endif + +#ifdef PLUGIN_164 + Plugin_id[x] = 164; Plugin_ptr[x++] = &Plugin_164; +#endif + +#ifdef PLUGIN_165 + Plugin_id[x] = 165; Plugin_ptr[x++] = &Plugin_165; +#endif + +#ifdef PLUGIN_166 + Plugin_id[x] = 166; Plugin_ptr[x++] = &Plugin_166; +#endif + +#ifdef PLUGIN_167 + Plugin_id[x] = 167; Plugin_ptr[x++] = &Plugin_167; +#endif + +#ifdef PLUGIN_168 + Plugin_id[x] = 168; Plugin_ptr[x++] = &Plugin_168; +#endif + +#ifdef PLUGIN_169 + Plugin_id[x] = 169; Plugin_ptr[x++] = &Plugin_169; +#endif + +#ifdef PLUGIN_170 + Plugin_id[x] = 170; Plugin_ptr[x++] = &Plugin_170; +#endif + +#ifdef PLUGIN_171 + Plugin_id[x] = 171; Plugin_ptr[x++] = &Plugin_171; +#endif + +#ifdef PLUGIN_172 + Plugin_id[x] = 172; Plugin_ptr[x++] = &Plugin_172; +#endif + +#ifdef PLUGIN_173 + Plugin_id[x] = 173; Plugin_ptr[x++] = &Plugin_173; +#endif + +#ifdef PLUGIN_174 + Plugin_id[x] = 174; Plugin_ptr[x++] = &Plugin_174; +#endif + +#ifdef PLUGIN_175 + Plugin_id[x] = 175; Plugin_ptr[x++] = &Plugin_175; +#endif + +#ifdef PLUGIN_176 + Plugin_id[x] = 176; Plugin_ptr[x++] = &Plugin_176; +#endif + +#ifdef PLUGIN_177 + Plugin_id[x] = 177; Plugin_ptr[x++] = &Plugin_177; +#endif + +#ifdef PLUGIN_178 + Plugin_id[x] = 178; Plugin_ptr[x++] = &Plugin_178; +#endif + +#ifdef PLUGIN_179 + Plugin_id[x] = 179; Plugin_ptr[x++] = &Plugin_179; +#endif + +#ifdef PLUGIN_180 + Plugin_id[x] = 180; Plugin_ptr[x++] = &Plugin_180; +#endif + +#ifdef PLUGIN_181 + Plugin_id[x] = 181; Plugin_ptr[x++] = &Plugin_181; +#endif + +#ifdef PLUGIN_182 + Plugin_id[x] = 182; Plugin_ptr[x++] = &Plugin_182; +#endif + +#ifdef PLUGIN_183 + Plugin_id[x] = 183; Plugin_ptr[x++] = &Plugin_183; +#endif + +#ifdef PLUGIN_184 + Plugin_id[x] = 184; Plugin_ptr[x++] = &Plugin_184; +#endif + +#ifdef PLUGIN_185 + Plugin_id[x] = 185; Plugin_ptr[x++] = &Plugin_185; +#endif + +#ifdef PLUGIN_186 + Plugin_id[x] = 186; Plugin_ptr[x++] = &Plugin_186; +#endif + +#ifdef PLUGIN_187 + Plugin_id[x] = 187; Plugin_ptr[x++] = &Plugin_187; +#endif + +#ifdef PLUGIN_188 + Plugin_id[x] = 188; Plugin_ptr[x++] = &Plugin_188; +#endif + +#ifdef PLUGIN_189 + Plugin_id[x] = 189; Plugin_ptr[x++] = &Plugin_189; +#endif + +#ifdef PLUGIN_190 + Plugin_id[x] = 190; Plugin_ptr[x++] = &Plugin_190; +#endif + +#ifdef PLUGIN_191 + Plugin_id[x] = 191; Plugin_ptr[x++] = &Plugin_191; +#endif + +#ifdef PLUGIN_192 + Plugin_id[x] = 192; Plugin_ptr[x++] = &Plugin_192; +#endif + +#ifdef PLUGIN_193 + Plugin_id[x] = 193; Plugin_ptr[x++] = &Plugin_193; +#endif + +#ifdef PLUGIN_194 + Plugin_id[x] = 194; Plugin_ptr[x++] = &Plugin_194; +#endif + +#ifdef PLUGIN_195 + Plugin_id[x] = 195; Plugin_ptr[x++] = &Plugin_195; +#endif + +#ifdef PLUGIN_196 + Plugin_id[x] = 196; Plugin_ptr[x++] = &Plugin_196; +#endif + +#ifdef PLUGIN_197 + Plugin_id[x] = 197; Plugin_ptr[x++] = &Plugin_197; +#endif + +#ifdef PLUGIN_198 + Plugin_id[x] = 198; Plugin_ptr[x++] = &Plugin_198; +#endif + +#ifdef PLUGIN_199 + Plugin_id[x] = 199; Plugin_ptr[x++] = &Plugin_199; +#endif + +#ifdef PLUGIN_200 + Plugin_id[x] = 200; Plugin_ptr[x++] = &Plugin_200; +#endif + +#ifdef PLUGIN_201 + Plugin_id[x] = 201; Plugin_ptr[x++] = &Plugin_201; +#endif + +#ifdef PLUGIN_202 + Plugin_id[x] = 202; Plugin_ptr[x++] = &Plugin_202; +#endif + +#ifdef PLUGIN_203 + Plugin_id[x] = 203; Plugin_ptr[x++] = &Plugin_203; +#endif + +#ifdef PLUGIN_204 + Plugin_id[x] = 204; Plugin_ptr[x++] = &Plugin_204; +#endif + +#ifdef PLUGIN_205 + Plugin_id[x] = 205; Plugin_ptr[x++] = &Plugin_205; +#endif + +#ifdef PLUGIN_206 + Plugin_id[x] = 206; Plugin_ptr[x++] = &Plugin_206; +#endif + +#ifdef PLUGIN_207 + Plugin_id[x] = 207; Plugin_ptr[x++] = &Plugin_207; +#endif + +#ifdef PLUGIN_208 + Plugin_id[x] = 208; Plugin_ptr[x++] = &Plugin_208; +#endif + +#ifdef PLUGIN_209 + Plugin_id[x] = 209; Plugin_ptr[x++] = &Plugin_209; +#endif + +#ifdef PLUGIN_210 + Plugin_id[x] = 210; Plugin_ptr[x++] = &Plugin_210; +#endif + +#ifdef PLUGIN_211 + Plugin_id[x] = 211; Plugin_ptr[x++] = &Plugin_211; +#endif + +#ifdef PLUGIN_212 + Plugin_id[x] = 212; Plugin_ptr[x++] = &Plugin_212; +#endif + +#ifdef PLUGIN_213 + Plugin_id[x] = 213; Plugin_ptr[x++] = &Plugin_213; +#endif + +#ifdef PLUGIN_214 + Plugin_id[x] = 214; Plugin_ptr[x++] = &Plugin_214; +#endif + +#ifdef PLUGIN_215 + Plugin_id[x] = 215; Plugin_ptr[x++] = &Plugin_215; +#endif + +#ifdef PLUGIN_216 + Plugin_id[x] = 216; Plugin_ptr[x++] = &Plugin_216; +#endif + +#ifdef PLUGIN_217 + Plugin_id[x] = 217; Plugin_ptr[x++] = &Plugin_217; +#endif + +#ifdef PLUGIN_218 + Plugin_id[x] = 218; Plugin_ptr[x++] = &Plugin_218; +#endif + +#ifdef PLUGIN_219 + Plugin_id[x] = 219; Plugin_ptr[x++] = &Plugin_219; +#endif + +#ifdef PLUGIN_220 + Plugin_id[x] = 220; Plugin_ptr[x++] = &Plugin_220; +#endif + +#ifdef PLUGIN_221 + Plugin_id[x] = 221; Plugin_ptr[x++] = &Plugin_221; +#endif + +#ifdef PLUGIN_222 + Plugin_id[x] = 222; Plugin_ptr[x++] = &Plugin_222; +#endif + +#ifdef PLUGIN_223 + Plugin_id[x] = 223; Plugin_ptr[x++] = &Plugin_223; +#endif + +#ifdef PLUGIN_224 + Plugin_id[x] = 224; Plugin_ptr[x++] = &Plugin_224; +#endif + +#ifdef PLUGIN_225 + Plugin_id[x] = 225; Plugin_ptr[x++] = &Plugin_225; +#endif + +#ifdef PLUGIN_226 + Plugin_id[x] = 226; Plugin_ptr[x++] = &Plugin_226; +#endif + +#ifdef PLUGIN_227 + Plugin_id[x] = 227; Plugin_ptr[x++] = &Plugin_227; +#endif + +#ifdef PLUGIN_228 + Plugin_id[x] = 228; Plugin_ptr[x++] = &Plugin_228; +#endif + +#ifdef PLUGIN_229 + Plugin_id[x] = 229; Plugin_ptr[x++] = &Plugin_229; +#endif + +#ifdef PLUGIN_230 + Plugin_id[x] = 230; Plugin_ptr[x++] = &Plugin_230; +#endif + +#ifdef PLUGIN_231 + Plugin_id[x] = 231; Plugin_ptr[x++] = &Plugin_231; +#endif + +#ifdef PLUGIN_232 + Plugin_id[x] = 232; Plugin_ptr[x++] = &Plugin_232; +#endif + +#ifdef PLUGIN_233 + Plugin_id[x] = 233; Plugin_ptr[x++] = &Plugin_233; +#endif + +#ifdef PLUGIN_234 + Plugin_id[x] = 234; Plugin_ptr[x++] = &Plugin_234; +#endif + +#ifdef PLUGIN_235 + Plugin_id[x] = 235; Plugin_ptr[x++] = &Plugin_235; +#endif + +#ifdef PLUGIN_236 + Plugin_id[x] = 236; Plugin_ptr[x++] = &Plugin_236; +#endif + +#ifdef PLUGIN_237 + Plugin_id[x] = 237; Plugin_ptr[x++] = &Plugin_237; +#endif + +#ifdef PLUGIN_238 + Plugin_id[x] = 238; Plugin_ptr[x++] = &Plugin_238; +#endif + +#ifdef PLUGIN_239 + Plugin_id[x] = 239; Plugin_ptr[x++] = &Plugin_239; +#endif + +#ifdef PLUGIN_240 + Plugin_id[x] = 240; Plugin_ptr[x++] = &Plugin_240; +#endif + +#ifdef PLUGIN_241 + Plugin_id[x] = 241; Plugin_ptr[x++] = &Plugin_241; +#endif + +#ifdef PLUGIN_242 + Plugin_id[x] = 242; Plugin_ptr[x++] = &Plugin_242; +#endif + +#ifdef PLUGIN_243 + Plugin_id[x] = 243; Plugin_ptr[x++] = &Plugin_243; +#endif + +#ifdef PLUGIN_244 + Plugin_id[x] = 244; Plugin_ptr[x++] = &Plugin_244; +#endif + +#ifdef PLUGIN_245 + Plugin_id[x] = 245; Plugin_ptr[x++] = &Plugin_245; +#endif + +#ifdef PLUGIN_246 + Plugin_id[x] = 246; Plugin_ptr[x++] = &Plugin_246; +#endif + +#ifdef PLUGIN_247 + Plugin_id[x] = 247; Plugin_ptr[x++] = &Plugin_247; +#endif + +#ifdef PLUGIN_248 + Plugin_id[x] = 248; Plugin_ptr[x++] = &Plugin_248; +#endif + +#ifdef PLUGIN_249 + Plugin_id[x] = 249; Plugin_ptr[x++] = &Plugin_249; +#endif + +#ifdef PLUGIN_250 + Plugin_id[x] = 250; Plugin_ptr[x++] = &Plugin_250; +#endif + +#ifdef PLUGIN_251 + Plugin_id[x] = 251; Plugin_ptr[x++] = &Plugin_251; +#endif + +#ifdef PLUGIN_252 + Plugin_id[x] = 252; Plugin_ptr[x++] = &Plugin_252; +#endif + +#ifdef PLUGIN_253 + Plugin_id[x] = 253; Plugin_ptr[x++] = &Plugin_253; +#endif + +#ifdef PLUGIN_254 + Plugin_id[x] = 254; Plugin_ptr[x++] = &Plugin_254; +#endif + +#ifdef PLUGIN_255 + Plugin_id[x] = 255; Plugin_ptr[x++] = &Plugin_255; +#endif + + PluginCall(PLUGIN_DEVICE_ADD, 0, dummyString); + PluginCall(PLUGIN_INIT_ALL, 0, dummyString); + +} + + +/*********************************************************************************************\ +* Function call to all or specific plugins +\*********************************************************************************************/ +byte PluginCall(byte Function, struct EventStruct *event, String& str) +{ + int x; + struct EventStruct TempEvent; + + if (event == 0) + event = &TempEvent; + + switch (Function) + { + // Unconditional calls to all plugins + case PLUGIN_DEVICE_ADD: + for (x = 0; x < PLUGIN_MAX; x++) + if (Plugin_id[x] != 0) + Plugin_ptr[x](Function, event, str); + return true; + break; + + // Call to all plugins. Return at first match + case PLUGIN_WRITE: + for (x = 0; x < PLUGIN_MAX; x++) + if (Plugin_id[x] != 0) + if (Plugin_ptr[x](Function, event, str)) + return true; + break; + + // Call to all plugins used in a task. Return at first match + case PLUGIN_SERIAL_IN: + case PLUGIN_UDP_IN: + { + for (byte y = 0; y < TASKS_MAX; y++) + { + if (Settings.TaskDeviceNumber[y] != 0) + { + for (x = 0; x < PLUGIN_MAX; x++) + { + if (Plugin_id[x] == Settings.TaskDeviceNumber[y]) + { + byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[y]); + TempEvent.TaskIndex = y; + TempEvent.BaseVarIndex = y * VARS_PER_TASK; + TempEvent.idx = Settings.TaskDeviceID[y]; + TempEvent.sensorType = Device[DeviceIndex].VType; + if (Plugin_ptr[x](Function, event, str)) + return true; + } + } + } + } + return false; + break; + } + + // Call to all plugins that are used in a task + case PLUGIN_ONCE_A_SECOND: + case PLUGIN_TEN_PER_SECOND: + case PLUGIN_INIT_ALL: + case PLUGIN_CLOCK_IN: + case PLUGIN_EVENT_OUT: + { + if (Function == PLUGIN_INIT_ALL) + Function = PLUGIN_INIT; + for (byte y = 0; y < TASKS_MAX; y++) + { + if (Settings.TaskDeviceNumber[y] != 0) + { + if (Settings.TaskDeviceDataFeed[y] == 0) // these calls only to tasks with local feed + { + byte DeviceIndex = getDeviceIndex(Settings.TaskDeviceNumber[y]); + TempEvent.TaskIndex = y; + TempEvent.BaseVarIndex = y * VARS_PER_TASK; + TempEvent.idx = Settings.TaskDeviceID[y]; + TempEvent.sensorType = Device[DeviceIndex].VType; + TempEvent.OriginTaskIndex = event->TaskIndex; + for (x = 0; x < PLUGIN_MAX; x++) + { + if (Plugin_id[x] == Settings.TaskDeviceNumber[y]) + { + Plugin_ptr[x](Function, &TempEvent, str); + } + } + } + } + } + return true; + break; + } + + // Call to specific plugin that is used for current task + case PLUGIN_INIT: + case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SAVE: + case PLUGIN_WEBFORM_SHOW_VALUES: + case PLUGIN_WEBFORM_SHOW_CONFIG: + case PLUGIN_GET_DEVICEVALUENAMES: + case PLUGIN_READ: + for (x = 0; x < PLUGIN_MAX; x++) + { + if ((Plugin_id[x] != 0 ) && (Plugin_id[x] == Settings.TaskDeviceNumber[event->TaskIndex])) + { + event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; + return Plugin_ptr[x](Function, event, str); + } + } + return false; + break; + + }// case + return false; +} diff --git a/__ReleaseNotes.ino b/__ReleaseNotes.ino new file mode 100644 index 0000000..f283444 --- /dev/null +++ b/__ReleaseNotes.ino @@ -0,0 +1,13 @@ +// R147 08-12-2016 +// First alpha version (Proof Of Concept!) +// Based on ESP Easy R147 + +// Limited on several features: +// SSDP is not implemented +// No SPIFFS but using SD card instead. So it does not run without an SD card fitted! +// Reused existing UDP socket for NTP, due to shortage of W5100 sockets. + +// Known issues: +// WD message was incorrect, failures reported as freemem, freemem is missing, workaround is using string object... +// when timed reboot, system does not reboot ??? +