From 8e1f1733b05e4baf0603f6bec86ea267e23a4895 Mon Sep 17 00:00:00 2001 From: Espablo Date: Mon, 16 Nov 2020 21:41:48 +0100 Subject: [PATCH 001/233] Arduino IDE -> VSC PlatformIO --- .gitignore | 5 + platformio.ini | 42 + GUI-Generic.ino => src/GUI-Generic.ino | 408 ++-- .../GUI-Generic_Config.h | 74 +- GUIGenericCommon.h => src/GUIGenericCommon.h | 0 .../SuplaCommonPROGMEM.cpp | 0 .../SuplaCommonPROGMEM.h | 0 SuplaConfigESP.cpp => src/SuplaConfigESP.cpp | 1014 ++++----- SuplaConfigESP.h => src/SuplaConfigESP.h | 188 +- .../SuplaConfigManager.cpp | 874 ++++---- .../SuplaConfigManager.h | 316 +-- SuplaDeviceGUI.cpp => src/SuplaDeviceGUI.cpp | 326 +-- SuplaDeviceGUI.h => src/SuplaDeviceGUI.h | 162 +- SuplaGuiWiFi.h => src/SuplaGuiWiFi.h | 0 .../SuplaSensorDS18B20.cpp | 338 +-- .../SuplaSensorDS18B20.h | 118 +- .../SuplaTemplateBoard.cpp | 0 .../SuplaTemplateBoard.h | 0 .../SuplaWebPageConfig.cpp | 330 +-- .../SuplaWebPageConfig.h | 50 +- .../SuplaWebPageControl.cpp | 0 .../SuplaWebPageControl.h | 92 +- .../SuplaWebPageRelay.cpp | 566 ++--- .../SuplaWebPageRelay.h | 84 +- .../SuplaWebPageSensor.cpp | 1866 ++++++++--------- .../SuplaWebPageSensor.h | 218 +- SuplaWebServer.cpp => src/SuplaWebServer.cpp | 1266 +++++------ SuplaWebServer.h => src/SuplaWebServer.h | 194 +- {language => src/language}/en.h | 0 {language => src/language}/pl.h | 0 30 files changed, 4289 insertions(+), 4242 deletions(-) create mode 100644 .gitignore create mode 100644 platformio.ini rename GUI-Generic.ino => src/GUI-Generic.ino (97%) rename GUI-Generic_Config.h => src/GUI-Generic_Config.h (95%) rename GUIGenericCommon.h => src/GUIGenericCommon.h (100%) rename SuplaCommonPROGMEM.cpp => src/SuplaCommonPROGMEM.cpp (100%) rename SuplaCommonPROGMEM.h => src/SuplaCommonPROGMEM.h (100%) rename SuplaConfigESP.cpp => src/SuplaConfigESP.cpp (96%) rename SuplaConfigESP.h => src/SuplaConfigESP.h (96%) rename SuplaConfigManager.cpp => src/SuplaConfigManager.cpp (96%) rename SuplaConfigManager.h => src/SuplaConfigManager.h (96%) rename SuplaDeviceGUI.cpp => src/SuplaDeviceGUI.cpp (97%) rename SuplaDeviceGUI.h => src/SuplaDeviceGUI.h (96%) rename SuplaGuiWiFi.h => src/SuplaGuiWiFi.h (100%) rename SuplaSensorDS18B20.cpp => src/SuplaSensorDS18B20.cpp (96%) rename SuplaSensorDS18B20.h => src/SuplaSensorDS18B20.h (96%) rename SuplaTemplateBoard.cpp => src/SuplaTemplateBoard.cpp (100%) rename SuplaTemplateBoard.h => src/SuplaTemplateBoard.h (100%) rename SuplaWebPageConfig.cpp => src/SuplaWebPageConfig.cpp (97%) rename SuplaWebPageConfig.h => src/SuplaWebPageConfig.h (95%) rename SuplaWebPageControl.cpp => src/SuplaWebPageControl.cpp (100%) rename SuplaWebPageControl.h => src/SuplaWebPageControl.h (96%) rename SuplaWebPageRelay.cpp => src/SuplaWebPageRelay.cpp (97%) rename SuplaWebPageRelay.h => src/SuplaWebPageRelay.h (95%) rename SuplaWebPageSensor.cpp => src/SuplaWebPageSensor.cpp (97%) rename SuplaWebPageSensor.h => src/SuplaWebPageSensor.h (96%) rename SuplaWebServer.cpp => src/SuplaWebServer.cpp (96%) rename SuplaWebServer.h => src/SuplaWebServer.h (96%) rename {language => src/language}/en.h (100%) rename {language => src/language}/pl.h (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..89cc49cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 00000000..c84981a7 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,42 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp8285] +platform = espressif8266 +board = esp8285 +framework = arduino +upload_resetmethod = nodemcu +monitor_speed = 74880 +lib_deps = + milesburton/DallasTemperature@^3.9.1 + adafruit/DHT sensor library@^1.4.0 + paulstoffregen/OneWire@^2.3.5 + adafruit/Adafruit BME280 Library@^2.1.1 + datacute/DoubleResetDetector@^1.0.3 + closedcube/ClosedCube SHT31D@^1.5.1 + adafruit/Adafruit Si7021 Library@^1.3.0 + +; [env:esp12e] +; platform = espressif8266 +; board = esp12e +; framework = arduino +; ; board_build.filesystem = littlefs +; ; board_build.flash_mode = dout +; upload_resetmethod = nodemcu +; ; upload_flags = ef +; monitor_speed = 74880 +; lib_deps = +; milesburton/DallasTemperature@^3.9.1 +; adafruit/DHT sensor library@^1.4.0 +; paulstoffregen/OneWire@^2.3.5 +; adafruit/Adafruit BME280 Library@^2.1.1 +; datacute/DoubleResetDetector@^1.0.3 +; closedcube/ClosedCube SHT31D@^1.5.1 +; adafruit/Adafruit Si7021 Library@^1.3.0 diff --git a/GUI-Generic.ino b/src/GUI-Generic.ino similarity index 97% rename from GUI-Generic.ino rename to src/GUI-Generic.ino index eeed7c91..6991d08d 100644 --- a/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -1,204 +1,204 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ -#include "GUI-Generic_Config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef SUPLA_BME280 -#include -#include "SuplaWebPageSensor.h" -#endif -#ifdef SUPLA_SHT30 -#include -#endif -#ifdef SUPLA_SI7021 -#include -#endif -#ifdef SUPLA_SI7021_SONOFF -#include -#endif -#ifdef SUPLA_MAX6675 -#include -#endif - -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" - -#define DRD_TIMEOUT 5 // Number of seconds after reset during which a subseqent reset will be considered a double reset. -#define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use -DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); - -void setup() { - Serial.begin(74880); - - if (drd.detectDoubleReset()) { - drd.stop(); - ConfigESP->factoryReset(); - } - - uint8_t nr, gpio; - String key; - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) - uint8_t rollershutters = ConfigManager->get(KEY_MAX_ROLLERSHUTTER)->getValueInt(); - - if (ConfigESP->getGpio(FUNCTION_RELAY) != OFF_GPIO && ConfigManager->get(KEY_MAX_RELAY)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { -#ifdef SUPLA_ROLLERSHUTTER - if (rollershutters > 0) { -#ifdef SUPLA_BUTTON - if (ConfigESP->getLevel(nr, FUNCTION_BUTTON) == Supla::ON_CHANGE && ConfigESP->getLevel(nr + 1, FUNCTION_BUTTON) == Supla::ON_CHANGE) { - Supla::GUI::addRolleShutterMomentary(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr + 1, FUNCTION_RELAY), - ConfigESP->getGpio(nr, FUNCTION_BUTTON), ConfigESP->getGpio(nr + 1, FUNCTION_BUTTON), - ConfigESP->getLevel(nr, FUNCTION_RELAY)); - } - else { -#endif - Supla::GUI::addRolleShutter(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr + 1, FUNCTION_RELAY), - ConfigESP->getGpio(nr, FUNCTION_BUTTON), ConfigESP->getGpio(nr + 1, FUNCTION_BUTTON), - ConfigESP->getLevel(nr, FUNCTION_RELAY)); -#ifdef SUPLA_BUTTON - } -#endif - rollershutters--; - nr++; - } - else { -#endif - Supla::GUI::addRelayButton(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr, FUNCTION_BUTTON), - ConfigESP->getLevel(nr, FUNCTION_RELAY)); -#ifdef SUPLA_ROLLERSHUTTER - } -#endif - } - } -#endif - -#ifdef SUPLA_LIMIT_SWITCH - if (ConfigESP->getGpio(FUNCTION_LIMIT_SWITCH) != OFF_GPIO && ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { - new Supla::Sensor::Binary(ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH), true); - } - } -#endif - -#ifdef SUPLA_CONFIG - Supla::GUI::addConfigESP(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), ConfigESP->getGpio(FUNCTION_CFG_LED), CONFIG_MODE_10_ON_PRESSES, - ConfigESP->getLevel(FUNCTION_CFG_LED)); -#endif - -#ifdef SUPLA_DHT11 - if (ConfigESP->getGpio(FUNCTION_DHT11) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT11)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { - new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT11), DHT11); - } - } -#endif - -#ifdef SUPLA_DHT22 - if (ConfigESP->getGpio(FUNCTION_DHT22) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT22)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { - new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22); - } - } -#endif - -#ifdef SUPLA_DS18B20 - if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { - Supla::GUI::addDS18B20MultiThermometer(ConfigESP->getGpio(FUNCTION_DS18B20)); - } -#endif - -#ifdef SUPLA_SI7021_SONOFF - if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != OFF_GPIO) { - new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); - } -#endif - -#ifdef SUPLA_HC_SR04 - if (ConfigESP->getGpio(FUNCTION_TRIG) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_ECHO) != OFF_GPIO) { - new Supla::Sensor::HC_SR04(ConfigESP->getGpio(FUNCTION_TRIG), ConfigESP->getGpio(FUNCTION_ECHO)); - } -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ - defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) - if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { - Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); - -#ifdef SUPLA_BME280 - switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt()) { - case BME280_ADDRESS_0X76: - new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); - break; - case BME280_ADDRESS_0X77: - new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); - break; - case BME280_ADDRESS_0X76_AND_0X77: - new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); - new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); - break; - } -#endif - -#ifdef SUPLA_SHT30 - switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT30).toInt()) { - case SHT30_ADDRESS_0X44: - new Supla::Sensor::SHT3x(0x44); - break; - case SHT30_ADDRESS_0X45: - new Supla::Sensor::SHT3x(0x45); - break; - case SHT30_ADDRESS_0X44_AND_0X45: - new Supla::Sensor::SHT3x(0x44); - new Supla::Sensor::SHT3x(0x45); - break; - } -#endif - -#ifdef SUPLA_SI7021 - if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt()) { - new Supla::Sensor::Si7021(); - } -#endif - } -#endif - -#ifdef SUPLA_MAX6675 - if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { - new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), - ConfigESP->getGpio(FUNCTION_D0)); - } - -#endif - - Supla::GUI::begin(); -} - -void loop() { - SuplaDevice.iterate(); - delay(25); - drd.loop(); -} +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "GUI-Generic_Config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPLA_BME280 +#include +#include "SuplaWebPageSensor.h" +#endif +#ifdef SUPLA_SHT30 +#include +#endif +#ifdef SUPLA_SI7021 +#include +#endif +#ifdef SUPLA_SI7021_SONOFF +#include +#endif +#ifdef SUPLA_MAX6675 +#include +#endif + +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" + +#define DRD_TIMEOUT 5 // Number of seconds after reset during which a subseqent reset will be considered a double reset. +#define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use +DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); + +void setup() { + Serial.begin(74880); + + if (drd.detectDoubleReset()) { + drd.stop(); + ConfigESP->factoryReset(); + } + + uint8_t nr, gpio; + String key; + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) + uint8_t rollershutters = ConfigManager->get(KEY_MAX_ROLLERSHUTTER)->getValueInt(); + + if (ConfigESP->getGpio(FUNCTION_RELAY) != OFF_GPIO && ConfigManager->get(KEY_MAX_RELAY)->getValueInt() > 0) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { +#ifdef SUPLA_ROLLERSHUTTER + if (rollershutters > 0) { +#ifdef SUPLA_BUTTON + if (ConfigESP->getLevel(nr, FUNCTION_BUTTON) == Supla::ON_CHANGE && ConfigESP->getLevel(nr + 1, FUNCTION_BUTTON) == Supla::ON_CHANGE) { + Supla::GUI::addRolleShutterMomentary(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr + 1, FUNCTION_RELAY), + ConfigESP->getGpio(nr, FUNCTION_BUTTON), ConfigESP->getGpio(nr + 1, FUNCTION_BUTTON), + ConfigESP->getLevel(nr, FUNCTION_RELAY)); + } + else { +#endif + Supla::GUI::addRolleShutter(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr + 1, FUNCTION_RELAY), + ConfigESP->getGpio(nr, FUNCTION_BUTTON), ConfigESP->getGpio(nr + 1, FUNCTION_BUTTON), + ConfigESP->getLevel(nr, FUNCTION_RELAY)); +#ifdef SUPLA_BUTTON + } +#endif + rollershutters--; + nr++; + } + else { +#endif + Supla::GUI::addRelayButton(ConfigESP->getGpio(nr, FUNCTION_RELAY), ConfigESP->getGpio(nr, FUNCTION_BUTTON), + ConfigESP->getLevel(nr, FUNCTION_RELAY)); +#ifdef SUPLA_ROLLERSHUTTER + } +#endif + } + } +#endif + +#ifdef SUPLA_LIMIT_SWITCH + if (ConfigESP->getGpio(FUNCTION_LIMIT_SWITCH) != OFF_GPIO && ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt() > 0) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { + new Supla::Sensor::Binary(ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH), true); + } + } +#endif + +#ifdef SUPLA_CONFIG + Supla::GUI::addConfigESP(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), ConfigESP->getGpio(FUNCTION_CFG_LED), CONFIG_MODE_10_ON_PRESSES, + ConfigESP->getLevel(FUNCTION_CFG_LED)); +#endif + +#ifdef SUPLA_DHT11 + if (ConfigESP->getGpio(FUNCTION_DHT11) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT11)->getValueInt() > 0) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { + new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT11), DHT11); + } + } +#endif + +#ifdef SUPLA_DHT22 + if (ConfigESP->getGpio(FUNCTION_DHT22) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT22)->getValueInt() > 0) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { + new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22); + } + } +#endif + +#ifdef SUPLA_DS18B20 + if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { + Supla::GUI::addDS18B20MultiThermometer(ConfigESP->getGpio(FUNCTION_DS18B20)); + } +#endif + +#ifdef SUPLA_SI7021_SONOFF + if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != OFF_GPIO) { + new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); + } +#endif + +#ifdef SUPLA_HC_SR04 + if (ConfigESP->getGpio(FUNCTION_TRIG) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_ECHO) != OFF_GPIO) { + new Supla::Sensor::HC_SR04(ConfigESP->getGpio(FUNCTION_TRIG), ConfigESP->getGpio(FUNCTION_ECHO)); + } +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ + defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) + if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { + Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + +#ifdef SUPLA_BME280 + switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt()) { + case BME280_ADDRESS_0X76: + new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + break; + case BME280_ADDRESS_0X77: + new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + break; + case BME280_ADDRESS_0X76_AND_0X77: + new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + break; + } +#endif + +#ifdef SUPLA_SHT30 + switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT30).toInt()) { + case SHT30_ADDRESS_0X44: + new Supla::Sensor::SHT3x(0x44); + break; + case SHT30_ADDRESS_0X45: + new Supla::Sensor::SHT3x(0x45); + break; + case SHT30_ADDRESS_0X44_AND_0X45: + new Supla::Sensor::SHT3x(0x44); + new Supla::Sensor::SHT3x(0x45); + break; + } +#endif + +#ifdef SUPLA_SI7021 + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt()) { + new Supla::Sensor::Si7021(); + } +#endif + } +#endif + +#ifdef SUPLA_MAX6675 + if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { + new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), + ConfigESP->getGpio(FUNCTION_D0)); + } + +#endif + + Supla::GUI::begin(); +} + +void loop() { + SuplaDevice.iterate(); + delay(25); + drd.loop(); +} diff --git a/GUI-Generic_Config.h b/src/GUI-Generic_Config.h similarity index 95% rename from GUI-Generic_Config.h rename to src/GUI-Generic_Config.h index c7c456f4..c8210a35 100644 --- a/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -1,37 +1,37 @@ -#ifndef GUI_Generic_Config_h -#define GUI_Generic_Config_h - -// #define DEBUG_MODE - -// Language en - english, pl - polish (default if not defined UI_LANGUAGE) -// #define UI_LANGUAGE en - -#define SUPLA_RELAY -#define SUPLA_BUTTON -#define SUPLA_LIMIT_SWITCH -#define SUPLA_ROLLERSHUTTER -#define SUPLA_CONFIG - -// 1Wire -#define SUPLA_DS18B20 -#define SUPLA_DHT11 -#define SUPLA_DHT22 -#define SUPLA_SI7021_SONOFF - -// i2c -#define SUPLA_BME280 -#define SUPLA_SHT30 -#define SUPLA_SI7021 -// #define SUPLA_HTU21D // 0x40 NOT SUPPORTED -// #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED -// #define SUPLA_BH1750 // 0x23 AND 0x5C NOT SUPPORTED -// #define SUPLA_MAX44009 // A0 NOT SUPPORTED - -// SPI -#define SUPLA_MAX6675 - -// Other -#define SUPLA_HC_SR04 -#define SUPLA_IMPULSE_COUNTER // NOT SUPPORTED - -#endif // GUI-Generic_Config_h +#ifndef GUI_Generic_Config_h +#define GUI_Generic_Config_h + +// #define DEBUG_MODE + +// Language en - english, pl - polish (default if not defined UI_LANGUAGE) +// #define UI_LANGUAGE en + +#define SUPLA_RELAY +#define SUPLA_BUTTON +#define SUPLA_LIMIT_SWITCH +#define SUPLA_ROLLERSHUTTER +#define SUPLA_CONFIG + +// 1Wire +#define SUPLA_DS18B20 +#define SUPLA_DHT11 +#define SUPLA_DHT22 +#define SUPLA_SI7021_SONOFF + +// i2c +#define SUPLA_BME280 +#define SUPLA_SHT30 +#define SUPLA_SI7021 +// #define SUPLA_HTU21D // 0x40 NOT SUPPORTED +// #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED +// #define SUPLA_BH1750 // 0x23 AND 0x5C NOT SUPPORTED +// #define SUPLA_MAX44009 // A0 NOT SUPPORTED + +// SPI +#define SUPLA_MAX6675 + +// Other +#define SUPLA_HC_SR04 +#define SUPLA_IMPULSE_COUNTER // NOT SUPPORTED + +#endif // GUI-Generic_Config_h diff --git a/GUIGenericCommon.h b/src/GUIGenericCommon.h similarity index 100% rename from GUIGenericCommon.h rename to src/GUIGenericCommon.h diff --git a/SuplaCommonPROGMEM.cpp b/src/SuplaCommonPROGMEM.cpp similarity index 100% rename from SuplaCommonPROGMEM.cpp rename to src/SuplaCommonPROGMEM.cpp diff --git a/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h similarity index 100% rename from SuplaCommonPROGMEM.h rename to src/SuplaCommonPROGMEM.h diff --git a/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp similarity index 96% rename from SuplaConfigESP.cpp rename to src/SuplaConfigESP.cpp index e4e50c0b..06b6f622 100644 --- a/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -1,507 +1,507 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include - -#include "SuplaConfigESP.h" -#include "SuplaConfigManager.h" -#include "SuplaDeviceGUI.h" - -SuplaConfigESP::SuplaConfigESP() { - configModeESP = NORMAL_MODE; - - if (ConfigManager->isDeviceConfigured()) { - ConfigManager->setGUIDandAUTHKEY(); - if (String(ConfigManager->get(KEY_LOGIN)->getValue()) == 0) { - ConfigManager->set(KEY_LOGIN, DEFAULT_LOGIN); - } - if (String(ConfigManager->get(KEY_LOGIN_PASS)->getValue()) == 0) { - ConfigManager->set(KEY_LOGIN_PASS, DEFAULT_LOGIN_PASS); - } - if (String(ConfigManager->get(KEY_HOST_NAME)->getValue()) == 0) { - ConfigManager->set(KEY_HOST_NAME, DEFAULT_HOSTNAME); - } - if (String(ConfigManager->get(KEY_SUPLA_SERVER)->getValue()) == 0) { - ConfigManager->set(KEY_SUPLA_SERVER, DEFAULT_SERVER); - } - if (String(ConfigManager->get(KEY_SUPLA_EMAIL)->getValue()) == 0) { - ConfigManager->set(KEY_SUPLA_EMAIL, DEFAULT_EMAIL); - } - ConfigManager->save(); - - configModeInit(); - } - - // if(String(ConfigManager->get(KEY_WIFI_SSID)->getValue()) == 0 || - // String(ConfigManager->get(KEY_WIFI_PASS)->getValue()) == 0 || - // String(ConfigManager->get(KEY_SUPLA_SERVER)->getValue()) == - // DEFAULT_SERVER || - // String(ConfigManager->get(KEY_SUPLA_EMAIL)->getValue()) == - // DEFAULT_EMAIL){ - // configModeInit(); - // return; - // } - SuplaDevice.setStatusFuncImpl(&status_func); -} - -void SuplaConfigESP::addConfigESP(int _pinNumberConfig, int _pinLedConfig, int _modeConfigButton, bool _highIsOn) { - pinNumberConfig = _pinNumberConfig; - pinLedConfig = _pinLedConfig; - modeConfigButton = _modeConfigButton; - highIsOn = _highIsOn; - - if (pinLedConfig <= 0) { - Serial.println(F("ESP - status LED disabled")); - } - else { - pinMode(pinLedConfig, OUTPUT); - digitalWrite(pinLedConfig, pinOffValue()); - } - - Supla::Control::Button *buttonConfig = new Supla::Control::Button(pinNumberConfig, true, true); - - if (modeConfigButton == CONFIG_MODE_10_ON_PRESSES) { - buttonConfig->addAction(CONFIG_MODE_10_ON_PRESSES, *ConfigESP, Supla::ON_PRESS); - } - if (modeConfigButton == CONFIG_MODE_5SEK_HOLD) { - buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_PRESS); - buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_RELEASE); - } -} - -void SuplaConfigESP::runAction(int event, int action) { - if (action == CONFIG_MODE_10_ON_PRESSES) { - if (millis() - cnfigChangeTimeMs > 10000UL) { - cnfigChangeTimeMs = millis(); - countPresses = 0; - } - countPresses++; - - if (countPresses == 10) { - // Serial.println(F("CONFIG_MODE_3_PRESSES")); - configModeInit(); - countPresses = 0; - return; - } - } - - if (action == CONFIG_MODE_5SEK_HOLD) { - if (event == Supla::ON_PRESS) { - cnfigChangeTimeMs = millis(); - } - if (event == Supla::ON_RELEASE) { - if (millis() - cnfigChangeTimeMs > 5000UL) { - if (!digitalRead(pinNumberConfig)) { - // Serial.println(F("CONFIG_MODE_5SEK_HOLD")); - configModeInit(); - } - } - cnfigChangeTimeMs = 0; - } - } - - if (configModeESP == CONFIG_MODE) { - if (event == Supla::ON_PRESS) { - rebootESP(); - } - } -} - -void SuplaConfigESP::rebootESP() { - delay(1000); - WiFi.forceSleepBegin(); - wdt_reset(); - ESP.restart(); - while (1) wdt_reset(); -} - -void WiFiEvent(WiFiEvent_t event) { - switch (event) { - case WIFI_EVENT_STAMODE_CONNECTED: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_STAMODE_CONNECTED")); - break; - case WIFI_EVENT_STAMODE_DISCONNECTED: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WiFi lost connection")); - break; - case WIFI_EVENT_STAMODE_AUTHMODE_CHANGE: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_STAMODE_AUTHMODE_CHANGE")); - break; - case WIFI_EVENT_STAMODE_GOT_IP: - // Serial.print(millis()); - // Serial.print(" => "); - // Serial.println(F("WIFI_EVENT_STAMODE_GOT_IP")); - // Serial.println(WiFi.localIP()); - break; - case WIFI_EVENT_STAMODE_DHCP_TIMEOUT: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_STAMODE_DHCP_TIMEOUT")); - break; - case WIFI_EVENT_SOFTAPMODE_STACONNECTED: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_SOFTAPMODE_STACONNECTED")); - break; - case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_SOFTAPMODE_STADISCONNECTED")); - break; - case WIFI_EVENT_SOFTAPMODE_PROBEREQRECVED: - // Serial.print(" => "); - // Serial.println("WIFI_EVENT_SOFTAPMODE_PROBEREQRECVED")); - break; - case WIFI_EVENT_MAX: - // Serial.print(millis()); - // Serial.print(" => "); - // - // Serial.println(F("WIFI_EVENT_MAX")); - break; - } -} - -void SuplaConfigESP::configModeInit() { - configModeESP = CONFIG_MODE; - ledBlinking(100); - - WiFi.onEvent(WiFiEvent); - // Serial.print(F("Creating Access Point")); - // Serial.print(F("Setting mode ... ")); - // Serial.println(WiFi.mode(WIFI_AP_STA) ? "Ready" : "Failed!"); - WiFi.softAPdisconnect(true); - WiFi.disconnect(true); - WiFi.mode(WIFI_AP_STA); - - String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); - while (!WiFi.softAP(CONFIG_WIFI_NAME, "")) { - // Serial.println(F(".")); - delay(100); - } - - // Serial.println(F("Network Created!")); - // Serial.print(F("Soft-AP IP address = ")); - // Serial.println(WiFi.softAPIP()); -} - -const char *SuplaConfigESP::getLastStatusSupla() { - return supla_status.msg; -} - -void SuplaConfigESP::ledBlinking(int time) { - os_timer_disarm(&led_timer); - os_timer_setfn(&led_timer, ledBlinking_func, NULL); - os_timer_arm(&led_timer, time, true); -} - -void SuplaConfigESP::ledBlinkingStop(void) { - os_timer_disarm(&led_timer); - digitalWrite(pinLedConfig, pinOffValue()); -} - -int SuplaConfigESP::getPinLedConfig() { - return pinLedConfig; -} - -String SuplaConfigESP::getMacAddress(bool formating) { - byte mac[6]; - WiFi.macAddress(mac); - char baseMacChr[18] = {0}; - - if (formating) - sprintf(baseMacChr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - else - sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - - return String(baseMacChr); -} - -void ledBlinking_func(void *timer_arg) { - int val = digitalRead(ConfigESP->getPinLedConfig()); - digitalWrite(ConfigESP->getPinLedConfig(), val == HIGH ? 0 : 1); -} - -void status_func(int status, const char *msg) { - switch (status) { - case 2: - ConfigESP->supla_status.msg = "Już zainicjalizowane"; - break; - case 3: - ConfigESP->supla_status.msg = "Nie przypisane CB"; - break; - case 4: - ConfigESP->supla_status.msg = - "Nieprawidłowy identyfikator GUID lub rejestracja urządzeń " - "NIEAKTYWNA"; - break; - case 5: - ConfigESP->supla_status.msg = "Nieznany adres serwera"; - break; - case 6: - ConfigESP->supla_status.msg = "Nieznany identyfikator ID"; - break; - case 7: - ConfigESP->supla_status.msg = "Zainicjowany"; - break; - case 8: - ConfigESP->supla_status.msg = "Przekroczono limit kanału"; - break; - case 9: - ConfigESP->supla_status.msg = "Rozłączony"; - break; - case 10: - ConfigESP->supla_status.msg = "Rejestracja w toku"; - break; - case 11: - ConfigESP->supla_status.msg = "Błąd zmiennej"; - break; - case 12: - ConfigESP->supla_status.msg = "Błąd wersji protokołu"; - break; - case 13: - ConfigESP->supla_status.msg = "Złe poświadczenia"; - break; - case 14: - ConfigESP->supla_status.msg = "Tymczasowo niedostępne"; - break; - case 15: - ConfigESP->supla_status.msg = "Konflikt lokalizacji"; - break; - case 16: - ConfigESP->supla_status.msg = "Konflikt kanałów"; - break; - case 17: - ConfigESP->supla_status.msg = "Zarejestrowany i gotowy"; - break; - case 18: - ConfigESP->supla_status.msg = "Urządzenie jest rozłączone"; - break; - case 19: - ConfigESP->supla_status.msg = "Lokalizacja jest wyłączona"; - break; - case 20: - ConfigESP->supla_status.msg = "Przekroczono limit urządzeń"; - } - - static int lock; - if (status == 17 && ConfigESP->configModeESP == NORMAL_MODE) { - ConfigESP->ledBlinkingStop(); - lock = 0; - } - else if (status != 17 && lock == 0 && ConfigESP->configModeESP == NORMAL_MODE) { - ConfigESP->ledBlinking(500); - lock = 1; - } - - if (ConfigESP->supla_status.old_msg != ConfigESP->supla_status.msg) { - ConfigESP->supla_status.old_msg = ConfigESP->supla_status.msg; - ConfigESP->supla_status.status = status; - // Serial.println(ConfigESP->supla_status.msg); - } -} -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -int SuplaConfigESP::getMemoryRelay(int nr) { - uint8_t gpio; - for (gpio = 0; gpio <= OFF_GPIO; gpio++) { - String key = GPIO; - key += gpio; - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_RELAY) { - if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { - uint8_t memory = ConfigManager->get(key.c_str())->getElement(MEMORY).toInt(); - return memory; - } - } - } - return OFF_GPIO; -} -#endif - -int SuplaConfigESP::getGpio(int nr, int function) { - uint8_t gpio; - for (gpio = 0; gpio <= OFF_GPIO; gpio++) { - String key = GPIO; - key += gpio; - if (function == FUNCTION_CFG_BUTTON) { - if (checkBusyCfg(gpio)) { - return gpio; - } - } - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == function) { - if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { - return gpio; - } - } - } - return OFF_GPIO; -} - -int SuplaConfigESP::getLevel(int nr, int function) { - uint8_t gpio; - for (gpio = 0; gpio <= OFF_GPIO; gpio++) { - String key = GPIO; - key += gpio; - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == function) { - if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { - uint8_t level = ConfigManager->get(key.c_str())->getElement(LEVEL).toInt(); - return level; - } - } - } - return OFF_GPIO; -} - -bool SuplaConfigESP::checkBusyCfg(int gpio) { - String key = GPIO; - key += gpio; - if (ConfigManager->get(key.c_str())->getElement(FUNCTION_CFG_LED).toInt() == 1) { - return true; - } - return false; -} - -int SuplaConfigESP::checkBusyGpio(int gpio, int function) { - if (gpio == 6 || gpio == 7 || gpio == 8 || gpio == 11) { - return true; - } - else { - String key = GPIO; - key += gpio; - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_BUTTON) { - if (function == FUNCTION_CFG_BUTTON) { - return false; - } - } - if (checkBusyCfg(gpio)) { - if (function != FUNCTION_BUTTON) { - return true; - } - } - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() != FUNCTION_OFF) { - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() != function) { - return true; - } - } - return false; - } -} - -void SuplaConfigESP::setGpio(uint8_t gpio, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory) { - String key = GPIO; - key += gpio; - if (function == FUNCTION_CFG_BUTTON) { - ConfigManager->setElement(key.c_str(), CFG, 1); - return; - } - - ConfigManager->setElement(key.c_str(), NR, nr); - ConfigManager->setElement(key.c_str(), FUNCTION, function); - ConfigManager->setElement(key.c_str(), LEVEL, level); - ConfigManager->setElement(key.c_str(), MEMORY, memory); - - // ConfigManager->setElement(key.c_str(), MEMORY, memory); - // ConfigManager->setElement(key.c_str(), CFG, cfg); -} - -void SuplaConfigESP::clearGpio(uint8_t gpio, uint8_t function) { - String key = GPIO; - key += gpio; - if (function == FUNCTION_CFG_BUTTON) { - ConfigManager->setElement(key.c_str(), CFG, 0); - return; - } - ConfigManager->setElement(key.c_str(), NR, 0); - ConfigManager->setElement(key.c_str(), FUNCTION, FUNCTION_OFF); - ConfigManager->setElement(key.c_str(), LEVEL, 0); - ConfigManager->setElement(key.c_str(), MEMORY, 0); -} - -uint8_t SuplaConfigESP::countFreeGpio(uint8_t exception) { - uint8_t count = 0; - for (uint8_t gpio = 0; gpio < OFF_GPIO; gpio++) { - if (gpio != 6 && gpio != 7 && gpio != 8 && gpio != 11) { - String key = GPIO; - key += gpio; - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == exception) { - count++; - } - } - } - return count; -} - -void SuplaConfigESP::factoryReset() { - delay(1000); - pinMode(0, INPUT); - if (!digitalRead(0)) { - Serial.println("FACTORY RESET!!!"); - - ConfigManager->set(KEY_WIFI_SSID, ""); - ConfigManager->set(KEY_WIFI_PASS, ""); - ConfigManager->set(KEY_SUPLA_SERVER, DEFAULT_SERVER); - ConfigManager->set(KEY_SUPLA_EMAIL, DEFAULT_EMAIL); - ConfigManager->set(KEY_HOST_NAME, DEFAULT_HOSTNAME); - ConfigManager->set(KEY_LOGIN, DEFAULT_LOGIN); - ConfigManager->set(KEY_LOGIN_PASS, DEFAULT_LOGIN_PASS); - ConfigManager->set(KEY_MAX_ROLLERSHUTTER, "0"); - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); - ConfigManager->set(KEY_MAX_DHT22, "1"); - ConfigManager->set(KEY_MAX_DHT11, "1"); - ConfigManager->set(KEY_MULTI_MAX_DS18B20, "1"); - ConfigManager->set(KEY_ALTITUDE_BME280, "0"); - - int nr; - String key; - - for (nr = 0; nr <= 17; nr++) { - key = GPIO; - key += nr; - ConfigManager->set(key.c_str(), "0,0,0,0,0"); - } - - ConfigManager->set(KEY_ACTIVE_SENSOR, "0,0,0,0,0"); - - for (nr = 0; nr <= MAX_DS18B20; nr++) { - key = KEY_DS; - key += nr; - ConfigManager->set(key.c_str(), ""); - key = KEY_DS_NAME; - key += nr; - ConfigManager->set(key.c_str(), ""); - } - - ConfigManager->save(); - - delay(3000); - WiFi.forceSleepBegin(); - wdt_reset(); - ESP.restart(); - while (1) wdt_reset(); - } -} +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include "SuplaConfigESP.h" +#include "SuplaConfigManager.h" +#include "SuplaDeviceGUI.h" + +SuplaConfigESP::SuplaConfigESP() { + configModeESP = NORMAL_MODE; + + if (ConfigManager->isDeviceConfigured()) { + ConfigManager->setGUIDandAUTHKEY(); + if (String(ConfigManager->get(KEY_LOGIN)->getValue()) == 0) { + ConfigManager->set(KEY_LOGIN, DEFAULT_LOGIN); + } + if (String(ConfigManager->get(KEY_LOGIN_PASS)->getValue()) == 0) { + ConfigManager->set(KEY_LOGIN_PASS, DEFAULT_LOGIN_PASS); + } + if (String(ConfigManager->get(KEY_HOST_NAME)->getValue()) == 0) { + ConfigManager->set(KEY_HOST_NAME, DEFAULT_HOSTNAME); + } + if (String(ConfigManager->get(KEY_SUPLA_SERVER)->getValue()) == 0) { + ConfigManager->set(KEY_SUPLA_SERVER, DEFAULT_SERVER); + } + if (String(ConfigManager->get(KEY_SUPLA_EMAIL)->getValue()) == 0) { + ConfigManager->set(KEY_SUPLA_EMAIL, DEFAULT_EMAIL); + } + ConfigManager->save(); + + configModeInit(); + } + + // if(String(ConfigManager->get(KEY_WIFI_SSID)->getValue()) == 0 || + // String(ConfigManager->get(KEY_WIFI_PASS)->getValue()) == 0 || + // String(ConfigManager->get(KEY_SUPLA_SERVER)->getValue()) == + // DEFAULT_SERVER || + // String(ConfigManager->get(KEY_SUPLA_EMAIL)->getValue()) == + // DEFAULT_EMAIL){ + // configModeInit(); + // return; + // } + SuplaDevice.setStatusFuncImpl(&status_func); +} + +void SuplaConfigESP::addConfigESP(int _pinNumberConfig, int _pinLedConfig, int _modeConfigButton, bool _highIsOn) { + pinNumberConfig = _pinNumberConfig; + pinLedConfig = _pinLedConfig; + modeConfigButton = _modeConfigButton; + highIsOn = _highIsOn; + + if (pinLedConfig <= 0) { + Serial.println(F("ESP - status LED disabled")); + } + else { + pinMode(pinLedConfig, OUTPUT); + digitalWrite(pinLedConfig, pinOffValue()); + } + + Supla::Control::Button *buttonConfig = new Supla::Control::Button(pinNumberConfig, true, true); + + if (modeConfigButton == CONFIG_MODE_10_ON_PRESSES) { + buttonConfig->addAction(CONFIG_MODE_10_ON_PRESSES, *ConfigESP, Supla::ON_PRESS); + } + if (modeConfigButton == CONFIG_MODE_5SEK_HOLD) { + buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_PRESS); + buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_RELEASE); + } +} + +void SuplaConfigESP::runAction(int event, int action) { + if (action == CONFIG_MODE_10_ON_PRESSES) { + if (millis() - cnfigChangeTimeMs > 10000UL) { + cnfigChangeTimeMs = millis(); + countPresses = 0; + } + countPresses++; + + if (countPresses == 10) { + // Serial.println(F("CONFIG_MODE_3_PRESSES")); + configModeInit(); + countPresses = 0; + return; + } + } + + if (action == CONFIG_MODE_5SEK_HOLD) { + if (event == Supla::ON_PRESS) { + cnfigChangeTimeMs = millis(); + } + if (event == Supla::ON_RELEASE) { + if (millis() - cnfigChangeTimeMs > 5000UL) { + if (!digitalRead(pinNumberConfig)) { + // Serial.println(F("CONFIG_MODE_5SEK_HOLD")); + configModeInit(); + } + } + cnfigChangeTimeMs = 0; + } + } + + if (configModeESP == CONFIG_MODE) { + if (event == Supla::ON_PRESS) { + rebootESP(); + } + } +} + +void SuplaConfigESP::rebootESP() { + delay(1000); + WiFi.forceSleepBegin(); + wdt_reset(); + ESP.restart(); + while (1) wdt_reset(); +} + +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case WIFI_EVENT_STAMODE_CONNECTED: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_STAMODE_CONNECTED")); + break; + case WIFI_EVENT_STAMODE_DISCONNECTED: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WiFi lost connection")); + break; + case WIFI_EVENT_STAMODE_AUTHMODE_CHANGE: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_STAMODE_AUTHMODE_CHANGE")); + break; + case WIFI_EVENT_STAMODE_GOT_IP: + // Serial.print(millis()); + // Serial.print(" => "); + // Serial.println(F("WIFI_EVENT_STAMODE_GOT_IP")); + // Serial.println(WiFi.localIP()); + break; + case WIFI_EVENT_STAMODE_DHCP_TIMEOUT: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_STAMODE_DHCP_TIMEOUT")); + break; + case WIFI_EVENT_SOFTAPMODE_STACONNECTED: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_SOFTAPMODE_STACONNECTED")); + break; + case WIFI_EVENT_SOFTAPMODE_STADISCONNECTED: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_SOFTAPMODE_STADISCONNECTED")); + break; + case WIFI_EVENT_SOFTAPMODE_PROBEREQRECVED: + // Serial.print(" => "); + // Serial.println("WIFI_EVENT_SOFTAPMODE_PROBEREQRECVED")); + break; + case WIFI_EVENT_MAX: + // Serial.print(millis()); + // Serial.print(" => "); + // + // Serial.println(F("WIFI_EVENT_MAX")); + break; + } +} + +void SuplaConfigESP::configModeInit() { + configModeESP = CONFIG_MODE; + ledBlinking(100); + + WiFi.onEvent(WiFiEvent); + // Serial.print(F("Creating Access Point")); + // Serial.print(F("Setting mode ... ")); + // Serial.println(WiFi.mode(WIFI_AP_STA) ? "Ready" : "Failed!"); + WiFi.softAPdisconnect(true); + WiFi.disconnect(true); + WiFi.mode(WIFI_AP_STA); + + String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); + while (!WiFi.softAP(CONFIG_WIFI_NAME, "")) { + // Serial.println(F(".")); + delay(100); + } + + // Serial.println(F("Network Created!")); + // Serial.print(F("Soft-AP IP address = ")); + // Serial.println(WiFi.softAPIP()); +} + +const char *SuplaConfigESP::getLastStatusSupla() { + return supla_status.msg; +} + +void SuplaConfigESP::ledBlinking(int time) { + os_timer_disarm(&led_timer); + os_timer_setfn(&led_timer, ledBlinking_func, NULL); + os_timer_arm(&led_timer, time, true); +} + +void SuplaConfigESP::ledBlinkingStop(void) { + os_timer_disarm(&led_timer); + digitalWrite(pinLedConfig, pinOffValue()); +} + +int SuplaConfigESP::getPinLedConfig() { + return pinLedConfig; +} + +String SuplaConfigESP::getMacAddress(bool formating) { + byte mac[6]; + WiFi.macAddress(mac); + char baseMacChr[18] = {0}; + + if (formating) + sprintf(baseMacChr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + else + sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + return String(baseMacChr); +} + +void ledBlinking_func(void *timer_arg) { + int val = digitalRead(ConfigESP->getPinLedConfig()); + digitalWrite(ConfigESP->getPinLedConfig(), val == HIGH ? 0 : 1); +} + +void status_func(int status, const char *msg) { + switch (status) { + case 2: + ConfigESP->supla_status.msg = "Już zainicjalizowane"; + break; + case 3: + ConfigESP->supla_status.msg = "Nie przypisane CB"; + break; + case 4: + ConfigESP->supla_status.msg = + "Nieprawidłowy identyfikator GUID lub rejestracja urządzeń " + "NIEAKTYWNA"; + break; + case 5: + ConfigESP->supla_status.msg = "Nieznany adres serwera"; + break; + case 6: + ConfigESP->supla_status.msg = "Nieznany identyfikator ID"; + break; + case 7: + ConfigESP->supla_status.msg = "Zainicjowany"; + break; + case 8: + ConfigESP->supla_status.msg = "Przekroczono limit kanału"; + break; + case 9: + ConfigESP->supla_status.msg = "Rozłączony"; + break; + case 10: + ConfigESP->supla_status.msg = "Rejestracja w toku"; + break; + case 11: + ConfigESP->supla_status.msg = "Błąd zmiennej"; + break; + case 12: + ConfigESP->supla_status.msg = "Błąd wersji protokołu"; + break; + case 13: + ConfigESP->supla_status.msg = "Złe poświadczenia"; + break; + case 14: + ConfigESP->supla_status.msg = "Tymczasowo niedostępne"; + break; + case 15: + ConfigESP->supla_status.msg = "Konflikt lokalizacji"; + break; + case 16: + ConfigESP->supla_status.msg = "Konflikt kanałów"; + break; + case 17: + ConfigESP->supla_status.msg = "Zarejestrowany i gotowy"; + break; + case 18: + ConfigESP->supla_status.msg = "Urządzenie jest rozłączone"; + break; + case 19: + ConfigESP->supla_status.msg = "Lokalizacja jest wyłączona"; + break; + case 20: + ConfigESP->supla_status.msg = "Przekroczono limit urządzeń"; + } + + static int lock; + if (status == 17 && ConfigESP->configModeESP == NORMAL_MODE) { + ConfigESP->ledBlinkingStop(); + lock = 0; + } + else if (status != 17 && lock == 0 && ConfigESP->configModeESP == NORMAL_MODE) { + ConfigESP->ledBlinking(500); + lock = 1; + } + + if (ConfigESP->supla_status.old_msg != ConfigESP->supla_status.msg) { + ConfigESP->supla_status.old_msg = ConfigESP->supla_status.msg; + ConfigESP->supla_status.status = status; + // Serial.println(ConfigESP->supla_status.msg); + } +} +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +int SuplaConfigESP::getMemoryRelay(int nr) { + uint8_t gpio; + for (gpio = 0; gpio <= OFF_GPIO; gpio++) { + String key = GPIO; + key += gpio; + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_RELAY) { + if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { + uint8_t memory = ConfigManager->get(key.c_str())->getElement(MEMORY).toInt(); + return memory; + } + } + } + return OFF_GPIO; +} +#endif + +int SuplaConfigESP::getGpio(int nr, int function) { + uint8_t gpio; + for (gpio = 0; gpio <= OFF_GPIO; gpio++) { + String key = GPIO; + key += gpio; + if (function == FUNCTION_CFG_BUTTON) { + if (checkBusyCfg(gpio)) { + return gpio; + } + } + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == function) { + if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { + return gpio; + } + } + } + return OFF_GPIO; +} + +int SuplaConfigESP::getLevel(int nr, int function) { + uint8_t gpio; + for (gpio = 0; gpio <= OFF_GPIO; gpio++) { + String key = GPIO; + key += gpio; + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == function) { + if (ConfigManager->get(key.c_str())->getElement(NR).toInt() == nr) { + uint8_t level = ConfigManager->get(key.c_str())->getElement(LEVEL).toInt(); + return level; + } + } + } + return OFF_GPIO; +} + +bool SuplaConfigESP::checkBusyCfg(int gpio) { + String key = GPIO; + key += gpio; + if (ConfigManager->get(key.c_str())->getElement(FUNCTION_CFG_LED).toInt() == 1) { + return true; + } + return false; +} + +int SuplaConfigESP::checkBusyGpio(int gpio, int function) { + if (gpio == 6 || gpio == 7 || gpio == 8 || gpio == 11) { + return true; + } + else { + String key = GPIO; + key += gpio; + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_BUTTON) { + if (function == FUNCTION_CFG_BUTTON) { + return false; + } + } + if (checkBusyCfg(gpio)) { + if (function != FUNCTION_BUTTON) { + return true; + } + } + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() != FUNCTION_OFF) { + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() != function) { + return true; + } + } + return false; + } +} + +void SuplaConfigESP::setGpio(uint8_t gpio, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory) { + String key = GPIO; + key += gpio; + if (function == FUNCTION_CFG_BUTTON) { + ConfigManager->setElement(key.c_str(), CFG, 1); + return; + } + + ConfigManager->setElement(key.c_str(), NR, nr); + ConfigManager->setElement(key.c_str(), FUNCTION, function); + ConfigManager->setElement(key.c_str(), LEVEL, level); + ConfigManager->setElement(key.c_str(), MEMORY, memory); + + // ConfigManager->setElement(key.c_str(), MEMORY, memory); + // ConfigManager->setElement(key.c_str(), CFG, cfg); +} + +void SuplaConfigESP::clearGpio(uint8_t gpio, uint8_t function) { + String key = GPIO; + key += gpio; + if (function == FUNCTION_CFG_BUTTON) { + ConfigManager->setElement(key.c_str(), CFG, 0); + return; + } + ConfigManager->setElement(key.c_str(), NR, 0); + ConfigManager->setElement(key.c_str(), FUNCTION, FUNCTION_OFF); + ConfigManager->setElement(key.c_str(), LEVEL, 0); + ConfigManager->setElement(key.c_str(), MEMORY, 0); +} + +uint8_t SuplaConfigESP::countFreeGpio(uint8_t exception) { + uint8_t count = 0; + for (uint8_t gpio = 0; gpio < OFF_GPIO; gpio++) { + if (gpio != 6 && gpio != 7 && gpio != 8 && gpio != 11) { + String key = GPIO; + key += gpio; + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == exception) { + count++; + } + } + } + return count; +} + +void SuplaConfigESP::factoryReset() { + delay(1000); + pinMode(0, INPUT); + if (!digitalRead(0)) { + Serial.println("FACTORY RESET!!!"); + + ConfigManager->set(KEY_WIFI_SSID, ""); + ConfigManager->set(KEY_WIFI_PASS, ""); + ConfigManager->set(KEY_SUPLA_SERVER, DEFAULT_SERVER); + ConfigManager->set(KEY_SUPLA_EMAIL, DEFAULT_EMAIL); + ConfigManager->set(KEY_HOST_NAME, DEFAULT_HOSTNAME); + ConfigManager->set(KEY_LOGIN, DEFAULT_LOGIN); + ConfigManager->set(KEY_LOGIN_PASS, DEFAULT_LOGIN_PASS); + ConfigManager->set(KEY_MAX_ROLLERSHUTTER, "0"); + ConfigManager->set(KEY_MAX_RELAY, "1"); + ConfigManager->set(KEY_MAX_BUTTON, "1"); + ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); + ConfigManager->set(KEY_MAX_DHT22, "1"); + ConfigManager->set(KEY_MAX_DHT11, "1"); + ConfigManager->set(KEY_MULTI_MAX_DS18B20, "1"); + ConfigManager->set(KEY_ALTITUDE_BME280, "0"); + + int nr; + String key; + + for (nr = 0; nr <= 17; nr++) { + key = GPIO; + key += nr; + ConfigManager->set(key.c_str(), "0,0,0,0,0"); + } + + ConfigManager->set(KEY_ACTIVE_SENSOR, "0,0,0,0,0"); + + for (nr = 0; nr <= MAX_DS18B20; nr++) { + key = KEY_DS; + key += nr; + ConfigManager->set(key.c_str(), ""); + key = KEY_DS_NAME; + key += nr; + ConfigManager->set(key.c_str(), ""); + } + + ConfigManager->save(); + + delay(3000); + WiFi.forceSleepBegin(); + wdt_reset(); + ESP.restart(); + while (1) wdt_reset(); + } +} diff --git a/SuplaConfigESP.h b/src/SuplaConfigESP.h similarity index 96% rename from SuplaConfigESP.h rename to src/SuplaConfigESP.h index 8a400913..f1324fa4 100644 --- a/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -1,94 +1,94 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef SuplaConfigESP_h -#define SuplaConfigESP_h - -#include - -#include "GUI-Generic_Config.h" - -enum _configModeESP { NORMAL_MODE, CONFIG_MODE }; - -typedef struct { - int status; - const char *msg; - const char *old_msg; -} _supla_status; - -class SuplaConfigESP : public Supla::Triggerable { - public: - SuplaConfigESP(); - - void addConfigESP(int _pinNumberConfig, int _pinLedConfig, int _modeConfigButton, bool _highIsOn = true); - void runAction(int event, int action); - void rebootESP(); - - const char *getLastStatusSupla(); - - void ledBlinking(int time); - void ledBlinkingStop(void); - int getPinLedConfig(); - virtual uint8_t pinOnValue() { - return highIsOn ? HIGH : LOW; - } - - virtual uint8_t pinOffValue() { - return highIsOn ? LOW : HIGH; - } - - String getMacAddress(bool formating); - - _configModeESP configModeESP; - _supla_status supla_status; - - int getGpio(int nr, int function); - int getGpio(int function) { - return getGpio(1, function); - } - int getLevel(int nr, int function); - int getLevel(int function) { - return getLevel(1, function); - } - bool checkBusyCfg(int gpio); - int checkBusyGpio(int gpio, int function); - uint8_t countFreeGpio(uint8_t exception = 0); - void setGpio(uint8_t gpio, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory = 0); - void setGpio(uint8_t gpio, uint8_t function, uint8_t level = 0) { - setGpio(gpio, 1, function, level); - } - void clearGpio(uint8_t gpio, uint8_t function = 0); - - int getMemoryRelay(int nr); - void factoryReset(); - - private: - void configModeInit(); - - int pinNumberConfig; - int pinLedConfig; - int modeConfigButton; - int countPresses = 0; - unsigned long cnfigChangeTimeMs = 0; - bool highIsOn; - - ETSTimer led_timer; -}; - -void ledBlinking_func(void *timer_arg); -void status_func(int status, const char *msg); - -#endif // SuplaConfigESP_h +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SuplaConfigESP_h +#define SuplaConfigESP_h + +#include + +#include "GUI-Generic_Config.h" + +enum _configModeESP { NORMAL_MODE, CONFIG_MODE }; + +typedef struct { + int status; + const char *msg; + const char *old_msg; +} _supla_status; + +class SuplaConfigESP : public Supla::Triggerable { + public: + SuplaConfigESP(); + + void addConfigESP(int _pinNumberConfig, int _pinLedConfig, int _modeConfigButton, bool _highIsOn = true); + void runAction(int event, int action); + void rebootESP(); + + const char *getLastStatusSupla(); + + void ledBlinking(int time); + void ledBlinkingStop(void); + int getPinLedConfig(); + virtual uint8_t pinOnValue() { + return highIsOn ? HIGH : LOW; + } + + virtual uint8_t pinOffValue() { + return highIsOn ? LOW : HIGH; + } + + String getMacAddress(bool formating); + + _configModeESP configModeESP; + _supla_status supla_status; + + int getGpio(int nr, int function); + int getGpio(int function) { + return getGpio(1, function); + } + int getLevel(int nr, int function); + int getLevel(int function) { + return getLevel(1, function); + } + bool checkBusyCfg(int gpio); + int checkBusyGpio(int gpio, int function); + uint8_t countFreeGpio(uint8_t exception = 0); + void setGpio(uint8_t gpio, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory = 0); + void setGpio(uint8_t gpio, uint8_t function, uint8_t level = 0) { + setGpio(gpio, 1, function, level); + } + void clearGpio(uint8_t gpio, uint8_t function = 0); + + int getMemoryRelay(int nr); + void factoryReset(); + + private: + void configModeInit(); + + int pinNumberConfig; + int pinLedConfig; + int modeConfigButton; + int countPresses = 0; + unsigned long cnfigChangeTimeMs = 0; + bool highIsOn; + + ETSTimer led_timer; +}; + +void ledBlinking_func(void *timer_arg); +void status_func(int status, const char *msg); + +#endif // SuplaConfigESP_h diff --git a/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp similarity index 96% rename from SuplaConfigManager.cpp rename to src/SuplaConfigManager.cpp index ce40aff3..8d435d23 100644 --- a/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -1,437 +1,437 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include -#include -#include - -#include -#include - -#include "FS.h" -#include "SuplaConfigManager.h" - -#define CONFIG_FILE_PATH "/dat" - -ConfigOption::ConfigOption(const char *key, const char *value, int maxLength) { - // size_t size = strlen(key) + 1; - // _key = (char *)malloc(sizeof(char) * size); - // memcpy(_key, key, size - 1); - // _key[size - 1] = 0; - size_t size = strlen(key) + 1; - _key = new char[size]; - strncpy(_key, key, size); - _key[size - 1] = '\0'; - - _maxLength = maxLength + 1; - setValue(value); -} - -const char *ConfigOption::getKey() { - return _key; -} - -const char *ConfigOption::getValue() { - return _value; -} - -int ConfigOption::getValueInt() { - return atoi(_value); -} - -uint8_t *ConfigOption::getValueBin(size_t size) { - uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t) * (size)); - - for (int i = 0; i < size; i++) { - sscanf(&_value[i * 2], "%2hhx", &buffer[i]); - } - return buffer; -} - -const char *ConfigOption::getValueHex(size_t size) { - char *buffer = (char *)malloc(sizeof(char) * (size * 2)); - int a, b; - - buffer[0] = 0; - b = 0; - - for (a = 0; a < size; a++) { - snprintf(&buffer[b], 3, "%02X", (unsigned char)_value[a]); // NOLINT - b += 2; - } - return buffer; -} - -int ConfigOption::getValueElement(int element) { - return _value[element] - 48; -} - -int ConfigOption::getLength() { - return _maxLength; -} - -String ConfigOption::getElement(int index) { - String data = _value; - int found = 0; - int strIndex[] = {0, -1}; - int maxIndex = data.length() - 1; - for (int i = 0; i <= maxIndex && found <= index; i++) { - if (data.charAt(i) == SEPARATOR || i == maxIndex) { - found++; - strIndex[0] = strIndex[1] + 1; - strIndex[1] = (i == maxIndex) ? i + 1 : i; - } - } - return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; -} - -String ConfigOption::replaceElement(int index, int newvalue) { - String data = _value; - int lenght = 5; - String table; - for (int i = 0; i < lenght; i++) { - if (i == index) { - table += newvalue; - } else { - table += this->getElement(i); - } - if (i < lenght - 1) table += SEPARATOR; - } - return table; -} - -void ConfigOption::setValue(const char *value) { - //size_t size = _maxLength + 1; - //_value = (char *)malloc(sizeof(char) * (size)); - - //if (value != NULL) { - // memcpy(_value, value, size - 1); - // _value[size - 1] = 0; - //} - if (value != NULL) { - size_t size = getLength(); - _value = new char[size]; - strncpy(_value, value, size); - _value[size - 1] = '\0'; - } -} - -// -// class SuplaConfigManager -// -SuplaConfigManager::SuplaConfigManager() { - if (SPIFFS.begin()) { - // Serial.println(F("\nSPIFFS mounted")); - // } else { - // Serial.println(F("\nFailed to mount SPIFFS")); - } - _optionCount = 0; - - this->addKey(KEY_SUPLA_GUID, MAX_GUID); - this->addKey(KEY_SUPLA_AUTHKEY, MAX_AUTHKEY); - this->addKey(KEY_WIFI_SSID, MAX_SSID); - this->addKey(KEY_WIFI_PASS, MAX_PASSWORD); - this->addKey(KEY_LOGIN, MAX_MLOGIN); - this->addKey(KEY_LOGIN_PASS, MAX_MPASSWORD); - this->addKey(KEY_HOST_NAME, DEFAULT_HOSTNAME, MAX_HOSTNAME); - this->addKey(KEY_SUPLA_SERVER, DEFAULT_SERVER, MAX_SUPLA_SERVER); - this->addKey(KEY_SUPLA_EMAIL, DEFAULT_EMAIL, MAX_EMAIL); - this->addKey(KEY_MAX_ROLLERSHUTTER, "0", 2); - this->addKey(KEY_MAX_RELAY, "1", 2); - this->addKey(KEY_MAX_BUTTON, "1", 2); - this->addKey(KEY_MAX_LIMIT_SWITCH, "0", 2); - this->addKey(KEY_MAX_DHT22, "1", 2); - this->addKey(KEY_MAX_DHT11, "1", 2); - this->addKey(KEY_MULTI_MAX_DS18B20, "1", 2); - this->addKey(KEY_ALTITUDE_BME280, "0", 4); - - int nr; - String key; - - for (nr = 0; nr <= 17; nr++) { - key = GPIO; - key += nr; - this->addKey(key.c_str(), "0,0,0,0,0", 14); - } - - this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 14); - this->addKey(KEY_BOARD, "0", 2); - - for (nr = 0; nr <= MAX_DS18B20; nr++) { - key = KEY_DS; - key += nr; - this->addKey(key.c_str(), MAX_DS18B20_ADDRESS_HEX); - key = KEY_DS_NAME; - key += nr; - this->addKey(key.c_str(), MAX_DS18B20_NAME); - } - this->load(); - // switch (this->load()) { - // case E_CONFIG_OK: - // Serial.println(F("Config read")); - // return; - // case E_CONFIG_FS_ACCESS: - // Serial.println(F("E_CONFIG_FS_ACCESS: Couldn't access file system")); - // return; - // case E_CONFIG_FILE_NOT_FOUND: - // Serial.println(F("E_CONFIG_FILE_NOT_FOUND: File not found")); - // return; - // case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - // return; - // case E_CONFIG_PARSE_ERROR: - // Serial.println(F("E_CONFIG_PARSE_ERROR: File was not parsable")); - // return; - // } -} - -uint8_t SuplaConfigManager::addKey(const char *key, int maxLength) { - return addKey(key, "", maxLength); -} - -uint8_t SuplaConfigManager::addKey(const char *key, const char *value, int maxLength) { - if (_optionCount == CONFIG_MAX_OPTIONS) { - return E_CONFIG_MAX; - } - _options[_optionCount] = new ConfigOption(key, value, maxLength); - _optionCount += 1; - - return E_CONFIG_OK; -} - -uint8_t SuplaConfigManager::addKeyAndRead(const char *key, const char *value, int maxLength) { - addKey(key, maxLength); - if (this->loadItem(key) != E_CONFIG_OK) { - this->set(key, value); - } - return E_CONFIG_OK; -} - -uint8_t SuplaConfigManager::deleteKey(const char *key) { - for (int i = 0; i < _optionCount; i++) { - if (strcmp(_options[i]->getKey(), key) == 0) { - delete _options[_optionCount]; - _optionCount -= 1; - } - } - - return E_CONFIG_OK; -} - -uint8_t SuplaConfigManager::load() { - - if (SPIFFS.begin()) { - if (SPIFFS.exists(CONFIG_FILE_PATH)) { - File configFile = SPIFFS.open(CONFIG_FILE_PATH, "r"); - - if (configFile) { - int i = 0; - int offset = 0; - int length = 0; - - for (i = 0; i < _optionCount; i++) { - length += _options[i]->getLength(); - } - - // if (length != configFile.size()) { - // return E_CONFIG_PARSE_ERROR; - // } - - uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); - configFile.read(content, length); - - for (i = 0; i < _optionCount; i++) { - _options[i]->setValue((const char *)(content + offset)); - offset += _options[i]->getLength(); - } - - configFile.close(); - free(content); - - return E_CONFIG_OK; - } else { - configFile.close(); - return E_CONFIG_FILE_OPEN; - } - } else { - return E_CONFIG_FILE_NOT_FOUND; - } - } else { - return E_CONFIG_FS_ACCESS; - } -} - -uint8_t SuplaConfigManager::loadItem(const char *key) { - - if (SPIFFS.begin()) { - if (SPIFFS.exists(CONFIG_FILE_PATH)) { - File configFile = SPIFFS.open(CONFIG_FILE_PATH, "r"); - - if (configFile) { - int i = 0; - int offset = 0; - int length = 0; - - for (i = 0; i < _optionCount; i++) { - length += _options[i]->getLength(); - } - - uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); - configFile.read(content, length); - - for (i = 0; i < _optionCount; i++) { - if (strcmp(_options[i]->getKey(), key) == 0) { - _options[i]->setValue((const char *)(content + offset)); - } - offset += _options[i]->getLength(); - } - - configFile.close(); - free(content); - - return E_CONFIG_OK; - } else { - configFile.close(); - return E_CONFIG_FILE_OPEN; - } - } else { - return E_CONFIG_FILE_NOT_FOUND; - } - } else { - return E_CONFIG_FS_ACCESS; - } -} - -uint8_t SuplaConfigManager::save() { - if (SPIFFS.begin()) { - int i = 0; - int offset = 0; - int length = 0; - - for (i = 0; i < _optionCount; i++) { - length += _options[i]->getLength(); - } - - File configFile = SPIFFS.open(CONFIG_FILE_PATH, "w+"); - if (configFile) { - uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); - for (i = 0; i < _optionCount; i++) { - Serial.println("Save key " + String(_options[i]->getKey()) + ": " + String(_options[i]->getValue())); - memcpy(content + offset, _options[i]->getValue(), _options[i]->getLength()); - offset += _options[i]->getLength(); - } - - configFile.write(content, length); - configFile.close(); - - free(content); - return E_CONFIG_OK; - } else { - return E_CONFIG_FILE_OPEN; - } - } - - return E_CONFIG_FS_ACCESS; -} - -void SuplaConfigManager::showAllValue() { - for (int i = 0; i < _optionCount; i++) { - Serial.println("Key: " + String(_options[i]->getKey()) + " Value: " + String(_options[i]->getValue())); - } -} - -bool SuplaConfigManager::isDeviceConfigured() { - return strcmp(this->get(KEY_SUPLA_GUID)->getValue(), "") == 0 || - strcmp(this->get(KEY_SUPLA_AUTHKEY)->getValue(), "") == 0 || - strcmp(this->get(KEY_WIFI_SSID)->getValue(), "") == 0 || - strcmp(this->get(KEY_WIFI_PASS)->getValue(), "") == 0 || - strcmp(this->get(KEY_LOGIN)->getValue(), "") == 0; -} - -ConfigOption *SuplaConfigManager::get(const char *key) { - for (int i = 0; i < _optionCount; i++) { - if (strcmp(_options[i]->getKey(), key) == 0) { - return _options[i]; - } - } - return NULL; -} - -bool SuplaConfigManager::set(const char *key, const char *value) { - for (int i = 0; i < _optionCount; i++) { - if (strcmp(key, _options[i]->getKey()) == 0) { - _options[i]->setValue(value); - return true; - } - } - return false; -} - -bool SuplaConfigManager::setElement(const char *key, int index, int newvalue) { - for (int i = 0; i < _optionCount; i++) { - if (strcmp(key, _options[i]->getKey()) == 0) { - String data = _options[i]->replaceElement(index, newvalue); - _options[i]->setValue(data.c_str()); - return true; - } - } - return false; -} - -void SuplaConfigManager::setGUIDandAUTHKEY() { - if (strcmp(this->get(KEY_SUPLA_GUID)->getValue(), "") != 0 || - strcmp(this->get(KEY_SUPLA_AUTHKEY)->getValue(), "") != 0) { - return; - } - - char mac[6]; - int a; - - char GUID[SUPLA_GUID_SIZE]; - char AUTHKEY[SUPLA_AUTHKEY_SIZE]; - - memset(GUID, 0, SUPLA_GUID_SIZE); - memset(AUTHKEY, 0, SUPLA_AUTHKEY_SIZE); - - os_get_random((unsigned char *)GUID, SUPLA_GUID_SIZE); - os_get_random((unsigned char *)AUTHKEY, SUPLA_AUTHKEY_SIZE); - - if (SUPLA_GUID_SIZE >= 6) { - wifi_get_macaddr(STATION_IF, (unsigned char *)mac); - - for (a = 0; a < 6; a++) - GUID[a] = (GUID[a] * mac[a]) % 255; - } - - if (SUPLA_GUID_SIZE >= 12) { - wifi_get_macaddr(SOFTAP_IF, (unsigned char *)mac); - - for (a = 0; a < 6; a++) GUID[a + 6] = (GUID[a + 6] * mac[a]) % 255; - } - - for (a = 0; a < SUPLA_GUID_SIZE; a++) { - GUID[a] = (GUID[a] + system_get_time() + spi_flash_get_id() + system_get_chip_id() + system_get_rtc_time()) % 255; - } - - a = SUPLA_GUID_SIZE > SUPLA_AUTHKEY_SIZE ? SUPLA_AUTHKEY_SIZE : SUPLA_GUID_SIZE; - a--; - for (; a > 0; a--) { - AUTHKEY[a] += GUID[a]; - } - - this->set(KEY_SUPLA_GUID, GUID); - this->set(KEY_SUPLA_AUTHKEY, AUTHKEY); -} +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +#include +#include + +#include "FS.h" +#include "SuplaConfigManager.h" + +#define CONFIG_FILE_PATH "/dat" + +ConfigOption::ConfigOption(const char *key, const char *value, int maxLength) { + // size_t size = strlen(key) + 1; + // _key = (char *)malloc(sizeof(char) * size); + // memcpy(_key, key, size - 1); + // _key[size - 1] = 0; + size_t size = strlen(key) + 1; + _key = new char[size]; + strncpy(_key, key, size); + _key[size - 1] = '\0'; + + _maxLength = maxLength + 1; + setValue(value); +} + +const char *ConfigOption::getKey() { + return _key; +} + +const char *ConfigOption::getValue() { + return _value; +} + +int ConfigOption::getValueInt() { + return atoi(_value); +} + +uint8_t *ConfigOption::getValueBin(size_t size) { + uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t) * (size)); + + for (int i = 0; i < size; i++) { + sscanf(&_value[i * 2], "%2hhx", &buffer[i]); + } + return buffer; +} + +const char *ConfigOption::getValueHex(size_t size) { + char *buffer = (char *)malloc(sizeof(char) * (size * 2)); + int a, b; + + buffer[0] = 0; + b = 0; + + for (a = 0; a < size; a++) { + snprintf(&buffer[b], 3, "%02X", (unsigned char)_value[a]); // NOLINT + b += 2; + } + return buffer; +} + +int ConfigOption::getValueElement(int element) { + return _value[element] - 48; +} + +int ConfigOption::getLength() { + return _maxLength; +} + +String ConfigOption::getElement(int index) { + String data = _value; + int found = 0; + int strIndex[] = {0, -1}; + int maxIndex = data.length() - 1; + for (int i = 0; i <= maxIndex && found <= index; i++) { + if (data.charAt(i) == SEPARATOR || i == maxIndex) { + found++; + strIndex[0] = strIndex[1] + 1; + strIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; +} + +String ConfigOption::replaceElement(int index, int newvalue) { + String data = _value; + int lenght = 5; + String table; + for (int i = 0; i < lenght; i++) { + if (i == index) { + table += newvalue; + } else { + table += this->getElement(i); + } + if (i < lenght - 1) table += SEPARATOR; + } + return table; +} + +void ConfigOption::setValue(const char *value) { + //size_t size = _maxLength + 1; + //_value = (char *)malloc(sizeof(char) * (size)); + + //if (value != NULL) { + // memcpy(_value, value, size - 1); + // _value[size - 1] = 0; + //} + if (value != NULL) { + size_t size = getLength(); + _value = new char[size]; + strncpy(_value, value, size); + _value[size - 1] = '\0'; + } +} + +// +// class SuplaConfigManager +// +SuplaConfigManager::SuplaConfigManager() { + if (SPIFFS.begin()) { + // Serial.println(F("\nSPIFFS mounted")); + // } else { + // Serial.println(F("\nFailed to mount SPIFFS")); + } + _optionCount = 0; + + this->addKey(KEY_SUPLA_GUID, MAX_GUID); + this->addKey(KEY_SUPLA_AUTHKEY, MAX_AUTHKEY); + this->addKey(KEY_WIFI_SSID, MAX_SSID); + this->addKey(KEY_WIFI_PASS, MAX_PASSWORD); + this->addKey(KEY_LOGIN, MAX_MLOGIN); + this->addKey(KEY_LOGIN_PASS, MAX_MPASSWORD); + this->addKey(KEY_HOST_NAME, DEFAULT_HOSTNAME, MAX_HOSTNAME); + this->addKey(KEY_SUPLA_SERVER, DEFAULT_SERVER, MAX_SUPLA_SERVER); + this->addKey(KEY_SUPLA_EMAIL, DEFAULT_EMAIL, MAX_EMAIL); + this->addKey(KEY_MAX_ROLLERSHUTTER, "0", 2); + this->addKey(KEY_MAX_RELAY, "1", 2); + this->addKey(KEY_MAX_BUTTON, "1", 2); + this->addKey(KEY_MAX_LIMIT_SWITCH, "0", 2); + this->addKey(KEY_MAX_DHT22, "1", 2); + this->addKey(KEY_MAX_DHT11, "1", 2); + this->addKey(KEY_MULTI_MAX_DS18B20, "1", 2); + this->addKey(KEY_ALTITUDE_BME280, "0", 4); + + int nr; + String key; + + for (nr = 0; nr <= 17; nr++) { + key = GPIO; + key += nr; + this->addKey(key.c_str(), "0,0,0,0,0", 14); + } + + this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 14); + this->addKey(KEY_BOARD, "0", 2); + + for (nr = 0; nr <= MAX_DS18B20; nr++) { + key = KEY_DS; + key += nr; + this->addKey(key.c_str(), MAX_DS18B20_ADDRESS_HEX); + key = KEY_DS_NAME; + key += nr; + this->addKey(key.c_str(), MAX_DS18B20_NAME); + } + this->load(); + // switch (this->load()) { + // case E_CONFIG_OK: + // Serial.println(F("Config read")); + // return; + // case E_CONFIG_FS_ACCESS: + // Serial.println(F("E_CONFIG_FS_ACCESS: Couldn't access file system")); + // return; + // case E_CONFIG_FILE_NOT_FOUND: + // Serial.println(F("E_CONFIG_FILE_NOT_FOUND: File not found")); + // return; + // case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + // return; + // case E_CONFIG_PARSE_ERROR: + // Serial.println(F("E_CONFIG_PARSE_ERROR: File was not parsable")); + // return; + // } +} + +uint8_t SuplaConfigManager::addKey(const char *key, int maxLength) { + return addKey(key, "", maxLength); +} + +uint8_t SuplaConfigManager::addKey(const char *key, const char *value, int maxLength) { + if (_optionCount == CONFIG_MAX_OPTIONS) { + return E_CONFIG_MAX; + } + _options[_optionCount] = new ConfigOption(key, value, maxLength); + _optionCount += 1; + + return E_CONFIG_OK; +} + +uint8_t SuplaConfigManager::addKeyAndRead(const char *key, const char *value, int maxLength) { + addKey(key, maxLength); + if (this->loadItem(key) != E_CONFIG_OK) { + this->set(key, value); + } + return E_CONFIG_OK; +} + +uint8_t SuplaConfigManager::deleteKey(const char *key) { + for (int i = 0; i < _optionCount; i++) { + if (strcmp(_options[i]->getKey(), key) == 0) { + delete _options[_optionCount]; + _optionCount -= 1; + } + } + + return E_CONFIG_OK; +} + +uint8_t SuplaConfigManager::load() { + + if (SPIFFS.begin()) { + if (SPIFFS.exists(CONFIG_FILE_PATH)) { + File configFile = SPIFFS.open(CONFIG_FILE_PATH, "r"); + + if (configFile) { + int i = 0; + int offset = 0; + int length = 0; + + for (i = 0; i < _optionCount; i++) { + length += _options[i]->getLength(); + } + + // if (length != configFile.size()) { + // return E_CONFIG_PARSE_ERROR; + // } + + uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); + configFile.read(content, length); + + for (i = 0; i < _optionCount; i++) { + _options[i]->setValue((const char *)(content + offset)); + offset += _options[i]->getLength(); + } + + configFile.close(); + free(content); + + return E_CONFIG_OK; + } else { + configFile.close(); + return E_CONFIG_FILE_OPEN; + } + } else { + return E_CONFIG_FILE_NOT_FOUND; + } + } else { + return E_CONFIG_FS_ACCESS; + } +} + +uint8_t SuplaConfigManager::loadItem(const char *key) { + + if (SPIFFS.begin()) { + if (SPIFFS.exists(CONFIG_FILE_PATH)) { + File configFile = SPIFFS.open(CONFIG_FILE_PATH, "r"); + + if (configFile) { + int i = 0; + int offset = 0; + int length = 0; + + for (i = 0; i < _optionCount; i++) { + length += _options[i]->getLength(); + } + + uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); + configFile.read(content, length); + + for (i = 0; i < _optionCount; i++) { + if (strcmp(_options[i]->getKey(), key) == 0) { + _options[i]->setValue((const char *)(content + offset)); + } + offset += _options[i]->getLength(); + } + + configFile.close(); + free(content); + + return E_CONFIG_OK; + } else { + configFile.close(); + return E_CONFIG_FILE_OPEN; + } + } else { + return E_CONFIG_FILE_NOT_FOUND; + } + } else { + return E_CONFIG_FS_ACCESS; + } +} + +uint8_t SuplaConfigManager::save() { + if (SPIFFS.begin()) { + int i = 0; + int offset = 0; + int length = 0; + + for (i = 0; i < _optionCount; i++) { + length += _options[i]->getLength(); + } + + File configFile = SPIFFS.open(CONFIG_FILE_PATH, "w+"); + if (configFile) { + uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); + for (i = 0; i < _optionCount; i++) { + Serial.println("Save key " + String(_options[i]->getKey()) + ": " + String(_options[i]->getValue())); + memcpy(content + offset, _options[i]->getValue(), _options[i]->getLength()); + offset += _options[i]->getLength(); + } + + configFile.write(content, length); + configFile.close(); + + free(content); + return E_CONFIG_OK; + } else { + return E_CONFIG_FILE_OPEN; + } + } + + return E_CONFIG_FS_ACCESS; +} + +void SuplaConfigManager::showAllValue() { + for (int i = 0; i < _optionCount; i++) { + Serial.println("Key: " + String(_options[i]->getKey()) + " Value: " + String(_options[i]->getValue())); + } +} + +bool SuplaConfigManager::isDeviceConfigured() { + return strcmp(this->get(KEY_SUPLA_GUID)->getValue(), "") == 0 || + strcmp(this->get(KEY_SUPLA_AUTHKEY)->getValue(), "") == 0 || + strcmp(this->get(KEY_WIFI_SSID)->getValue(), "") == 0 || + strcmp(this->get(KEY_WIFI_PASS)->getValue(), "") == 0 || + strcmp(this->get(KEY_LOGIN)->getValue(), "") == 0; +} + +ConfigOption *SuplaConfigManager::get(const char *key) { + for (int i = 0; i < _optionCount; i++) { + if (strcmp(_options[i]->getKey(), key) == 0) { + return _options[i]; + } + } + return NULL; +} + +bool SuplaConfigManager::set(const char *key, const char *value) { + for (int i = 0; i < _optionCount; i++) { + if (strcmp(key, _options[i]->getKey()) == 0) { + _options[i]->setValue(value); + return true; + } + } + return false; +} + +bool SuplaConfigManager::setElement(const char *key, int index, int newvalue) { + for (int i = 0; i < _optionCount; i++) { + if (strcmp(key, _options[i]->getKey()) == 0) { + String data = _options[i]->replaceElement(index, newvalue); + _options[i]->setValue(data.c_str()); + return true; + } + } + return false; +} + +void SuplaConfigManager::setGUIDandAUTHKEY() { + if (strcmp(this->get(KEY_SUPLA_GUID)->getValue(), "") != 0 || + strcmp(this->get(KEY_SUPLA_AUTHKEY)->getValue(), "") != 0) { + return; + } + + char mac[6]; + int a; + + char GUID[SUPLA_GUID_SIZE]; + char AUTHKEY[SUPLA_AUTHKEY_SIZE]; + + memset(GUID, 0, SUPLA_GUID_SIZE); + memset(AUTHKEY, 0, SUPLA_AUTHKEY_SIZE); + + os_get_random((unsigned char *)GUID, SUPLA_GUID_SIZE); + os_get_random((unsigned char *)AUTHKEY, SUPLA_AUTHKEY_SIZE); + + if (SUPLA_GUID_SIZE >= 6) { + wifi_get_macaddr(STATION_IF, (unsigned char *)mac); + + for (a = 0; a < 6; a++) + GUID[a] = (GUID[a] * mac[a]) % 255; + } + + if (SUPLA_GUID_SIZE >= 12) { + wifi_get_macaddr(SOFTAP_IF, (unsigned char *)mac); + + for (a = 0; a < 6; a++) GUID[a + 6] = (GUID[a + 6] * mac[a]) % 255; + } + + for (a = 0; a < SUPLA_GUID_SIZE; a++) { + GUID[a] = (GUID[a] + system_get_time() + spi_flash_get_id() + system_get_chip_id() + system_get_rtc_time()) % 255; + } + + a = SUPLA_GUID_SIZE > SUPLA_AUTHKEY_SIZE ? SUPLA_AUTHKEY_SIZE : SUPLA_GUID_SIZE; + a--; + for (; a > 0; a--) { + AUTHKEY[a] += GUID[a]; + } + + this->set(KEY_SUPLA_GUID, GUID); + this->set(KEY_SUPLA_AUTHKEY, AUTHKEY); +} diff --git a/SuplaConfigManager.h b/src/SuplaConfigManager.h similarity index 96% rename from SuplaConfigManager.h rename to src/SuplaConfigManager.h index a728d9d5..27a935b0 100644 --- a/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -1,158 +1,158 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef SuplaConfigManager_h -#define SuplaConfigManager_h - -#define DEFAULT_HOSTNAME "GUI Generic" - -#define DEFAULT_LOGIN "admin" -#define DEFAULT_LOGIN_PASS "pass" - -#define DEFAULT_SERVER "svrX.supla.org" -#define DEFAULT_EMAIL "email@address.com" - -#define KEY_SUPLA_GUID "GUID" -#define KEY_SUPLA_AUTHKEY "AUTHKEY" -#define KEY_WIFI_SSID "wifiSSID" -#define KEY_WIFI_PASS "wifiPass" -#define KEY_LOGIN "login" -#define KEY_LOGIN_PASS "loginPass" -#define KEY_HOST_NAME "hostName" -#define KEY_SUPLA_SERVER "suplaServer" -#define KEY_SUPLA_EMAIL "suplaEmail" -#define KEY_DS "ds" -#define KEY_DS_NAME "dsName" -#define KEY_MULTI_MAX_DS18B20 "multiMaxDs" -#define KEY_SUPLA_FUNCTION "function" -#define KEY_MAX_RELAY "maxRelay" -#define KEY_MAX_BUTTON "maxButton" -#define KEY_MAX_LIMIT_SWITCH "maxLimitSwitch" -#define KEY_MAX_DHT11 "maxDht11" -#define KEY_MAX_DHT22 "maxDht22" -#define KEY_MAX_ROLLERSHUTTER "maxRollerShutter" -#define KEY_ALTITUDE_BME280 "altbme280" -#define KEY_ACTIVE_SENSOR "sensor" -#define KEY_BOARD "board" - -#define GPIO "GPIO" -#define SEPARATOR ',' - -enum _settings -{ - NR, - FUNCTION, - LEVEL, - MEMORY, - CFG -}; - -enum _function -{ - FUNCTION_OFF, - FUNCTION_RELAY, - FUNCTION_BUTTON, - FUNCTION_LIMIT_SWITCH, - FUNCTION_CFG_LED, - FUNCTION_CFG_BUTTON, - FUNCTION_DS18B20, - FUNCTION_DHT11, - FUNCTION_DHT22, - FUNCTION_SDA, - FUNCTION_SCL, - FUNCTION_TRIG, - FUNCTION_ECHO, - FUNCTION_SI7021_SONOFF, - FUNCTION_CLK, - FUNCTION_CS, - FUNCTION_D0 -}; - -#define MAX_GUID SUPLA_GUID_SIZE -#define MAX_AUTHKEY SUPLA_GUID_SIZE -#define MAX_SSID 32 -#define MAX_PASSWORD 64 -#define MIN_PASSWORD 8 -#define MAX_MLOGIN 32 -#define MAX_MPASSWORD 64 -#define MAX_HOSTNAME 32 -#define MAX_SUPLA_SERVER SUPLA_SERVER_NAME_MAXSIZE -#define MAX_EMAIL SUPLA_EMAIL_MAXSIZE -#define MAX_DS18B20_ADDRESS_HEX 16 -#define MAX_DS18B20_ADDRESS 8 -#define MAX_DS18B20_NAME 8 -#define MAX_TYPE_BUTTON 4 -#define MAX_MONOSTABLE_TRIGGER 1 -#define MAX_FUNCTION 1 -#define MAX_DS18B20 10 - -enum _e_onfig -{ - E_CONFIG_OK, - E_CONFIG_FS_ACCESS, - E_CONFIG_FILE_NOT_FOUND, - E_CONFIG_FILE_OPEN, - E_CONFIG_PARSE_ERROR, - E_CONFIG_MAX -}; - -#define CONFIG_MAX_OPTIONS 88 - -class ConfigOption { - public: - ConfigOption(const char *key, const char *value, int maxLength); - const char *getKey(); - const char *getValue(); - int getValueInt(); - uint8_t *getValueBin(size_t size); - const char *getValueHex(size_t size); - int getValueElement(int element); - - int getLength(); - void setValue(const char *value); - String getElement(int index); - String replaceElement(int index, int value); - - private: - char *_key; - char *_value; - int _maxLength; -}; - -class SuplaConfigManager { - public: - SuplaConfigManager(); - uint8_t addKey(const char *key, int maxLength); - uint8_t addKey(const char *key, const char *value, int maxLength); - uint8_t addKeyAndRead(const char *key, const char *value, int maxLength); - uint8_t deleteKey(const char *key); - uint8_t load(); - uint8_t loadItem(const char *key); - uint8_t save(); - void showAllValue(); - - ConfigOption *get(const char *key); - bool set(const char *key, const char *value); - bool setElement(const char *key, int index, int newvalue); - - bool isDeviceConfigured(); - void setGUIDandAUTHKEY(); - - private: - int _optionCount; - ConfigOption *_options[CONFIG_MAX_OPTIONS]; -}; -#endif +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SuplaConfigManager_h +#define SuplaConfigManager_h + +#define DEFAULT_HOSTNAME "GUI Generic" + +#define DEFAULT_LOGIN "admin" +#define DEFAULT_LOGIN_PASS "pass" + +#define DEFAULT_SERVER "svrX.supla.org" +#define DEFAULT_EMAIL "email@address.com" + +#define KEY_SUPLA_GUID "GUID" +#define KEY_SUPLA_AUTHKEY "AUTHKEY" +#define KEY_WIFI_SSID "wifiSSID" +#define KEY_WIFI_PASS "wifiPass" +#define KEY_LOGIN "login" +#define KEY_LOGIN_PASS "loginPass" +#define KEY_HOST_NAME "hostName" +#define KEY_SUPLA_SERVER "suplaServer" +#define KEY_SUPLA_EMAIL "suplaEmail" +#define KEY_DS "ds" +#define KEY_DS_NAME "dsName" +#define KEY_MULTI_MAX_DS18B20 "multiMaxDs" +#define KEY_SUPLA_FUNCTION "function" +#define KEY_MAX_RELAY "maxRelay" +#define KEY_MAX_BUTTON "maxButton" +#define KEY_MAX_LIMIT_SWITCH "maxLimitSwitch" +#define KEY_MAX_DHT11 "maxDht11" +#define KEY_MAX_DHT22 "maxDht22" +#define KEY_MAX_ROLLERSHUTTER "maxRollerShutter" +#define KEY_ALTITUDE_BME280 "altbme280" +#define KEY_ACTIVE_SENSOR "sensor" +#define KEY_BOARD "board" + +#define GPIO "GPIO" +#define SEPARATOR ',' + +enum _settings +{ + NR, + FUNCTION, + LEVEL, + MEMORY, + CFG +}; + +enum _function +{ + FUNCTION_OFF, + FUNCTION_RELAY, + FUNCTION_BUTTON, + FUNCTION_LIMIT_SWITCH, + FUNCTION_CFG_LED, + FUNCTION_CFG_BUTTON, + FUNCTION_DS18B20, + FUNCTION_DHT11, + FUNCTION_DHT22, + FUNCTION_SDA, + FUNCTION_SCL, + FUNCTION_TRIG, + FUNCTION_ECHO, + FUNCTION_SI7021_SONOFF, + FUNCTION_CLK, + FUNCTION_CS, + FUNCTION_D0 +}; + +#define MAX_GUID SUPLA_GUID_SIZE +#define MAX_AUTHKEY SUPLA_GUID_SIZE +#define MAX_SSID 32 +#define MAX_PASSWORD 64 +#define MIN_PASSWORD 8 +#define MAX_MLOGIN 32 +#define MAX_MPASSWORD 64 +#define MAX_HOSTNAME 32 +#define MAX_SUPLA_SERVER SUPLA_SERVER_NAME_MAXSIZE +#define MAX_EMAIL SUPLA_EMAIL_MAXSIZE +#define MAX_DS18B20_ADDRESS_HEX 16 +#define MAX_DS18B20_ADDRESS 8 +#define MAX_DS18B20_NAME 8 +#define MAX_TYPE_BUTTON 4 +#define MAX_MONOSTABLE_TRIGGER 1 +#define MAX_FUNCTION 1 +#define MAX_DS18B20 10 + +enum _e_onfig +{ + E_CONFIG_OK, + E_CONFIG_FS_ACCESS, + E_CONFIG_FILE_NOT_FOUND, + E_CONFIG_FILE_OPEN, + E_CONFIG_PARSE_ERROR, + E_CONFIG_MAX +}; + +#define CONFIG_MAX_OPTIONS 88 + +class ConfigOption { + public: + ConfigOption(const char *key, const char *value, int maxLength); + const char *getKey(); + const char *getValue(); + int getValueInt(); + uint8_t *getValueBin(size_t size); + const char *getValueHex(size_t size); + int getValueElement(int element); + + int getLength(); + void setValue(const char *value); + String getElement(int index); + String replaceElement(int index, int value); + + private: + char *_key; + char *_value; + int _maxLength; +}; + +class SuplaConfigManager { + public: + SuplaConfigManager(); + uint8_t addKey(const char *key, int maxLength); + uint8_t addKey(const char *key, const char *value, int maxLength); + uint8_t addKeyAndRead(const char *key, const char *value, int maxLength); + uint8_t deleteKey(const char *key); + uint8_t load(); + uint8_t loadItem(const char *key); + uint8_t save(); + void showAllValue(); + + ConfigOption *get(const char *key); + bool set(const char *key, const char *value); + bool setElement(const char *key, int index, int newvalue); + + bool isDeviceConfigured(); + void setGUIDandAUTHKEY(); + + private: + int _optionCount; + ConfigOption *_options[CONFIG_MAX_OPTIONS]; +}; +#endif diff --git a/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp similarity index 97% rename from SuplaDeviceGUI.cpp rename to src/SuplaDeviceGUI.cpp index 8526a4f6..40ea3955 100644 --- a/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -1,163 +1,163 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ -#include "SuplaDeviceGUI.h" -#include "SuplaConfigManager.h" -#include "SuplaGuiWiFi.h" - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -#define TIME_SAVE_PERIOD_SEK 30 // the time is given in seconds -#define STORAGE_OFFSET 0 -#include -Supla::Eeprom eeprom(STORAGE_OFFSET); -#endif - -namespace Supla { -namespace GUI { -void begin() { -#ifdef DEBUG_MODE - new Supla::Sensor::EspFreeHeap(); -#endif - - Supla::GUIESPWifi *wifi = new Supla::GUIESPWifi(ConfigManager->get(KEY_WIFI_SSID)->getValue(), ConfigManager->get(KEY_WIFI_PASS)->getValue()); - - wifi->enableBuffer(true); - wifi->enableSSL(false); - - String supla_hostname = ConfigManager->get(KEY_HOST_NAME)->getValue(); - supla_hostname.replace(" ", "_"); - wifi->setHostName(supla_hostname.c_str()); - - SuplaDevice.setName((char *)ConfigManager->get(KEY_HOST_NAME)->getValue()); - - SuplaDevice.begin((char *)ConfigManager->get(KEY_SUPLA_GUID)->getValue(), // Global Unique Identifier - (char *)ConfigManager->get(KEY_SUPLA_SERVER)->getValue(), // SUPLA server address - (char *)ConfigManager->get(KEY_SUPLA_EMAIL)->getValue(), // Email address used to login to Supla Cloud - (char *)ConfigManager->get(KEY_SUPLA_AUTHKEY)->getValue()); // Authorization key - - ConfigManager->showAllValue(); - - WebServer->begin(); -} - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -void addRelayButton(int pinRelay, int pinButton, bool highIsOn) { - if (pinRelay != OFF_GPIO) { - relay.push_back(new Supla::Control::Relay(pinRelay, highIsOn)); - - int size = relay.size() - 1; - - switch (ConfigESP->getMemoryRelay(size + 1)) { - case MEMORY_RELAY_OFF: - relay[size]->setDefaultStateOff(); - break; - case MEMORY_RELAY_ON: - relay[size]->setDefaultStateOn(); - break; - case MEMORY_RELAY_RESTORE: - relay[size]->setDefaultStateRestore(); - break; - } - - relay[size]->keepTurnOnDuration(); - eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); - - button.push_back(new Supla::Control::Button(pinButton, true)); - if (pinButton != OFF_GPIO) { - button[size]->addAction(Supla::TOGGLE, *relay[size], ConfigESP->getLevel(size + 1, FUNCTION_BUTTON)); - button[size]->setSwNoiseFilterDelay(50); - } - } -} -#endif - -#ifdef SUPLA_DS18B20 -void addDS18B20MultiThermometer(int pinNumber) { - if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { - for (int i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); ++i) { - String ds_key = KEY_DS; - ds_key += i; - sensorDS.push_back(new DS18B20(pinNumber, ConfigManager->get(ds_key.c_str())->getValueBin(MAX_DS18B20_ADDRESS))); - supla_log(LOG_DEBUG, "Index %d - address %s", i, ConfigManager->get(ds_key.c_str())->getValue()); - } - } - else { - sensorDS.push_back(new DS18B20(ConfigESP->getGpio(FUNCTION_DS18B20))); - } -} -#endif - -#ifdef SUPLA_CONFIG -void addConfigESP(int pinNumberConfig, int pinLedConfig, int modeConfigButton, bool highIsOn) { - ConfigESP->addConfigESP(pinNumberConfig, pinLedConfig, modeConfigButton, highIsOn); -} -#endif - -#ifdef SUPLA_ROLLERSHUTTER -void addRolleShutter(int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn) { - RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); - if (pinButtonUp != OFF_GPIO) - RollerShutterButtonOpen.push_back(new Supla::Control::Button(pinButtonUp, true, true)); - if (pinButtonDown != OFF_GPIO) - RollerShutterButtonClose.push_back(new Supla::Control::Button(pinButtonDown, true, true)); - int size = RollerShutterRelay.size() - 1; - if (pinButtonUp != OFF_GPIO && pinButtonDown != OFF_GPIO) { - RollerShutterButtonOpen[size]->addAction(Supla::OPEN_OR_STOP, *RollerShutterRelay[size], Supla::ON_PRESS); - RollerShutterButtonClose[size]->addAction(Supla::CLOSE_OR_STOP, *RollerShutterRelay[size], Supla::ON_PRESS); - } - else if ((pinButtonUp == OFF_GPIO && pinButtonDown != OFF_GPIO) || (pinButtonUp != OFF_GPIO && pinButtonDown == OFF_GPIO)) { - RollerShutterButtonOpen[size]->addAction(Supla::STEP_BY_STEP, *RollerShutterRelay[size], Supla::ON_PRESS); - } - eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); -} - -void addRolleShutterMomentary(int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn) { - RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); - if (pinButtonUp != OFF_GPIO) - RollerShutterButtonOpen.push_back(new Supla::Control::Button(pinButtonUp, true, true)); - if (pinButtonDown != OFF_GPIO) - RollerShutterButtonClose.push_back(new Supla::Control::Button(pinButtonDown, true, true)); - int size = RollerShutterRelay.size() - 1; - if (pinButtonUp != OFF_GPIO && pinButtonDown != OFF_GPIO) { - RollerShutterButtonOpen[size]->addAction(Supla::OPEN, *RollerShutterRelay[size], Supla::ON_PRESS); - RollerShutterButtonOpen[size]->addAction(Supla::STOP, *RollerShutterRelay[size], Supla::ON_RELEASE); - - RollerShutterButtonClose[size]->addAction(Supla::CLOSE, *RollerShutterRelay[size], Supla::ON_PRESS); - RollerShutterButtonClose[size]->addAction(Supla::STOP, *RollerShutterRelay[size], Supla::ON_RELEASE); - } - eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); -} -#endif - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -std::vector relay; -std::vector button; -#endif - -#ifdef SUPLA_DS18B20 -std::vector sensorDS; -#endif - -#ifdef SUPLA_ROLLERSHUTTER -std::vector RollerShutterRelay; -std::vector RollerShutterButtonOpen; -std::vector RollerShutterButtonClose; -#endif -} // namespace GUI -} // namespace Supla - -SuplaConfigManager *ConfigManager = new SuplaConfigManager(); -SuplaConfigESP *ConfigESP = new SuplaConfigESP(); -SuplaWebServer *WebServer = new SuplaWebServer(); +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "SuplaDeviceGUI.h" +#include "SuplaConfigManager.h" +#include "SuplaGuiWiFi.h" + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +#define TIME_SAVE_PERIOD_SEK 30 // the time is given in seconds +#define STORAGE_OFFSET 0 +#include +Supla::Eeprom eeprom(STORAGE_OFFSET); +#endif + +namespace Supla { +namespace GUI { +void begin() { +#ifdef DEBUG_MODE + new Supla::Sensor::EspFreeHeap(); +#endif + + Supla::GUIESPWifi *wifi = new Supla::GUIESPWifi(ConfigManager->get(KEY_WIFI_SSID)->getValue(), ConfigManager->get(KEY_WIFI_PASS)->getValue()); + + wifi->enableBuffer(true); + wifi->enableSSL(false); + + String supla_hostname = ConfigManager->get(KEY_HOST_NAME)->getValue(); + supla_hostname.replace(" ", "_"); + wifi->setHostName(supla_hostname.c_str()); + + SuplaDevice.setName((char *)ConfigManager->get(KEY_HOST_NAME)->getValue()); + + SuplaDevice.begin((char *)ConfigManager->get(KEY_SUPLA_GUID)->getValue(), // Global Unique Identifier + (char *)ConfigManager->get(KEY_SUPLA_SERVER)->getValue(), // SUPLA server address + (char *)ConfigManager->get(KEY_SUPLA_EMAIL)->getValue(), // Email address used to login to Supla Cloud + (char *)ConfigManager->get(KEY_SUPLA_AUTHKEY)->getValue()); // Authorization key + + ConfigManager->showAllValue(); + + WebServer->begin(); +} + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +void addRelayButton(int pinRelay, int pinButton, bool highIsOn) { + if (pinRelay != OFF_GPIO) { + relay.push_back(new Supla::Control::Relay(pinRelay, highIsOn)); + + int size = relay.size() - 1; + + switch (ConfigESP->getMemoryRelay(size + 1)) { + case MEMORY_RELAY_OFF: + relay[size]->setDefaultStateOff(); + break; + case MEMORY_RELAY_ON: + relay[size]->setDefaultStateOn(); + break; + case MEMORY_RELAY_RESTORE: + relay[size]->setDefaultStateRestore(); + break; + } + + relay[size]->keepTurnOnDuration(); + eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); + + button.push_back(new Supla::Control::Button(pinButton, true)); + if (pinButton != OFF_GPIO) { + button[size]->addAction(Supla::TOGGLE, *relay[size], ConfigESP->getLevel(size + 1, FUNCTION_BUTTON)); + button[size]->setSwNoiseFilterDelay(50); + } + } +} +#endif + +#ifdef SUPLA_DS18B20 +void addDS18B20MultiThermometer(int pinNumber) { + if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { + for (int i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); ++i) { + String ds_key = KEY_DS; + ds_key += i; + sensorDS.push_back(new DS18B20(pinNumber, ConfigManager->get(ds_key.c_str())->getValueBin(MAX_DS18B20_ADDRESS))); + supla_log(LOG_DEBUG, "Index %d - address %s", i, ConfigManager->get(ds_key.c_str())->getValue()); + } + } + else { + sensorDS.push_back(new DS18B20(ConfigESP->getGpio(FUNCTION_DS18B20))); + } +} +#endif + +#ifdef SUPLA_CONFIG +void addConfigESP(int pinNumberConfig, int pinLedConfig, int modeConfigButton, bool highIsOn) { + ConfigESP->addConfigESP(pinNumberConfig, pinLedConfig, modeConfigButton, highIsOn); +} +#endif + +#ifdef SUPLA_ROLLERSHUTTER +void addRolleShutter(int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn) { + RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); + if (pinButtonUp != OFF_GPIO) + RollerShutterButtonOpen.push_back(new Supla::Control::Button(pinButtonUp, true, true)); + if (pinButtonDown != OFF_GPIO) + RollerShutterButtonClose.push_back(new Supla::Control::Button(pinButtonDown, true, true)); + int size = RollerShutterRelay.size() - 1; + if (pinButtonUp != OFF_GPIO && pinButtonDown != OFF_GPIO) { + RollerShutterButtonOpen[size]->addAction(Supla::OPEN_OR_STOP, *RollerShutterRelay[size], Supla::ON_PRESS); + RollerShutterButtonClose[size]->addAction(Supla::CLOSE_OR_STOP, *RollerShutterRelay[size], Supla::ON_PRESS); + } + else if ((pinButtonUp == OFF_GPIO && pinButtonDown != OFF_GPIO) || (pinButtonUp != OFF_GPIO && pinButtonDown == OFF_GPIO)) { + RollerShutterButtonOpen[size]->addAction(Supla::STEP_BY_STEP, *RollerShutterRelay[size], Supla::ON_PRESS); + } + eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); +} + +void addRolleShutterMomentary(int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn) { + RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); + if (pinButtonUp != OFF_GPIO) + RollerShutterButtonOpen.push_back(new Supla::Control::Button(pinButtonUp, true, true)); + if (pinButtonDown != OFF_GPIO) + RollerShutterButtonClose.push_back(new Supla::Control::Button(pinButtonDown, true, true)); + int size = RollerShutterRelay.size() - 1; + if (pinButtonUp != OFF_GPIO && pinButtonDown != OFF_GPIO) { + RollerShutterButtonOpen[size]->addAction(Supla::OPEN, *RollerShutterRelay[size], Supla::ON_PRESS); + RollerShutterButtonOpen[size]->addAction(Supla::STOP, *RollerShutterRelay[size], Supla::ON_RELEASE); + + RollerShutterButtonClose[size]->addAction(Supla::CLOSE, *RollerShutterRelay[size], Supla::ON_PRESS); + RollerShutterButtonClose[size]->addAction(Supla::STOP, *RollerShutterRelay[size], Supla::ON_RELEASE); + } + eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); +} +#endif + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +std::vector relay; +std::vector button; +#endif + +#ifdef SUPLA_DS18B20 +std::vector sensorDS; +#endif + +#ifdef SUPLA_ROLLERSHUTTER +std::vector RollerShutterRelay; +std::vector RollerShutterButtonOpen; +std::vector RollerShutterButtonClose; +#endif +} // namespace GUI +} // namespace Supla + +SuplaConfigManager *ConfigManager = new SuplaConfigManager(); +SuplaConfigESP *ConfigESP = new SuplaConfigESP(); +SuplaWebServer *WebServer = new SuplaWebServer(); diff --git a/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h similarity index 96% rename from SuplaDeviceGUI.h rename to src/SuplaDeviceGUI.h index 2bc43268..8ecfc25a 100644 --- a/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -1,81 +1,81 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef SuplaDeviceGUI_h -#define SuplaDeviceGUI_h - -#include "GUI-Generic_Config.h" - -#include - -#include -#include -#include -#include "SuplaSensorDS18B20.h" - -#ifdef DEBUG_MODE -#include -#endif - -#include "SuplaConfigESP.h" -#include "SuplaConfigManager.h" -#include "SuplaWebServer.h" -#include "SuplaWebPageRelay.h" - -#include - -enum ConfigMode { CONFIG_MODE_10_ON_PRESSES, CONFIG_MODE_5SEK_HOLD }; - -namespace Supla { -namespace GUI { - -void begin(); - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -void addRelayButton(int pinRelay, int pinButton, bool highIsOn = true); - -extern std::vector relay; -extern std::vector button; -#endif - -#ifdef SUPLA_DS18B20 -void addDS18B20MultiThermometer(int pinNumber); - -extern std::vector sensorDS; -#endif - -#ifdef SUPLA_CONFIG -void addConfigESP(int pinNumberConfig, int pinLedConfig, int modeConfigButton, bool highIsOn); -#endif - -#ifdef SUPLA_ROLLERSHUTTER -void addRolleShutter( - int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn = true); -void addRolleShutterMomentary( - int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn = true); - -extern std::vector RollerShutterRelay; -extern std::vector RollerShutterButtonOpen; -extern std::vector RollerShutterButtonClose; -#endif -}; -}; - -extern SuplaConfigManager *ConfigManager; -extern SuplaConfigESP *ConfigESP; -extern SuplaWebServer *WebServer; - -#endif // SuplaDeviceGUI_h +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SuplaDeviceGUI_h +#define SuplaDeviceGUI_h + +#include "GUI-Generic_Config.h" + +#include + +#include +#include +#include +#include "SuplaSensorDS18B20.h" + +#ifdef DEBUG_MODE +#include +#endif + +#include "SuplaConfigESP.h" +#include "SuplaConfigManager.h" +#include "SuplaWebServer.h" +#include "SuplaWebPageRelay.h" + +#include + +enum ConfigMode { CONFIG_MODE_10_ON_PRESSES, CONFIG_MODE_5SEK_HOLD }; + +namespace Supla { +namespace GUI { + +void begin(); + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +void addRelayButton(int pinRelay, int pinButton, bool highIsOn = true); + +extern std::vector relay; +extern std::vector button; +#endif + +#ifdef SUPLA_DS18B20 +void addDS18B20MultiThermometer(int pinNumber); + +extern std::vector sensorDS; +#endif + +#ifdef SUPLA_CONFIG +void addConfigESP(int pinNumberConfig, int pinLedConfig, int modeConfigButton, bool highIsOn); +#endif + +#ifdef SUPLA_ROLLERSHUTTER +void addRolleShutter( + int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn = true); +void addRolleShutterMomentary( + int pinRelayUp, int pinRelayDown, int pinButtonUp, int pinButtonDown, bool highIsOn = true); + +extern std::vector RollerShutterRelay; +extern std::vector RollerShutterButtonOpen; +extern std::vector RollerShutterButtonClose; +#endif +}; +}; + +extern SuplaConfigManager *ConfigManager; +extern SuplaConfigESP *ConfigESP; +extern SuplaWebServer *WebServer; + +#endif // SuplaDeviceGUI_h diff --git a/SuplaGuiWiFi.h b/src/SuplaGuiWiFi.h similarity index 100% rename from SuplaGuiWiFi.h rename to src/SuplaGuiWiFi.h diff --git a/SuplaSensorDS18B20.cpp b/src/SuplaSensorDS18B20.cpp similarity index 96% rename from SuplaSensorDS18B20.cpp rename to src/SuplaSensorDS18B20.cpp index 874f6a7b..a5f0a3a1 100644 --- a/SuplaSensorDS18B20.cpp +++ b/src/SuplaSensorDS18B20.cpp @@ -1,169 +1,169 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "SuplaSensorDS18B20.h" - -OneWireBus::OneWireBus(uint8_t pinNumberConfig) - : oneWire(pinNumberConfig), pin(pinNumberConfig), nextBus(nullptr), lastReadTime(0) { - supla_log(LOG_DEBUG, "Initializing OneWire bus at pin %d", pinNumberConfig); - sensors.setOneWire(&oneWire); - sensors.begin(); - if (sensors.isParasitePowerMode()) { - supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is ON", pinNumberConfig); - } else { - supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is OFF", pinNumberConfig); - } - - supla_log(LOG_DEBUG, "OneWire(pin %d) Found %d devices:", pinNumberConfig, - sensors.getDeviceCount()); - - // report parasite power requirements - - DeviceAddress address; - char strAddr[64]; - for (int i = 0; i < sensors.getDeviceCount(); i++) { - if (!sensors.getAddress(address, i)) { - supla_log(LOG_DEBUG, "Unable to find address for Device %d", i); - } else { - /*sprintf( - strAddr, - "{0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X}", - address[0], - address[1], - address[2], - address[3], - address[4], - address[5], - address[6], - address[7]); - supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr);*/ - sensors.setResolution(address, 12); - } - delay(0); - } - sensors.setWaitForConversion(true); - sensors.requestTemperatures(); - sensors.setWaitForConversion(false); -} - -int8_t OneWireBus::getIndex(uint8_t *deviceAddress) { - DeviceAddress address; - for (int i = 0; i < sensors.getDeviceCount(); i++) { - if (sensors.getAddress(address, i)) { - bool found = true; - for (int j = 0; j < 8; j++) { - if (deviceAddress[j] != address[j]) { - found = false; - } - } - if (found) { - return i; - } - } - } - return -1; -} - -DS18B20::DS18B20(uint8_t pin, uint8_t *deviceAddress) { - OneWireBus *bus = oneWireBus; - OneWireBus *prevBus = nullptr; - address[0] = 0; - lastValidValue = TEMPERATURE_NOT_AVAILABLE; - retryCounter = 0; - - if (bus) { - while (bus) { - if (bus->pin == pin) { - myBus = bus; - break; - } - prevBus = bus; - bus = bus->nextBus; - } - } - - // There is no OneWire bus created yet for this pin - if (!bus) { - supla_log(LOG_DEBUG, "Creating OneWire bus for pin: %d", pin); - myBus = new OneWireBus(pin); - if (prevBus) { - prevBus->nextBus = myBus; - } else { - oneWireBus = myBus; - } - } - if (deviceAddress == nullptr) { - supla_log(LOG_DEBUG, "Device address not provided. Using device from index 0"); - } else { - memcpy(address, deviceAddress, 8); - } -} - -void DS18B20::iterateAlways() { - if (myBus->lastReadTime + 10000 < millis()) { - myBus->sensors.requestTemperatures(); - myBus->lastReadTime = millis(); - } - if (myBus->lastReadTime + 5000 < millis() && (lastReadTime != myBus->lastReadTime)) { - channel.setNewValue(getValue()); - lastReadTime = myBus->lastReadTime; - } -} - -double DS18B20::getValue() { - double value = TEMPERATURE_NOT_AVAILABLE; - if (address[0] == 0) { - value = myBus->sensors.getTempCByIndex(0); - } else { - value = myBus->sensors.getTempC(address); - } - - if (value == DEVICE_DISCONNECTED_C || value == 85.0) { - value = TEMPERATURE_NOT_AVAILABLE; - } - - if (value == TEMPERATURE_NOT_AVAILABLE) { - retryCounter++; - if (retryCounter > 3) { - retryCounter = 0; - } else { - value = lastValidValue; - } - } else { - retryCounter = 0; - } - lastValidValue = value; - - return value; -} - -void DS18B20::onInit() { - channel.setNewValue(getValue()); -} - -uint8_t DS18B20::getPin() { - return myBus->pin; -} - -void DS18B20::setDeviceAddress(uint8_t *deviceAddress) { - if (deviceAddress == nullptr) { - supla_log(LOG_DEBUG, "Device address not provided. Using device from index 0"); - } else { - memcpy(address, deviceAddress, 8); - } -} - -OneWireBus *DS18B20::oneWireBus = nullptr; +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "SuplaSensorDS18B20.h" + +OneWireBus::OneWireBus(uint8_t pinNumberConfig) + : oneWire(pinNumberConfig), pin(pinNumberConfig), nextBus(nullptr), lastReadTime(0) { + supla_log(LOG_DEBUG, "Initializing OneWire bus at pin %d", pinNumberConfig); + sensors.setOneWire(&oneWire); + sensors.begin(); + if (sensors.isParasitePowerMode()) { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is ON", pinNumberConfig); + } else { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is OFF", pinNumberConfig); + } + + supla_log(LOG_DEBUG, "OneWire(pin %d) Found %d devices:", pinNumberConfig, + sensors.getDeviceCount()); + + // report parasite power requirements + + DeviceAddress address; + char strAddr[64]; + for (int i = 0; i < sensors.getDeviceCount(); i++) { + if (!sensors.getAddress(address, i)) { + supla_log(LOG_DEBUG, "Unable to find address for Device %d", i); + } else { + /*sprintf( + strAddr, + "{0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X}", + address[0], + address[1], + address[2], + address[3], + address[4], + address[5], + address[6], + address[7]); + supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr);*/ + sensors.setResolution(address, 12); + } + delay(0); + } + sensors.setWaitForConversion(true); + sensors.requestTemperatures(); + sensors.setWaitForConversion(false); +} + +int8_t OneWireBus::getIndex(uint8_t *deviceAddress) { + DeviceAddress address; + for (int i = 0; i < sensors.getDeviceCount(); i++) { + if (sensors.getAddress(address, i)) { + bool found = true; + for (int j = 0; j < 8; j++) { + if (deviceAddress[j] != address[j]) { + found = false; + } + } + if (found) { + return i; + } + } + } + return -1; +} + +DS18B20::DS18B20(uint8_t pin, uint8_t *deviceAddress) { + OneWireBus *bus = oneWireBus; + OneWireBus *prevBus = nullptr; + address[0] = 0; + lastValidValue = TEMPERATURE_NOT_AVAILABLE; + retryCounter = 0; + + if (bus) { + while (bus) { + if (bus->pin == pin) { + myBus = bus; + break; + } + prevBus = bus; + bus = bus->nextBus; + } + } + + // There is no OneWire bus created yet for this pin + if (!bus) { + supla_log(LOG_DEBUG, "Creating OneWire bus for pin: %d", pin); + myBus = new OneWireBus(pin); + if (prevBus) { + prevBus->nextBus = myBus; + } else { + oneWireBus = myBus; + } + } + if (deviceAddress == nullptr) { + supla_log(LOG_DEBUG, "Device address not provided. Using device from index 0"); + } else { + memcpy(address, deviceAddress, 8); + } +} + +void DS18B20::iterateAlways() { + if (myBus->lastReadTime + 10000 < millis()) { + myBus->sensors.requestTemperatures(); + myBus->lastReadTime = millis(); + } + if (myBus->lastReadTime + 5000 < millis() && (lastReadTime != myBus->lastReadTime)) { + channel.setNewValue(getValue()); + lastReadTime = myBus->lastReadTime; + } +} + +double DS18B20::getValue() { + double value = TEMPERATURE_NOT_AVAILABLE; + if (address[0] == 0) { + value = myBus->sensors.getTempCByIndex(0); + } else { + value = myBus->sensors.getTempC(address); + } + + if (value == DEVICE_DISCONNECTED_C || value == 85.0) { + value = TEMPERATURE_NOT_AVAILABLE; + } + + if (value == TEMPERATURE_NOT_AVAILABLE) { + retryCounter++; + if (retryCounter > 3) { + retryCounter = 0; + } else { + value = lastValidValue; + } + } else { + retryCounter = 0; + } + lastValidValue = value; + + return value; +} + +void DS18B20::onInit() { + channel.setNewValue(getValue()); +} + +uint8_t DS18B20::getPin() { + return myBus->pin; +} + +void DS18B20::setDeviceAddress(uint8_t *deviceAddress) { + if (deviceAddress == nullptr) { + supla_log(LOG_DEBUG, "Device address not provided. Using device from index 0"); + } else { + memcpy(address, deviceAddress, 8); + } +} + +OneWireBus *DS18B20::oneWireBus = nullptr; diff --git a/SuplaSensorDS18B20.h b/src/SuplaSensorDS18B20.h similarity index 96% rename from SuplaSensorDS18B20.h rename to src/SuplaSensorDS18B20.h index 5bbf3696..b80b2e48 100644 --- a/SuplaSensorDS18B20.h +++ b/src/SuplaSensorDS18B20.h @@ -1,59 +1,59 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef SuplaSensorDS18B20_h -#define SuplaSensorDS18B20_h - -#include -#include -#include - -#include -#include - - -class OneWireBus { - public: - OneWireBus(uint8_t pinNumberConfig); - int8_t getIndex(uint8_t *deviceAddress); - - uint8_t pin; - OneWireBus *nextBus; - unsigned long lastReadTime; - DallasTemperature sensors; - - protected: - OneWire oneWire; -}; - -class DS18B20 : public Supla::Sensor::Thermometer { - public: - DS18B20(uint8_t pin, uint8_t *deviceAddress = nullptr); - void iterateAlways(); - double getValue(); - void onInit(); - uint8_t getPin(); - void setDeviceAddress(uint8_t *deviceAddress = nullptr); - - protected: - static OneWireBus *oneWireBus; - OneWireBus *myBus; - DeviceAddress address; - int8_t retryCounter; - double lastValidValue; -}; - -#endif // SuplaSensorDS18B20_h +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SuplaSensorDS18B20_h +#define SuplaSensorDS18B20_h + +#include +#include +#include + +#include +#include + + +class OneWireBus { + public: + OneWireBus(uint8_t pinNumberConfig); + int8_t getIndex(uint8_t *deviceAddress); + + uint8_t pin; + OneWireBus *nextBus; + unsigned long lastReadTime; + DallasTemperature sensors; + + protected: + OneWire oneWire; +}; + +class DS18B20 : public Supla::Sensor::Thermometer { + public: + DS18B20(uint8_t pin, uint8_t *deviceAddress = nullptr); + void iterateAlways(); + double getValue(); + void onInit(); + uint8_t getPin(); + void setDeviceAddress(uint8_t *deviceAddress = nullptr); + + protected: + static OneWireBus *oneWireBus; + OneWireBus *myBus; + DeviceAddress address; + int8_t retryCounter; + double lastValidValue; +}; + +#endif // SuplaSensorDS18B20_h diff --git a/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp similarity index 100% rename from SuplaTemplateBoard.cpp rename to src/SuplaTemplateBoard.cpp diff --git a/SuplaTemplateBoard.h b/src/SuplaTemplateBoard.h similarity index 100% rename from SuplaTemplateBoard.h rename to src/SuplaTemplateBoard.h diff --git a/SuplaWebPageConfig.cpp b/src/SuplaWebPageConfig.cpp similarity index 97% rename from SuplaWebPageConfig.cpp rename to src/SuplaWebPageConfig.cpp index c613d939..b7a3fa10 100644 --- a/SuplaWebPageConfig.cpp +++ b/src/SuplaWebPageConfig.cpp @@ -1,165 +1,165 @@ -#include "SuplaWebPageConfig.h" -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" -#include "SuplaCommonPROGMEM.h" -#include "GUIGenericCommon.h" - -SuplaWebPageConfig *WebPageConfig = new SuplaWebPageConfig(); - -SuplaWebPageConfig::SuplaWebPageConfig() { -} - -void SuplaWebPageConfig::createWebPageConfig() { - String path; - path = PATH_START; - path += PATH_CONFIG; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageConfig::handleConfig, this)); - path = PATH_START; - path += PATH_SAVE_CONFIG; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageConfig::handleConfigSave, this)); -} - -void SuplaWebPageConfig::handleConfig() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_config(0)); -} - -void SuplaWebPageConfig::handleConfigSave() { - // Serial.println(F("HTTP_POST - metoda handleConfigSave")); - - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - - String key, input; - input = INPUT_CFG_LED_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_CFG_LED) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_LED)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), 1, FUNCTION_CFG_LED, 1); - } - else if (ConfigESP->getGpio(FUNCTION_CFG_LED) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CFG_LED) { - key = GPIO; - key += ConfigESP->getGpio(FUNCTION_CFG_LED); - input = INPUT_CFG_LED_LEVEL; - ConfigManager->setElement(key.c_str(), LEVEL, WebServer->httpServer.arg(input).toInt()); - } - else { - WebServer->sendContent(supla_webpage_config(6)); - return; - } - } - - input = INPUT_CFG_BTN_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CFG_BUTTON)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CFG_BUTTON); - } - else if (ConfigESP->checkBusyGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_BUTTON) == false) { - // ConfigManager->setElement(key.c_str(), CFG, 1); - ConfigESP->setGpio(key.toInt(), FUNCTION_CFG_BUTTON); - } - else { - WebServer->sendContent(supla_webpage_config(6)); - return; - } - } - - if (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), FUNCTION_CFG_BUTTON); - } - - /*#ifdef SUPLA_BUTTON - for (int i = 1; i <= ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); i++) { - key = GPIO; - key += ConfigESP->getGpio(i, FUNCTION_BUTTON); - if (ConfigESP->getGpio(i, FUNCTION_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) - { if (ConfigManager->get(key.c_str())->getElement(CFG).toInt() == 1) { ConfigManager->setElement(key.c_str(), CFG, 0); - } - } - } - #endif*/ - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_config(1)); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_config(2)); - break; - } -} - -String SuplaWebPageConfig::supla_webpage_config(int save) { - uint8_t selected, suported; - String page = ""; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_CONFIG); - page += F("

"); - page += S_GPIO_SETTINGS_FOR_CONFIG; - page += F("

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_CFG_LED_GPIO, FUNCTION_CFG_LED); - page += F(""); - - if (selected != 17) { - page += F(""); - } - page += F(""); - page += WebServer->selectGPIO(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); - page += F(""); - page += F("
"); - page += F("
"); - page += F(""); - return page; -} +#include "SuplaWebPageConfig.h" +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" +#include "SuplaCommonPROGMEM.h" +#include "GUIGenericCommon.h" + +SuplaWebPageConfig *WebPageConfig = new SuplaWebPageConfig(); + +SuplaWebPageConfig::SuplaWebPageConfig() { +} + +void SuplaWebPageConfig::createWebPageConfig() { + String path; + path = PATH_START; + path += PATH_CONFIG; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageConfig::handleConfig, this)); + path = PATH_START; + path += PATH_SAVE_CONFIG; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageConfig::handleConfigSave, this)); +} + +void SuplaWebPageConfig::handleConfig() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_config(0)); +} + +void SuplaWebPageConfig::handleConfigSave() { + // Serial.println(F("HTTP_POST - metoda handleConfigSave")); + + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String key, input; + input = INPUT_CFG_LED_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_CFG_LED) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_LED)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), 1, FUNCTION_CFG_LED, 1); + } + else if (ConfigESP->getGpio(FUNCTION_CFG_LED) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CFG_LED) { + key = GPIO; + key += ConfigESP->getGpio(FUNCTION_CFG_LED); + input = INPUT_CFG_LED_LEVEL; + ConfigManager->setElement(key.c_str(), LEVEL, WebServer->httpServer.arg(input).toInt()); + } + else { + WebServer->sendContent(supla_webpage_config(6)); + return; + } + } + + input = INPUT_CFG_BTN_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CFG_BUTTON)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CFG_BUTTON); + } + else if (ConfigESP->checkBusyGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_BUTTON) == false) { + // ConfigManager->setElement(key.c_str(), CFG, 1); + ConfigESP->setGpio(key.toInt(), FUNCTION_CFG_BUTTON); + } + else { + WebServer->sendContent(supla_webpage_config(6)); + return; + } + } + + if (ConfigESP->getGpio(FUNCTION_CFG_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), FUNCTION_CFG_BUTTON); + } + + /*#ifdef SUPLA_BUTTON + for (int i = 1; i <= ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); i++) { + key = GPIO; + key += ConfigESP->getGpio(i, FUNCTION_BUTTON); + if (ConfigESP->getGpio(i, FUNCTION_BUTTON) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) + { if (ConfigManager->get(key.c_str())->getElement(CFG).toInt() == 1) { ConfigManager->setElement(key.c_str(), CFG, 0); + } + } + } + #endif*/ + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Config save")); + WebServer->sendContent(supla_webpage_config(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_config(2)); + break; + } +} + +String SuplaWebPageConfig::supla_webpage_config(int save) { + uint8_t selected, suported; + String page = ""; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_CONFIG); + page += F("

"); + page += S_GPIO_SETTINGS_FOR_CONFIG; + page += F("

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_CFG_LED_GPIO, FUNCTION_CFG_LED); + page += F(""); + + if (selected != 17) { + page += F(""); + } + page += F(""); + page += WebServer->selectGPIO(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); + page += F(""); + page += F("
"); + page += F("
"); + page += F(""); + return page; +} diff --git a/SuplaWebPageConfig.h b/src/SuplaWebPageConfig.h similarity index 95% rename from SuplaWebPageConfig.h rename to src/SuplaWebPageConfig.h index 66a93a18..9e3aaac7 100644 --- a/SuplaWebPageConfig.h +++ b/src/SuplaWebPageConfig.h @@ -1,25 +1,25 @@ -#ifndef SuplaWebPageConfig_h -#define SuplaWebPageConfig_h - -#include "SuplaWebServer.h" -#include "SuplaDeviceGUI.h" - -#define PATH_CONFIG "config" -#define PATH_SAVE_CONFIG "saveconfig" -#define INPUT_CFG_LED_GPIO "cfgl" -#define INPUT_CFG_BTN_GPIO "cfgb" -#define INPUT_CFG_LED_LEVEL "icll" - -class SuplaWebPageConfig { - public: - SuplaWebPageConfig(); - void createWebPageConfig(); - void handleConfig(); - void handleConfigSave(); - - private: - String supla_webpage_config(int save); -}; - -extern SuplaWebPageConfig *WebPageConfig; -#endif // SuplaWebPageConfig_h +#ifndef SuplaWebPageConfig_h +#define SuplaWebPageConfig_h + +#include "SuplaWebServer.h" +#include "SuplaDeviceGUI.h" + +#define PATH_CONFIG "config" +#define PATH_SAVE_CONFIG "saveconfig" +#define INPUT_CFG_LED_GPIO "cfgl" +#define INPUT_CFG_BTN_GPIO "cfgb" +#define INPUT_CFG_LED_LEVEL "icll" + +class SuplaWebPageConfig { + public: + SuplaWebPageConfig(); + void createWebPageConfig(); + void handleConfig(); + void handleConfigSave(); + + private: + String supla_webpage_config(int save); +}; + +extern SuplaWebPageConfig *WebPageConfig; +#endif // SuplaWebPageConfig_h diff --git a/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp similarity index 100% rename from SuplaWebPageControl.cpp rename to src/SuplaWebPageControl.cpp diff --git a/SuplaWebPageControl.h b/src/SuplaWebPageControl.h similarity index 96% rename from SuplaWebPageControl.h rename to src/SuplaWebPageControl.h index ab47ee94..88e17b98 100644 --- a/SuplaWebPageControl.h +++ b/src/SuplaWebPageControl.h @@ -1,46 +1,46 @@ -#ifndef SuplaWebPageControl_h -#define SuplaWebPageControl_h - -#include "SuplaWebServer.h" -#include "SuplaDeviceGUI.h" - -#define PATH_CONTROL "control" -#define PATH_SAVE_CONTROL "savecontrol" -#define PATH_BUTTON_SET "setbutton" -#define PATH_SAVE_BUTTON_SET "savesetbutton" -#define INPUT_TRIGGER "trs" -#define INPUT_BUTTON_SET "bts" -#define INPUT_BUTTON_GPIO "btg" -#define INPUT_BUTTON_LEVEL "icl" -#define INPUT_LIMIT_SWITCH_GPIO "lsg" -#define INPUT_MAX_BUTTON "mbt" -#define INPUT_MAX_LIMIT_SWITCH "mls" - -/*enum _trigger_button { - TRIGGER_PRESS, - TRIGGER_RELEASE, - TRIGGER_CHANGE -};*/ - -class SuplaWebPageControl { - public: - void createWebPageControl(); - void handleControl(); - void handleControlSave(); - -#if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_RSUPLA_BUTTONELAY) || defined(SUPLA_ROLLERSHUTTER)) - void handleButtonSet(); - void handleButtonSaveSet(); -#endif - - private: - String supla_webpage_control(int save); - -#ifdef SUPLA_BUTTON - String supla_webpage_button_set(int save); -#endif - -}; - -extern SuplaWebPageControl* WebPageControl; -#endif // SuplaWebPageControl_h +#ifndef SuplaWebPageControl_h +#define SuplaWebPageControl_h + +#include "SuplaWebServer.h" +#include "SuplaDeviceGUI.h" + +#define PATH_CONTROL "control" +#define PATH_SAVE_CONTROL "savecontrol" +#define PATH_BUTTON_SET "setbutton" +#define PATH_SAVE_BUTTON_SET "savesetbutton" +#define INPUT_TRIGGER "trs" +#define INPUT_BUTTON_SET "bts" +#define INPUT_BUTTON_GPIO "btg" +#define INPUT_BUTTON_LEVEL "icl" +#define INPUT_LIMIT_SWITCH_GPIO "lsg" +#define INPUT_MAX_BUTTON "mbt" +#define INPUT_MAX_LIMIT_SWITCH "mls" + +/*enum _trigger_button { + TRIGGER_PRESS, + TRIGGER_RELEASE, + TRIGGER_CHANGE +};*/ + +class SuplaWebPageControl { + public: + void createWebPageControl(); + void handleControl(); + void handleControlSave(); + +#if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_RSUPLA_BUTTONELAY) || defined(SUPLA_ROLLERSHUTTER)) + void handleButtonSet(); + void handleButtonSaveSet(); +#endif + + private: + String supla_webpage_control(int save); + +#ifdef SUPLA_BUTTON + String supla_webpage_button_set(int save); +#endif + +}; + +extern SuplaWebPageControl* WebPageControl; +#endif // SuplaWebPageControl_h diff --git a/SuplaWebPageRelay.cpp b/src/SuplaWebPageRelay.cpp similarity index 97% rename from SuplaWebPageRelay.cpp rename to src/SuplaWebPageRelay.cpp index a1b453fe..fc84be82 100644 --- a/SuplaWebPageRelay.cpp +++ b/src/SuplaWebPageRelay.cpp @@ -1,283 +1,283 @@ -#include "SuplaWebPageRelay.h" -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" -#include "SuplaCommonPROGMEM.h" -#include "GUIGenericCommon.h" - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) -SuplaWebPageRelay *WebPageRelay = new SuplaWebPageRelay(); - -SuplaWebPageRelay::SuplaWebPageRelay() { -} - -void SuplaWebPageRelay::createWebPageRelay() { - String path; - path += PATH_START; - path += PATH_RELAY; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelay, this)); - path = PATH_START; - path += PATH_SAVE_RELAY; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySave, this)); - - for (uint8_t i = 1; i <= MAX_GPIO; i++) { - path = PATH_START; - path += PATH_RELAY_SET; - path += i; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySet, this)); - - path = PATH_START; - path += PATH_SAVE_RELAY_SET; - path += i; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySaveSet, this)); - } -} - -void SuplaWebPageRelay::handleRelay() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_relay(0)); -} - -void SuplaWebPageRelay::handleRelaySave() { - // Serial.println(F("HTTP_POST - metoda handleRelaySave")); - - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - String key, input; - uint8_t nr, current_value, last_value; - - last_value = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); - current_value = WebServer->httpServer.arg(INPUT_MAX_RELAY).toInt(); - - if (last_value > 0) { - for (nr = 1; nr <= last_value; nr++) { - input = INPUT_RELAY_GPIO; - input += nr; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(nr, FUNCTION_RELAY) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { - ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_RELAY)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_RELAY, 1); - } - else if (ConfigESP->getGpio(nr, FUNCTION_RELAY) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_RELAY) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_RELAY, ConfigESP->getLevel(nr, FUNCTION_RELAY)); - } - else { - WebServer->sendContent(supla_webpage_relay(6)); - return; - } - } - } - } - if (strcmp(WebServer->httpServer.arg(INPUT_MAX_RELAY).c_str(), "") != 0) { - ConfigManager->set(KEY_MAX_RELAY, WebServer->httpServer.arg(INPUT_MAX_RELAY).c_str()); - } - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_relay(1)); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_relay(2)); - break; - } -} - -String SuplaWebPageRelay::supla_webpage_relay(int save) { - String key; - uint8_t selected, suported, nr; - - String pagerelay = ""; - pagerelay += WebServer->SuplaSaveResult(save); - pagerelay += WebServer->SuplaJavaScript(PATH_RELAY); - pagerelay += F("

"); - pagerelay += S_GPIO_SETTINGS_FOR_RELAYS; - pagerelay += F("

"); - pagerelay += F(""); - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { - pagerelay += F(""); - pagerelay += WebServer->selectGPIO(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr); - pagerelay += F(""); - } - pagerelay += F("
"); - pagerelay += F("
"); - pagerelay += F(""); - return pagerelay; -} - -void SuplaWebPageRelay::handleRelaySet() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_relay_set(0)); -} - -void SuplaWebPageRelay::handleRelaySaveSet() { - // Serial.println(F("HTTP_POST - metoda handleRelaySaveSet")); - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - - String readUrl, nr_relay, key, input; - uint8_t place; - - String path = PATH_START; - path += PATH_SAVE_RELAY_SET; - readUrl = WebServer->httpServer.uri(); - - place = readUrl.indexOf(path); - nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); - key = GPIO; - key += ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY); - - input = INPUT_RELAY_MEMORY; - input += nr_relay; - ConfigManager->setElement(key.c_str(), MEMORY, WebServer->httpServer.arg(input).toInt()); - - input = INPUT_RELAY_LEVEL; - input += nr_relay; - ConfigManager->setElement(key.c_str(), LEVEL, WebServer->httpServer.arg(input).toInt()); - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Dane zapisane")); - WebServer->sendContent(supla_webpage_relay(1)); - break; - - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_relay(2)); - break; - } -} - -String SuplaWebPageRelay::supla_webpage_relay_set(int save) { - String readUrl, nr_relay, key; - uint8_t place, selected, suported; - - String path = PATH_START; - path += PATH_RELAY_SET; - readUrl = WebServer->httpServer.uri(); - - place = readUrl.indexOf(path); - nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); - - String page = ""; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_RELAY); - uint8_t relays = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); - if (nr_relay.toInt() <= relays && ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY) != OFF_GPIO) { - page += F("

"); - page += S_RELAY_NR_SETTINGS; - page += F(" "); - page += nr_relay; - page += F("

"); - page += F(""); - page += F(""); - page += F("
"); - } - else { - page += F("

"); - page += S_NO_RELAY_NR; - page += F(" "); - page += nr_relay; - page += F("

"); - } - page += F("
"); - page += F("
"); - - return page; -} -#endif +#include "SuplaWebPageRelay.h" +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" +#include "SuplaCommonPROGMEM.h" +#include "GUIGenericCommon.h" + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) +SuplaWebPageRelay *WebPageRelay = new SuplaWebPageRelay(); + +SuplaWebPageRelay::SuplaWebPageRelay() { +} + +void SuplaWebPageRelay::createWebPageRelay() { + String path; + path += PATH_START; + path += PATH_RELAY; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelay, this)); + path = PATH_START; + path += PATH_SAVE_RELAY; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySave, this)); + + for (uint8_t i = 1; i <= MAX_GPIO; i++) { + path = PATH_START; + path += PATH_RELAY_SET; + path += i; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySet, this)); + + path = PATH_START; + path += PATH_SAVE_RELAY_SET; + path += i; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySaveSet, this)); + } +} + +void SuplaWebPageRelay::handleRelay() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_relay(0)); +} + +void SuplaWebPageRelay::handleRelaySave() { + // Serial.println(F("HTTP_POST - metoda handleRelaySave")); + + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + String key, input; + uint8_t nr, current_value, last_value; + + last_value = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); + current_value = WebServer->httpServer.arg(INPUT_MAX_RELAY).toInt(); + + if (last_value > 0) { + for (nr = 1; nr <= last_value; nr++) { + input = INPUT_RELAY_GPIO; + input += nr; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(nr, FUNCTION_RELAY) != WebServer->httpServer.arg(input).toInt() || + WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { + ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_RELAY)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_RELAY, 1); + } + else if (ConfigESP->getGpio(nr, FUNCTION_RELAY) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_RELAY) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_RELAY, ConfigESP->getLevel(nr, FUNCTION_RELAY)); + } + else { + WebServer->sendContent(supla_webpage_relay(6)); + return; + } + } + } + } + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_RELAY).c_str(), "") != 0) { + ConfigManager->set(KEY_MAX_RELAY, WebServer->httpServer.arg(INPUT_MAX_RELAY).c_str()); + } + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Config save")); + WebServer->sendContent(supla_webpage_relay(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_relay(2)); + break; + } +} + +String SuplaWebPageRelay::supla_webpage_relay(int save) { + String key; + uint8_t selected, suported, nr; + + String pagerelay = ""; + pagerelay += WebServer->SuplaSaveResult(save); + pagerelay += WebServer->SuplaJavaScript(PATH_RELAY); + pagerelay += F("

"); + pagerelay += S_GPIO_SETTINGS_FOR_RELAYS; + pagerelay += F("

"); + pagerelay += F(""); + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { + pagerelay += F(""); + pagerelay += WebServer->selectGPIO(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr); + pagerelay += F(""); + } + pagerelay += F("
"); + pagerelay += F("
"); + pagerelay += F(""); + return pagerelay; +} + +void SuplaWebPageRelay::handleRelaySet() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_relay_set(0)); +} + +void SuplaWebPageRelay::handleRelaySaveSet() { + // Serial.println(F("HTTP_POST - metoda handleRelaySaveSet")); + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String readUrl, nr_relay, key, input; + uint8_t place; + + String path = PATH_START; + path += PATH_SAVE_RELAY_SET; + readUrl = WebServer->httpServer.uri(); + + place = readUrl.indexOf(path); + nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); + key = GPIO; + key += ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY); + + input = INPUT_RELAY_MEMORY; + input += nr_relay; + ConfigManager->setElement(key.c_str(), MEMORY, WebServer->httpServer.arg(input).toInt()); + + input = INPUT_RELAY_LEVEL; + input += nr_relay; + ConfigManager->setElement(key.c_str(), LEVEL, WebServer->httpServer.arg(input).toInt()); + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Dane zapisane")); + WebServer->sendContent(supla_webpage_relay(1)); + break; + + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_relay(2)); + break; + } +} + +String SuplaWebPageRelay::supla_webpage_relay_set(int save) { + String readUrl, nr_relay, key; + uint8_t place, selected, suported; + + String path = PATH_START; + path += PATH_RELAY_SET; + readUrl = WebServer->httpServer.uri(); + + place = readUrl.indexOf(path); + nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); + + String page = ""; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_RELAY); + uint8_t relays = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); + if (nr_relay.toInt() <= relays && ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY) != OFF_GPIO) { + page += F("

"); + page += S_RELAY_NR_SETTINGS; + page += F(" "); + page += nr_relay; + page += F("

"); + page += F(""); + page += F(""); + page += F("
"); + } + else { + page += F("

"); + page += S_NO_RELAY_NR; + page += F(" "); + page += nr_relay; + page += F("

"); + } + page += F("
"); + page += F("
"); + + return page; +} +#endif diff --git a/SuplaWebPageRelay.h b/src/SuplaWebPageRelay.h similarity index 95% rename from SuplaWebPageRelay.h rename to src/SuplaWebPageRelay.h index dfeeaad4..88c71aa5 100644 --- a/SuplaWebPageRelay.h +++ b/src/SuplaWebPageRelay.h @@ -1,42 +1,42 @@ -#ifndef SuplaWebPageRelay_h -#define SuplaWebPageRelay_h - -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" - -#define PATH_RELAY "relay" -#define PATH_SAVE_RELAY "saverelay" -#define PATH_RELAY_SET "setrelay" -#define PATH_SAVE_RELAY_SET "savesetrelay" -#define INPUT_MAX_RELAY "mrl" -#define INPUT_RELAY_GPIO "rlg" -#define INPUT_RELAY_LEVEL "irl" -#define INPUT_RELAY_MEMORY "irm" -#define INPUT_RELAY_DURATION "ird" -#define INPUT_ROLLERSHUTTER "irsr" - -enum _memory_relay { - MEMORY_RELAY_OFF, - MEMORY_RELAY_ON, - MEMORY_RELAY_RESTORE -}; - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) - -class SuplaWebPageRelay { - public: - SuplaWebPageRelay(); - void createWebPageRelay(); - void handleRelay(); - void handleRelaySave(); - void handleRelaySet(); - void handleRelaySaveSet(); - - private: - String supla_webpage_relay_set(int save); - String supla_webpage_relay(int save); -}; - -extern SuplaWebPageRelay* WebPageRelay; -#endif -#endif // SuplaWebPageRelay_h +#ifndef SuplaWebPageRelay_h +#define SuplaWebPageRelay_h + +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" + +#define PATH_RELAY "relay" +#define PATH_SAVE_RELAY "saverelay" +#define PATH_RELAY_SET "setrelay" +#define PATH_SAVE_RELAY_SET "savesetrelay" +#define INPUT_MAX_RELAY "mrl" +#define INPUT_RELAY_GPIO "rlg" +#define INPUT_RELAY_LEVEL "irl" +#define INPUT_RELAY_MEMORY "irm" +#define INPUT_RELAY_DURATION "ird" +#define INPUT_ROLLERSHUTTER "irsr" + +enum _memory_relay { + MEMORY_RELAY_OFF, + MEMORY_RELAY_ON, + MEMORY_RELAY_RESTORE +}; + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) + +class SuplaWebPageRelay { + public: + SuplaWebPageRelay(); + void createWebPageRelay(); + void handleRelay(); + void handleRelaySave(); + void handleRelaySet(); + void handleRelaySaveSet(); + + private: + String supla_webpage_relay_set(int save); + String supla_webpage_relay(int save); +}; + +extern SuplaWebPageRelay* WebPageRelay; +#endif +#endif // SuplaWebPageRelay_h diff --git a/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp similarity index 97% rename from SuplaWebPageSensor.cpp rename to src/SuplaWebPageSensor.cpp index c1f46178..aa85d1b4 100644 --- a/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -1,933 +1,933 @@ -#include "SuplaWebPageSensor.h" - -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" -#include "SuplaCommonPROGMEM.h" -#include "GUIGenericCommon.h" - -SuplaWebPageSensor *WebPageSensor = new SuplaWebPageSensor(); - -SuplaWebPageSensor::SuplaWebPageSensor() { -} - -void SuplaWebPageSensor::createWebPageSensor() { - String path; - -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - path = PATH_START; - path += PATH_1WIRE; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handle1Wire, this)); - path = PATH_START; - path += PATH_SAVE_1WIRE; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handle1WireSave, this)); -#ifdef SUPLA_DS18B20 - path = PATH_START; - path += PATH_MULTI_DS; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSearchDS, this)); - path = PATH_START; - path += PATH_SAVE_MULTI_DS; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleDSSave, this)); -#endif -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) - path = PATH_START; - path += PATH_I2C; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2c, this)); - path = PATH_START; - path += PATH_SAVE_I2C; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2cSave, this)); -#endif - -#if defined(SUPLA_MAX6675) - path = PATH_START; - path += PATH_SPI; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSpi, this)); - path = PATH_START; - path += PATH_SAVE_SPI; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSpiSave, this)); -#endif -} - -#ifdef SUPLA_DS18B20 -void SuplaWebPageSensor::handleSearchDS() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_search(0)); -} - -void SuplaWebPageSensor::handleDSSave() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { - String ds_key = KEY_DS; - String ds_name_key = KEY_DS_NAME; - ds_key += i; - ds_name_key += i; - - String ds = F("dschlid"); - String ds_name = F("dsnameid"); - ds += i; - ds_name += i; - - ConfigManager->set(ds_key.c_str(), WebServer->httpServer.arg(ds).c_str()); - ConfigManager->set(ds_name_key.c_str(), WebServer->httpServer.arg(ds_name).c_str()); - - Supla::GUI::sensorDS[i]->setDeviceAddress(ConfigManager->get(ds_key.c_str())->getValueBin(MAX_DS18B20_ADDRESS)); - } - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_search(1)); - // WebServer->rebootESP(); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_search(2)); - break; - } -} - -String SuplaWebPageSensor::supla_webpage_search(int save) { - String content = ""; - uint8_t count = 0; - uint8_t pin = ConfigESP->getGpio(FUNCTION_DS18B20); - - OneWire ow(pin); - DallasTemperature sensors; - DeviceAddress address; - char strAddr[64]; - uint8_t i; - - content += WebServer->SuplaSaveResult(save); - content += WebServer->SuplaJavaScript(PATH_MULTI_DS); - content += F("
"); - if (ConfigESP->getGpio(FUNCTION_DS18B20) < OFF_GPIO || !Supla::GUI::sensorDS.empty()) { - content += F("
"); - this->showDS18B20(content); - content += F("
"); - content += F("
"); - } - content += F("
"); - content += F("
"); - content += F("

"); - content += S_FOUND; - content += F(" DS18b20

"); - sensors.setOneWire(&ow); - sensors.begin(); - if (sensors.isParasitePowerMode()) { - supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is ON", pin); - } - else { - supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is OFF", pin); - } - // report parasite power requirements - for (i = 0; i < sensors.getDeviceCount(); i++) { - if (!sensors.getAddress(address, i)) { - supla_log(LOG_DEBUG, "Unable to find address for Device %d", i); - } - else { - sprintf(strAddr, "%02X%02X%02X%02X%02X%02X%02X%02X", address[0], address[1], address[2], address[3], address[4], address[5], address[6], - address[7]); - supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr); - - content += F(""); - - count++; - } - delay(0); - } - - if (count == 0) { - content += F(""); - } - content += F("
"); - content += F("
"); - content += F(""); - content += F("

"); - content += F(""); - - return content; -} - -void SuplaWebPageSensor::showDS18B20(String &content, bool readonly) { - if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { - content += F("
"); - content += F("

"); - content += S_TEMPERATURE; - content += F("

"); - for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { - String ds_key = KEY_DS; - String ds_name_key = KEY_DS_NAME; - ds_key += i; - ds_name_key += i; - - double temp = Supla::GUI::sensorDS[i]->getValue(); - content += F(""); - content += F(""); - delay(0); - } - content += F("
"); - } -} -#endif - -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) -void SuplaWebPageSensor::handle1Wire() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_1wire(0)); -} - -void SuplaWebPageSensor::handle1WireSave() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - - String key, input; - uint8_t nr, current_value, last_value; - -#ifdef SUPLA_DHT11 - last_value = ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); - current_value = WebServer->httpServer.arg(INPUT_MAX_DHT11).toInt(); - - if (last_value > 0) { - for (nr = 1; nr <= last_value; nr++) { - input = INPUT_DHT11_GPIO; - input += nr; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(nr, FUNCTION_DHT11) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { - ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_DHT11)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(nr, FUNCTION_DHT11) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DHT11)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_DHT11, 0); - } - else { - WebServer->sendContent(supla_webpage_1wire(6)); - return; - } - } - } - } - if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DHT11).c_str(), "") != 0) { - ConfigManager->set(KEY_MAX_DHT11, WebServer->httpServer.arg(INPUT_MAX_DHT11).c_str()); - } -#endif - -#ifdef SUPLA_DHT22 - last_value = ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); - current_value = WebServer->httpServer.arg(INPUT_MAX_DHT22).toInt(); - - if (last_value > 0) { - for (nr = 1; nr <= last_value; nr++) { - input = INPUT_DHT22_GPIO; - input += nr; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(nr, FUNCTION_DHT22) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { - ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_DHT22)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(nr, FUNCTION_DHT22) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DHT22)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_DHT22, 0); - } - else { - WebServer->sendContent(supla_webpage_1wire(6)); - return; - } - } - } - } - if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DHT22).c_str(), "") != 0) { - ConfigManager->set(KEY_MAX_DHT22, WebServer->httpServer.arg(INPUT_MAX_DHT22).c_str()); - } -#endif - -#ifdef SUPLA_DS18B20 - input = INPUT_MULTI_DS_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_DS18B20) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_DS18B20)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_DS18B20) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DS18B20)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_DS18B20); - } - else { - WebServer->sendContent(supla_webpage_1wire(6)); - return; - } - } - - if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DS18B20).c_str(), "") > 0) { - ConfigManager->set(KEY_MULTI_MAX_DS18B20, WebServer->httpServer.arg(INPUT_MAX_DS18B20).c_str()); - } -#endif - -#ifdef SUPLA_SI7021_SONOFF - input = INPUT_SI7021_SONOFF; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SI7021_SONOFF)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SI7021_SONOFF); - } - else { - WebServer->sendContent(supla_webpage_1wire(6)); - return; - } - } -#endif - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_1wire(1)); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_1wire(2)); - break; - } -} - -String SuplaWebPageSensor::supla_webpage_1wire(int save) { - uint8_t nr, suported, selected; - String page, key; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_1WIRE); - page += F("
"); -#ifdef SUPLA_DHT11 - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" DHT11

"); - page += F(""); - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { - page += F(""); - page += WebServer->selectGPIO(INPUT_DHT11_GPIO, FUNCTION_DHT11, nr); - page += F(""); - } - page += F("
"); -#endif - -#ifdef SUPLA_DHT22 - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" DHT22

"); - page += F(""); - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { - page += F(""); - page += WebServer->selectGPIO(INPUT_DHT22_GPIO, FUNCTION_DHT22, nr); - page += F(""); - } - page += F("
"); -#endif - -#ifdef SUPLA_SI7021_SONOFF - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" Si7021 Sonoff

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_SI7021_SONOFF, FUNCTION_SI7021_SONOFF); - page += F(""); - page += F("
"); -#endif - -#ifdef SUPLA_DS18B20 - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" Multi DS18B20

"); - page += F(""); - page += F(""); - if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { - page += F(""); - } - page += WebServer->selectGPIO(INPUT_MULTI_DS_GPIO, FUNCTION_DS18B20); - page += F(""); - page += F("
"); -#endif - - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F(""); - return page; -} -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) -void SuplaWebPageSensor::handlei2c() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_i2c(0)); -} - -void SuplaWebPageSensor::handlei2cSave() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - - String key, input; - uint8_t nr, current_value, last_value; - -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) - input = INPUT_SDA_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_SDA) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SDA)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_SDA) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SDA)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SDA); - } - else { - WebServer->sendContent(supla_webpage_i2c(6)); - return; - } - } - - input = INPUT_SCL_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_SCL) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SCL)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_SCL) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SCL)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SCL); - } - else { - WebServer->sendContent(supla_webpage_i2c(6)); - return; - } - } -#endif - -#ifdef SUPLA_BME280 - key = KEY_ACTIVE_SENSOR; - input = INPUT_BME280; - if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_BME280, WebServer->httpServer.arg(input).toInt()); - } - - key = KEY_ALTITUDE_BME280; - input = INPUT_ALTITUDE_BME280; - if (strcmp(WebServer->httpServer.arg(INPUT_ALTITUDE_BME280).c_str(), "") != 0) { - ConfigManager->set(key.c_str(), WebServer->httpServer.arg(input).c_str()); - } -#endif - -#ifdef SUPLA_SHT30 - key = KEY_ACTIVE_SENSOR; - input = INPUT_SHT30; - if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SHT30, WebServer->httpServer.arg(input).toInt()); - } -#endif - -#ifdef SUPLA_SI7021 - key = KEY_ACTIVE_SENSOR; - input = INPUT_SI7021; - if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SI7021, WebServer->httpServer.arg(input).toInt()); - } -#endif - -#ifdef SUPLA_HC_SR04 - input = INPUT_TRIG_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_TRIG) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_TRIG)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_TRIG) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_TRIG)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_TRIG); - } - else { - WebServer->sendContent(supla_webpage_i2c(6)); - return; - } - } - - input = INPUT_ECHO_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_ECHO) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_ECHO)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_ECHO) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_ECHO)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_ECHO); - } - else { - WebServer->sendContent(supla_webpage_i2c(6)); - return; - } - } - -#endif - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_i2c(1)); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_i2c(2)); - break; - } -} - -String SuplaWebPageSensor::supla_webpage_i2c(int save) { - uint8_t nr, suported, selected; - String page, key; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_I2C); - page += F("
"); - -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" i2c

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_SDA_GPIO, FUNCTION_SDA); - page += F(""); - page += F(""); - page += WebServer->selectGPIO(INPUT_SCL_GPIO, FUNCTION_SCL); - page += F(""); - - if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { -#ifdef SUPLA_BME280 - page += F(""); - page += F(""); -#endif - -#ifdef SUPLA_SHT30 - page += F(""); -#endif - -#ifdef SUPLA_SI7021 - page += F(""); -#endif - } - page += F("
"); -#endif - -#ifdef SUPLA_HC_SR04 - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" HC-SR04

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_TRIG_GPIO, FUNCTION_TRIG); - page += F(""); - page += F(""); - page += WebServer->selectGPIO(INPUT_ECHO_GPIO, FUNCTION_ECHO); - page += F(""); - page += F("
"); -#endif - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F(""); - return page; -} -#endif - -#if defined(SUPLA_MAX6675) -void SuplaWebPageSensor::handleSpi() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - WebServer->sendContent(supla_webpage_spi(0)); -} - -void SuplaWebPageSensor::handleSpiSave() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) - return WebServer->httpServer.requestAuthentication(); - } - - String key, input; - uint8_t nr, current_value, last_value; - -#if defined(SUPLA_MAX6675) - input = INPUT_CLK_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_CLK) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CLK)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_CLK) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CLK)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CLK); - } - else { - WebServer->sendContent(supla_webpage_spi(6)); - return; - } - } - - input = INPUT_CS_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_CS) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CS)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_CS) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CS)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CS); - } - else { - WebServer->sendContent(supla_webpage_spi(6)); - return; - } - } - - input = INPUT_D0_GPIO; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(FUNCTION_D0) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { - ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_D0)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || - (ConfigESP->getGpio(FUNCTION_D0) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_D0)) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_D0); - } - else { - WebServer->sendContent(supla_webpage_spi(6)); - return; - } - } -#endif - -#ifdef SUPLA_MAX6675 - key = KEY_ACTIVE_SENSOR; - input = INPUT_MAX6675; - if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_MAX6675, WebServer->httpServer.arg(input).toInt()); - } -#endif - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_spi(1)); - break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_spi(2)); - break; - } -} - -String SuplaWebPageSensor::supla_webpage_spi(int save) { - uint8_t nr, suported, selected; - String page, key; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_SPI); - page += F("
"); - -#if defined(SUPLA_MAX6675) - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" SPI

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_CLK_GPIO, FUNCTION_CLK); - page += F(""); - page += F(""); - page += WebServer->selectGPIO(INPUT_CS_GPIO, FUNCTION_CS); - page += F(""); - page += F(""); - page += WebServer->selectGPIO(INPUT_D0_GPIO, FUNCTION_D0); - page += F(""); - - if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { -#ifdef SUPLA_MAX6675 - page += F(""); -#endif - } - page += F("
"); -#endif - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F(""); - return page; -} -#endif +#include "SuplaWebPageSensor.h" + +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" +#include "SuplaCommonPROGMEM.h" +#include "GUIGenericCommon.h" + +SuplaWebPageSensor *WebPageSensor = new SuplaWebPageSensor(); + +SuplaWebPageSensor::SuplaWebPageSensor() { +} + +void SuplaWebPageSensor::createWebPageSensor() { + String path; + +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) + path = PATH_START; + path += PATH_1WIRE; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handle1Wire, this)); + path = PATH_START; + path += PATH_SAVE_1WIRE; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handle1WireSave, this)); +#ifdef SUPLA_DS18B20 + path = PATH_START; + path += PATH_MULTI_DS; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSearchDS, this)); + path = PATH_START; + path += PATH_SAVE_MULTI_DS; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleDSSave, this)); +#endif +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) + path = PATH_START; + path += PATH_I2C; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2c, this)); + path = PATH_START; + path += PATH_SAVE_I2C; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2cSave, this)); +#endif + +#if defined(SUPLA_MAX6675) + path = PATH_START; + path += PATH_SPI; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSpi, this)); + path = PATH_START; + path += PATH_SAVE_SPI; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleSpiSave, this)); +#endif +} + +#ifdef SUPLA_DS18B20 +void SuplaWebPageSensor::handleSearchDS() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_search(0)); +} + +void SuplaWebPageSensor::handleDSSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { + String ds_key = KEY_DS; + String ds_name_key = KEY_DS_NAME; + ds_key += i; + ds_name_key += i; + + String ds = F("dschlid"); + String ds_name = F("dsnameid"); + ds += i; + ds_name += i; + + ConfigManager->set(ds_key.c_str(), WebServer->httpServer.arg(ds).c_str()); + ConfigManager->set(ds_name_key.c_str(), WebServer->httpServer.arg(ds_name).c_str()); + + Supla::GUI::sensorDS[i]->setDeviceAddress(ConfigManager->get(ds_key.c_str())->getValueBin(MAX_DS18B20_ADDRESS)); + } + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Config save")); + WebServer->sendContent(supla_webpage_search(1)); + // WebServer->rebootESP(); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_search(2)); + break; + } +} + +String SuplaWebPageSensor::supla_webpage_search(int save) { + String content = ""; + uint8_t count = 0; + uint8_t pin = ConfigESP->getGpio(FUNCTION_DS18B20); + + OneWire ow(pin); + DallasTemperature sensors; + DeviceAddress address; + char strAddr[64]; + uint8_t i; + + content += WebServer->SuplaSaveResult(save); + content += WebServer->SuplaJavaScript(PATH_MULTI_DS); + content += F("
"); + if (ConfigESP->getGpio(FUNCTION_DS18B20) < OFF_GPIO || !Supla::GUI::sensorDS.empty()) { + content += F("
"); + this->showDS18B20(content); + content += F("
"); + content += F("
"); + } + content += F("
"); + content += F("
"); + content += F("

"); + content += S_FOUND; + content += F(" DS18b20

"); + sensors.setOneWire(&ow); + sensors.begin(); + if (sensors.isParasitePowerMode()) { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is ON", pin); + } + else { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is OFF", pin); + } + // report parasite power requirements + for (i = 0; i < sensors.getDeviceCount(); i++) { + if (!sensors.getAddress(address, i)) { + supla_log(LOG_DEBUG, "Unable to find address for Device %d", i); + } + else { + sprintf(strAddr, "%02X%02X%02X%02X%02X%02X%02X%02X", address[0], address[1], address[2], address[3], address[4], address[5], address[6], + address[7]); + supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr); + + content += F(""); + + count++; + } + delay(0); + } + + if (count == 0) { + content += F(""); + } + content += F("
"); + content += F("
"); + content += F(""); + content += F("

"); + content += F(""); + + return content; +} + +void SuplaWebPageSensor::showDS18B20(String &content, bool readonly) { + if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { + content += F("
"); + content += F("

"); + content += S_TEMPERATURE; + content += F("

"); + for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { + String ds_key = KEY_DS; + String ds_name_key = KEY_DS_NAME; + ds_key += i; + ds_name_key += i; + + double temp = Supla::GUI::sensorDS[i]->getValue(); + content += F(""); + content += F(""); + delay(0); + } + content += F("
"); + } +} +#endif + +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) +void SuplaWebPageSensor::handle1Wire() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_1wire(0)); +} + +void SuplaWebPageSensor::handle1WireSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String key, input; + uint8_t nr, current_value, last_value; + +#ifdef SUPLA_DHT11 + last_value = ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); + current_value = WebServer->httpServer.arg(INPUT_MAX_DHT11).toInt(); + + if (last_value > 0) { + for (nr = 1; nr <= last_value; nr++) { + input = INPUT_DHT11_GPIO; + input += nr; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(nr, FUNCTION_DHT11) != WebServer->httpServer.arg(input).toInt() || + WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { + ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_DHT11)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(nr, FUNCTION_DHT11) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DHT11)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_DHT11, 0); + } + else { + WebServer->sendContent(supla_webpage_1wire(6)); + return; + } + } + } + } + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DHT11).c_str(), "") != 0) { + ConfigManager->set(KEY_MAX_DHT11, WebServer->httpServer.arg(INPUT_MAX_DHT11).c_str()); + } +#endif + +#ifdef SUPLA_DHT22 + last_value = ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); + current_value = WebServer->httpServer.arg(INPUT_MAX_DHT22).toInt(); + + if (last_value > 0) { + for (nr = 1; nr <= last_value; nr++) { + input = INPUT_DHT22_GPIO; + input += nr; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(nr, FUNCTION_DHT22) != WebServer->httpServer.arg(input).toInt() || + WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { + ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_DHT22)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(nr, FUNCTION_DHT22) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DHT22)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_DHT22, 0); + } + else { + WebServer->sendContent(supla_webpage_1wire(6)); + return; + } + } + } + } + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DHT22).c_str(), "") != 0) { + ConfigManager->set(KEY_MAX_DHT22, WebServer->httpServer.arg(INPUT_MAX_DHT22).c_str()); + } +#endif + +#ifdef SUPLA_DS18B20 + input = INPUT_MULTI_DS_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_DS18B20) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_DS18B20)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_DS18B20) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_DS18B20)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_DS18B20); + } + else { + WebServer->sendContent(supla_webpage_1wire(6)); + return; + } + } + + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DS18B20).c_str(), "") > 0) { + ConfigManager->set(KEY_MULTI_MAX_DS18B20, WebServer->httpServer.arg(INPUT_MAX_DS18B20).c_str()); + } +#endif + +#ifdef SUPLA_SI7021_SONOFF + input = INPUT_SI7021_SONOFF; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != WebServer->httpServer.arg(input).toInt() || + WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SI7021_SONOFF)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SI7021_SONOFF); + } + else { + WebServer->sendContent(supla_webpage_1wire(6)); + return; + } + } +#endif + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + WebServer->sendContent(supla_webpage_1wire(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_1wire(2)); + break; + } +} + +String SuplaWebPageSensor::supla_webpage_1wire(int save) { + uint8_t nr, suported, selected; + String page, key; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_1WIRE); + page += F("
"); +#ifdef SUPLA_DHT11 + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" DHT11

"); + page += F(""); + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { + page += F(""); + page += WebServer->selectGPIO(INPUT_DHT11_GPIO, FUNCTION_DHT11, nr); + page += F(""); + } + page += F("
"); +#endif + +#ifdef SUPLA_DHT22 + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" DHT22

"); + page += F(""); + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { + page += F(""); + page += WebServer->selectGPIO(INPUT_DHT22_GPIO, FUNCTION_DHT22, nr); + page += F(""); + } + page += F("
"); +#endif + +#ifdef SUPLA_SI7021_SONOFF + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" Si7021 Sonoff

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_SI7021_SONOFF, FUNCTION_SI7021_SONOFF); + page += F(""); + page += F("
"); +#endif + +#ifdef SUPLA_DS18B20 + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" Multi DS18B20

"); + page += F(""); + page += F(""); + if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { + page += F(""); + } + page += WebServer->selectGPIO(INPUT_MULTI_DS_GPIO, FUNCTION_DS18B20); + page += F(""); + page += F("
"); +#endif + + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F(""); + return page; +} +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +void SuplaWebPageSensor::handlei2c() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_i2c(0)); +} + +void SuplaWebPageSensor::handlei2cSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String key, input; + uint8_t nr, current_value, last_value; + +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) + input = INPUT_SDA_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_SDA) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SDA)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_SDA) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SDA)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SDA); + } + else { + WebServer->sendContent(supla_webpage_i2c(6)); + return; + } + } + + input = INPUT_SCL_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_SCL) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_SCL)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_SCL) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_SCL)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_SCL); + } + else { + WebServer->sendContent(supla_webpage_i2c(6)); + return; + } + } +#endif + +#ifdef SUPLA_BME280 + key = KEY_ACTIVE_SENSOR; + input = INPUT_BME280; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_BME280, WebServer->httpServer.arg(input).toInt()); + } + + key = KEY_ALTITUDE_BME280; + input = INPUT_ALTITUDE_BME280; + if (strcmp(WebServer->httpServer.arg(INPUT_ALTITUDE_BME280).c_str(), "") != 0) { + ConfigManager->set(key.c_str(), WebServer->httpServer.arg(input).c_str()); + } +#endif + +#ifdef SUPLA_SHT30 + key = KEY_ACTIVE_SENSOR; + input = INPUT_SHT30; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SHT30, WebServer->httpServer.arg(input).toInt()); + } +#endif + +#ifdef SUPLA_SI7021 + key = KEY_ACTIVE_SENSOR; + input = INPUT_SI7021; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SI7021, WebServer->httpServer.arg(input).toInt()); + } +#endif + +#ifdef SUPLA_HC_SR04 + input = INPUT_TRIG_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_TRIG) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_TRIG)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_TRIG) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_TRIG)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_TRIG); + } + else { + WebServer->sendContent(supla_webpage_i2c(6)); + return; + } + } + + input = INPUT_ECHO_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_ECHO) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_ECHO)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_ECHO) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_ECHO)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_ECHO); + } + else { + WebServer->sendContent(supla_webpage_i2c(6)); + return; + } + } + +#endif + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + WebServer->sendContent(supla_webpage_i2c(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_i2c(2)); + break; + } +} + +String SuplaWebPageSensor::supla_webpage_i2c(int save) { + uint8_t nr, suported, selected; + String page, key; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_I2C); + page += F("
"); + +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" i2c

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_SDA_GPIO, FUNCTION_SDA); + page += F(""); + page += F(""); + page += WebServer->selectGPIO(INPUT_SCL_GPIO, FUNCTION_SCL); + page += F(""); + + if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { +#ifdef SUPLA_BME280 + page += F(""); + page += F(""); +#endif + +#ifdef SUPLA_SHT30 + page += F(""); +#endif + +#ifdef SUPLA_SI7021 + page += F(""); +#endif + } + page += F("
"); +#endif + +#ifdef SUPLA_HC_SR04 + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" HC-SR04

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_TRIG_GPIO, FUNCTION_TRIG); + page += F(""); + page += F(""); + page += WebServer->selectGPIO(INPUT_ECHO_GPIO, FUNCTION_ECHO); + page += F(""); + page += F("
"); +#endif + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F(""); + return page; +} +#endif + +#if defined(SUPLA_MAX6675) +void SuplaWebPageSensor::handleSpi() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_spi(0)); +} + +void SuplaWebPageSensor::handleSpiSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String key, input; + uint8_t nr, current_value, last_value; + +#if defined(SUPLA_MAX6675) + input = INPUT_CLK_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_CLK) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CLK)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_CLK) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CLK)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CLK); + } + else { + WebServer->sendContent(supla_webpage_spi(6)); + return; + } + } + + input = INPUT_CS_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_CS) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_CS)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_CS) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_CS)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_CS); + } + else { + WebServer->sendContent(supla_webpage_spi(6)); + return; + } + } + + input = INPUT_D0_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_D0) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_D0)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_D0) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_D0)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_D0); + } + else { + WebServer->sendContent(supla_webpage_spi(6)); + return; + } + } +#endif + +#ifdef SUPLA_MAX6675 + key = KEY_ACTIVE_SENSOR; + input = INPUT_MAX6675; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_MAX6675, WebServer->httpServer.arg(input).toInt()); + } +#endif + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + WebServer->sendContent(supla_webpage_spi(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_spi(2)); + break; + } +} + +String SuplaWebPageSensor::supla_webpage_spi(int save) { + uint8_t nr, suported, selected; + String page, key; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_SPI); + page += F("
"); + +#if defined(SUPLA_MAX6675) + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" SPI

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_CLK_GPIO, FUNCTION_CLK); + page += F(""); + page += F(""); + page += WebServer->selectGPIO(INPUT_CS_GPIO, FUNCTION_CS); + page += F(""); + page += F(""); + page += WebServer->selectGPIO(INPUT_D0_GPIO, FUNCTION_D0); + page += F(""); + + if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { +#ifdef SUPLA_MAX6675 + page += F(""); +#endif + } + page += F("
"); +#endif + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F(""); + return page; +} +#endif diff --git a/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h similarity index 96% rename from SuplaWebPageSensor.h rename to src/SuplaWebPageSensor.h index 478d397e..95e7f7e9 100644 --- a/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -1,109 +1,109 @@ -#ifndef SuplaWebPageSensor_h -#define SuplaWebPageSensor_h - -#include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" - -#define PATH_MULTI_DS "multids" -#define PATH_SAVE_MULTI_DS "savemultids" -#define PATH_1WIRE "1wire" -#define PATH_SAVE_1WIRE "save1wire" -#define PATH_I2C "i2c" -#define PATH_SAVE_I2C "savei2c" -#define PATH_SPI "spi" -#define PATH_SAVE_SPI "savespi" - -#define INPUT_MULTI_DS_GPIO "mdsg" -#define INPUT_DHT11_GPIO "dht11" -#define INPUT_DHT22_GPIO "dht22" -#define INPUT_SDA_GPIO "sdag" -#define INPUT_SCL_GPIO "sclg" -#define INPUT_BME280 "bme280" -#define INPUT_ALTITUDE_BME280 "abme280" -#define INPUT_SHT30 "sht30" -#define INPUT_SI7021 "si7021" -#define INPUT_SI7021_SONOFF "si7021sonoff" -#define INPUT_TRIG_GPIO "trig" -#define INPUT_ECHO_GPIO "echo" -#define INPUT_MAX_DHT11 "mdht11" -#define INPUT_MAX_DHT22 "mdht22" -#define INPUT_MAX_DS18B20 "maxds" -#define INPUT_CLK_GPIO "clk" -#define INPUT_CS_GPIO "cs" -#define INPUT_D0_GPIO "d0" -#define INPUT_MAX6675 "max6675" - -enum _sensorI2C -{ - SENSOR_BME280, - SENSOR_SHT30, - SENSOR_SI7021 -}; - -enum _sensorSPI -{ - SENSOR_MAX6675 -}; - -#ifdef SUPLA_BME280 -enum _bmeAdress -{ - BME280_ADDRESS_0X76 = 1, - BME280_ADDRESS_0X77, - BME280_ADDRESS_0X76_AND_0X77 -}; -#endif - -#ifdef SUPLA_SHT30 -enum _shtAdress -{ - SHT30_ADDRESS_0X44 = 1, - SHT30_ADDRESS_0X45, - SHT30_ADDRESS_0X44_AND_0X45 -}; -#endif - -class SuplaWebPageSensor { - public: - SuplaWebPageSensor(); - void createWebPageSensor(); - -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - void handle1Wire(); - void handle1WireSave(); -#endif - -#ifdef SUPLA_DS18B20 - void handleSearchDS(); - void handleDSSave(); - void showDS18B20(String& content, bool readonly = false); -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) - void handlei2c(); - void handlei2cSave(); -#endif - -#if defined(SUPLA_MAX6675) - void handleSpi(); - void handleSpiSave(); -#endif - - private: -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - String supla_webpage_1wire(int save); -#ifdef SUPLA_DS18B20 - String supla_webpage_search(int save); -#endif -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) - String supla_webpage_i2c(int save); -#endif -#if defined(SUPLA_MAX6675) - String supla_webpage_spi(int save); -#endif -}; - -extern SuplaWebPageSensor* WebPageSensor; -#endif // SuplaWebPageSensor_h +#ifndef SuplaWebPageSensor_h +#define SuplaWebPageSensor_h + +#include "SuplaDeviceGUI.h" +#include "SuplaWebServer.h" + +#define PATH_MULTI_DS "multids" +#define PATH_SAVE_MULTI_DS "savemultids" +#define PATH_1WIRE "1wire" +#define PATH_SAVE_1WIRE "save1wire" +#define PATH_I2C "i2c" +#define PATH_SAVE_I2C "savei2c" +#define PATH_SPI "spi" +#define PATH_SAVE_SPI "savespi" + +#define INPUT_MULTI_DS_GPIO "mdsg" +#define INPUT_DHT11_GPIO "dht11" +#define INPUT_DHT22_GPIO "dht22" +#define INPUT_SDA_GPIO "sdag" +#define INPUT_SCL_GPIO "sclg" +#define INPUT_BME280 "bme280" +#define INPUT_ALTITUDE_BME280 "abme280" +#define INPUT_SHT30 "sht30" +#define INPUT_SI7021 "si7021" +#define INPUT_SI7021_SONOFF "si7021sonoff" +#define INPUT_TRIG_GPIO "trig" +#define INPUT_ECHO_GPIO "echo" +#define INPUT_MAX_DHT11 "mdht11" +#define INPUT_MAX_DHT22 "mdht22" +#define INPUT_MAX_DS18B20 "maxds" +#define INPUT_CLK_GPIO "clk" +#define INPUT_CS_GPIO "cs" +#define INPUT_D0_GPIO "d0" +#define INPUT_MAX6675 "max6675" + +enum _sensorI2C +{ + SENSOR_BME280, + SENSOR_SHT30, + SENSOR_SI7021 +}; + +enum _sensorSPI +{ + SENSOR_MAX6675 +}; + +#ifdef SUPLA_BME280 +enum _bmeAdress +{ + BME280_ADDRESS_0X76 = 1, + BME280_ADDRESS_0X77, + BME280_ADDRESS_0X76_AND_0X77 +}; +#endif + +#ifdef SUPLA_SHT30 +enum _shtAdress +{ + SHT30_ADDRESS_0X44 = 1, + SHT30_ADDRESS_0X45, + SHT30_ADDRESS_0X44_AND_0X45 +}; +#endif + +class SuplaWebPageSensor { + public: + SuplaWebPageSensor(); + void createWebPageSensor(); + +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) + void handle1Wire(); + void handle1WireSave(); +#endif + +#ifdef SUPLA_DS18B20 + void handleSearchDS(); + void handleDSSave(); + void showDS18B20(String& content, bool readonly = false); +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) + void handlei2c(); + void handlei2cSave(); +#endif + +#if defined(SUPLA_MAX6675) + void handleSpi(); + void handleSpiSave(); +#endif + + private: +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) + String supla_webpage_1wire(int save); +#ifdef SUPLA_DS18B20 + String supla_webpage_search(int save); +#endif +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) + String supla_webpage_i2c(int save); +#endif +#if defined(SUPLA_MAX6675) + String supla_webpage_spi(int save); +#endif +}; + +extern SuplaWebPageSensor* WebPageSensor; +#endif // SuplaWebPageSensor_h diff --git a/SuplaWebServer.cpp b/src/SuplaWebServer.cpp similarity index 96% rename from SuplaWebServer.cpp rename to src/SuplaWebServer.cpp index 7f9a97ec..0eaf1470 100644 --- a/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -1,633 +1,633 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "SuplaWebServer.h" -#include "SuplaDeviceGUI.h" -#include "SuplaWebPageConfig.h" -#include "SuplaWebPageControl.h" -#include "SuplaWebPageRelay.h" -#include "SuplaWebPageSensor.h" -#include "SuplaCommonPROGMEM.h" -#include "SuplaTemplateBoard.h" -#include "GUIGenericCommon.h" - -SuplaWebServer::SuplaWebServer() { -} - -void SuplaWebServer::begin() { - this->createWebServer(); - - strcpy(this->www_username, ConfigManager->get(KEY_LOGIN)->getValue()); - strcpy(this->www_password, ConfigManager->get(KEY_LOGIN_PASS)->getValue()); - - httpUpdater.setup(&httpServer, UPDATE_PATH, www_username, www_password); - httpServer.begin(); -} - -void SuplaWebServer::iterateAlways() { - httpServer.handleClient(); -} - -void SuplaWebServer::createWebServer() { - String path = PATH_START; - httpServer.on(path, HTTP_GET, std::bind(&SuplaWebServer::handle, this)); - path = PATH_START; - httpServer.on(path, std::bind(&SuplaWebServer::handleSave, this)); - path = PATH_START; - path += PATH_UPDATE; - httpServer.on(path, std::bind(&SuplaWebServer::handleFirmwareUp, this)); - path = PATH_START; - path += PATH_REBOT; - httpServer.on(path, std::bind(&SuplaWebServer::supla_webpage_reboot, this)); - path = PATH_START; - path += PATH_DEVICE_SETTINGS; - httpServer.on(path, std::bind(&SuplaWebServer::handleDeviceSettings, this)); - path = PATH_START; - path += PATH_SAVE_BOARD; - httpServer.on(path, std::bind(&SuplaWebServer::handleBoardSave, this)); - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) - WebPageRelay->createWebPageRelay(); -#endif -#if defined(SUPLA_BUTTON) || defined(SUPLA_LIMIT_SWITCH) - WebPageControl->createWebPageControl(); -#endif -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) - WebPageSensor->createWebPageSensor(); -#endif -#ifdef SUPLA_CONFIG - WebPageConfig->createWebPageConfig(); -#endif -} - -void SuplaWebServer::handle() { - // Serial.println(F("HTTP_GET - metoda handle")); - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(this->www_username, this->www_password)) - return httpServer.requestAuthentication(); - } - this->sendContent(supla_webpage_start(0)); -} - -void SuplaWebServer::handleSave() { - // Serial.println(F("HTTP_POST - metoda handleSave")); - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(this->www_username, this->www_password)) - return httpServer.requestAuthentication(); - } - - if (strcmp(httpServer.arg(PATH_REBOT).c_str(), "1") == 0) { - this->rebootESP(); - return; - } - - ConfigManager->set(KEY_WIFI_SSID, httpServer.arg(INPUT_WIFI_SSID).c_str()); - ConfigManager->set(KEY_WIFI_PASS, httpServer.arg(INPUT_WIFI_PASS).c_str()); - ConfigManager->set(KEY_SUPLA_SERVER, httpServer.arg(INPUT_SERVER).c_str()); - ConfigManager->set(KEY_SUPLA_EMAIL, httpServer.arg(INPUT_EMAIL).c_str()); - ConfigManager->set(KEY_HOST_NAME, httpServer.arg(INPUT_HOSTNAME).c_str()); - ConfigManager->set(KEY_LOGIN, httpServer.arg(INPUT_MODUL_LOGIN).c_str()); - ConfigManager->set(KEY_LOGIN_PASS, httpServer.arg(INPUT_MODUL_PASS).c_str()); - -#ifdef SUPLA_ROLLERSHUTTER - if (strcmp(WebServer->httpServer.arg(INPUT_ROLLERSHUTTER).c_str(), "") != 0) { - ConfigManager->set(KEY_MAX_ROLLERSHUTTER, httpServer.arg(INPUT_ROLLERSHUTTER).c_str()); - } -#endif - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Dane zapisane")); - if (ConfigESP->configModeESP == NORMAL_MODE) { - this->sendContent(supla_webpage_start(5)); - this->rebootESP(); - } - else { - this->sendContent(supla_webpage_start(7)); - } - break; - - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - this->sendContent(supla_webpage_start(4)); - break; - } -} - -void SuplaWebServer::handleFirmwareUp() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(www_username, www_password)) - return httpServer.requestAuthentication(); - } - this->sendContent(supla_webpage_upddate()); -} - -void SuplaWebServer::handleDeviceSettings() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(www_username, www_password)) - return httpServer.requestAuthentication(); - } - this->sendContent(deviceSettings(0)); -} - -String SuplaWebServer::supla_webpage_start(int save) { - String content = F(""); - content += SuplaSaveResult(save); - content += SuplaJavaScript(); - content += F("
"); - content += F("
"); - content += F("

"); - content += S_SETTING_WIFI_SSID; - content += F("

"); - content += F(" "); - content += F("get(KEY_WIFI_PASS)->getValue() != 0) { - content += F("required>"); - } - else { - content += F("'minlength='"); - content += MIN_PASSWORD; - content += F("' length="); - content += MAX_PASSWORD; - content += F(" required>"); - } - content += F(" "); - content += F(" "); - content += F("
"); - content += F("
"); - content += F("

"); - content += S_SETTING_SUPLA; - content += F("

"); - content += F(" "); - content += F(""); - content += F("
"); - - content += F("
"); - content += F("

"); - content += S_SETTING_ADMIN; - content += F("

"); - content += F(""); - content += F(""); - content += F(""); - content += F("
"); - -#ifdef SUPLA_ROLLERSHUTTER - uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); - if (maxrollershutter >= 2) { - content += F("
"); - content += F("

"); - content += S_ROLLERSHUTTERS; - content += F("

"); - content += F(""); - content += F("
"); - } -#endif - -#ifdef SUPLA_DS18B20 - WebPageSensor->showDS18B20(content, true); -#endif - - content += F("
"); - content += F("
"); - content += F(""); - content += F("

"); - content += F(""); - content += F("

"); - content += F("
"); - content += F("
"); - return content; -} - -String SuplaWebServer::supla_webpage_upddate() { - String content = ""; - content += F("
"); - content += F("

"); - content += S_SOFTWARE_UPDATE; - content += F("

"); - content += F("
"); - content += F("
"); - content += F(""); - content += F("
"); - content += F("
"); - content += F(""); - - return content; -} - -void SuplaWebServer::supla_webpage_reboot() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(www_username, www_password)) - return httpServer.requestAuthentication(); - } - this->sendContent(supla_webpage_start(2)); - this->rebootESP(); -} - -String SuplaWebServer::deviceSettings(int save) { - String content = ""; - - content += WebServer->SuplaSaveResult(save); - content += WebServer->SuplaJavaScript(PATH_DEVICE_SETTINGS); - content += F("
"); - content += F("

"); - content += S_TEMPLATE_BOARD; - content += F("

"); - content += F(""); - content += F("


"); - content += F("
"); - content += F("

"); - content += S_DEVICE_SETTINGS; - content += F("

"); - content += F("
"); - content += F("
"); - -#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) - content += F(""); - content += F("

"); -#endif - -#ifdef SUPLA_BUTTON - content += F(""); - content += F("

"); -#endif - -#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - content += F(""); - content += F("

"); -#endif - -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) - content += F(""); - content += F("

"); -#endif - -#if defined(SUPLA_MAX6675) - content += F(""); - content += F("

"); -#endif - -#ifdef SUPLA_CONFIG - content += F(""); - content += F("

"); -#endif - content += F("
"); - content += F(""); - content += F(""); - - return content; -} - -void SuplaWebServer::handleBoardSave() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(this->www_username, this->www_password)) - return httpServer.requestAuthentication(); - } - String input = INPUT_BOARD; - - if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->set(KEY_BOARD, httpServer.arg(input).c_str()); - - int nr; - String key; - for (nr = 0; nr <= 17; nr++) { - key = GPIO; - key += nr; - ConfigManager->set(key.c_str(), "0,0,0,0,0"); - } - - chooseTemplateBoard(WebServer->httpServer.arg(input).toInt()); - } - - switch (ConfigManager->save()) { - case E_CONFIG_OK: - WebServer->sendContent(deviceSettings(1)); - break; - case E_CONFIG_FILE_OPEN: - WebServer->sendContent(deviceSettings(2)); - break; - } -} - -String SuplaWebServer::selectGPIO(const char* input, uint8_t function, uint8_t nr) { - String page = ""; - page += F(""); - return page; -} - -const String SuplaWebServer::SuplaFavicon() { - // return F("\n"); - return F(""); -} - -const String SuplaWebServer::SuplaIconEdit() { - return F( - ""); -} - -const String SuplaWebServer::SuplaJavaScript(String java_return) { - String java_script = - F("\n"); - return java_script; -} - -const String SuplaWebServer::SuplaSaveResult(int save) { - if (save == 0) - return F(""); - String saveresult = ""; - saveresult += F("
"); - if (save == 1) { - saveresult += S_DATA_SAVED; - } - else if (save == 2) { - saveresult += S_RESTART_MODULE; - } - else if (save == 3) { - saveresult += S_DATA_ERASED_RESTART_DEVICE; - } - else if (save == 4) { - saveresult += S_WRITE_ERROR_UNABLE_TO_READ_FILE_FS_PARTITION_MISSING; - } - else if (save == 5) { - saveresult += S_DATA_SAVED_RESTART_MODULE; - } - else if (save == 6) { - saveresult += S_WRITE_ERROR_BAD_DATA; - } - else if (save == 7) { - saveresult += F("data saved"); - } - saveresult += F("
"); - return saveresult; -} - -void SuplaWebServer::rebootESP() { - wdt_reset(); - ESP.restart(); - while (1) wdt_reset(); -} - -void SuplaWebServer::sendContent(const String content) { - // httpServer.send(200, "text/html", ""); - const int bufferSize = 1000; - String _buffer; - int bufferCounter = 0; - int fileSize = content.length(); - -#ifdef DEBUG_MODE - Serial.print("Content size: "); - Serial.println(fileSize); -#endif - - httpServer.setContentLength(fileSize); - httpServer.chunkedResponseModeStart(200, "text/html"); - - httpServer.sendContent_P(HTTP_META); - httpServer.sendContent_P(HTTP_STYLE); - httpServer.sendContent_P(HTTP_LOGO); - - String summary = FPSTR(HTTP_SUMMARY); - summary.replace("{h}", ConfigManager->get(KEY_HOST_NAME)->getValue()); - summary.replace("{s}", ConfigESP->getLastStatusSupla()); - summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); - summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); - summary.replace("{m}", ConfigESP->getMacAddress(true)); - httpServer.sendContent(summary); - - // httpServer.send(200, "text/html", ""); - for (int i = 0; i < fileSize; i++) { - _buffer += content[i]; - bufferCounter++; - - if (bufferCounter >= bufferSize) { - httpServer.sendContent(_buffer); - yield(); - bufferCounter = 0; - _buffer = ""; - } - } - if (bufferCounter > 0) { - httpServer.sendContent(_buffer); - yield(); - bufferCounter = 0; - _buffer = ""; - } - httpServer.sendContent_P(HTTP_COPYRIGHT); - - httpServer.chunkedResponseFinalize(); -} - -void SuplaWebServer::redirectToIndex() { - httpServer.sendHeader("Location", "/", true); - httpServer.send(302, "text/plain", ""); - httpServer.client().stop(); -} +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "SuplaWebServer.h" +#include "SuplaDeviceGUI.h" +#include "SuplaWebPageConfig.h" +#include "SuplaWebPageControl.h" +#include "SuplaWebPageRelay.h" +#include "SuplaWebPageSensor.h" +#include "SuplaCommonPROGMEM.h" +#include "SuplaTemplateBoard.h" +#include "GUIGenericCommon.h" + +SuplaWebServer::SuplaWebServer() { +} + +void SuplaWebServer::begin() { + this->createWebServer(); + + strcpy(this->www_username, ConfigManager->get(KEY_LOGIN)->getValue()); + strcpy(this->www_password, ConfigManager->get(KEY_LOGIN_PASS)->getValue()); + + httpUpdater.setup(&httpServer, UPDATE_PATH, www_username, www_password); + httpServer.begin(); +} + +void SuplaWebServer::iterateAlways() { + httpServer.handleClient(); +} + +void SuplaWebServer::createWebServer() { + String path = PATH_START; + httpServer.on(path, HTTP_GET, std::bind(&SuplaWebServer::handle, this)); + path = PATH_START; + httpServer.on(path, std::bind(&SuplaWebServer::handleSave, this)); + path = PATH_START; + path += PATH_UPDATE; + httpServer.on(path, std::bind(&SuplaWebServer::handleFirmwareUp, this)); + path = PATH_START; + path += PATH_REBOT; + httpServer.on(path, std::bind(&SuplaWebServer::supla_webpage_reboot, this)); + path = PATH_START; + path += PATH_DEVICE_SETTINGS; + httpServer.on(path, std::bind(&SuplaWebServer::handleDeviceSettings, this)); + path = PATH_START; + path += PATH_SAVE_BOARD; + httpServer.on(path, std::bind(&SuplaWebServer::handleBoardSave, this)); + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) + WebPageRelay->createWebPageRelay(); +#endif +#if defined(SUPLA_BUTTON) || defined(SUPLA_LIMIT_SWITCH) + WebPageControl->createWebPageControl(); +#endif +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) + WebPageSensor->createWebPageSensor(); +#endif +#ifdef SUPLA_CONFIG + WebPageConfig->createWebPageConfig(); +#endif +} + +void SuplaWebServer::handle() { + // Serial.println(F("HTTP_GET - metoda handle")); + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(this->www_username, this->www_password)) + return httpServer.requestAuthentication(); + } + this->sendContent(supla_webpage_start(0)); +} + +void SuplaWebServer::handleSave() { + // Serial.println(F("HTTP_POST - metoda handleSave")); + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(this->www_username, this->www_password)) + return httpServer.requestAuthentication(); + } + + if (strcmp(httpServer.arg(PATH_REBOT).c_str(), "1") == 0) { + this->rebootESP(); + return; + } + + ConfigManager->set(KEY_WIFI_SSID, httpServer.arg(INPUT_WIFI_SSID).c_str()); + ConfigManager->set(KEY_WIFI_PASS, httpServer.arg(INPUT_WIFI_PASS).c_str()); + ConfigManager->set(KEY_SUPLA_SERVER, httpServer.arg(INPUT_SERVER).c_str()); + ConfigManager->set(KEY_SUPLA_EMAIL, httpServer.arg(INPUT_EMAIL).c_str()); + ConfigManager->set(KEY_HOST_NAME, httpServer.arg(INPUT_HOSTNAME).c_str()); + ConfigManager->set(KEY_LOGIN, httpServer.arg(INPUT_MODUL_LOGIN).c_str()); + ConfigManager->set(KEY_LOGIN_PASS, httpServer.arg(INPUT_MODUL_PASS).c_str()); + +#ifdef SUPLA_ROLLERSHUTTER + if (strcmp(WebServer->httpServer.arg(INPUT_ROLLERSHUTTER).c_str(), "") != 0) { + ConfigManager->set(KEY_MAX_ROLLERSHUTTER, httpServer.arg(INPUT_ROLLERSHUTTER).c_str()); + } +#endif + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Dane zapisane")); + if (ConfigESP->configModeESP == NORMAL_MODE) { + this->sendContent(supla_webpage_start(5)); + this->rebootESP(); + } + else { + this->sendContent(supla_webpage_start(7)); + } + break; + + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + this->sendContent(supla_webpage_start(4)); + break; + } +} + +void SuplaWebServer::handleFirmwareUp() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(www_username, www_password)) + return httpServer.requestAuthentication(); + } + this->sendContent(supla_webpage_upddate()); +} + +void SuplaWebServer::handleDeviceSettings() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(www_username, www_password)) + return httpServer.requestAuthentication(); + } + this->sendContent(deviceSettings(0)); +} + +String SuplaWebServer::supla_webpage_start(int save) { + String content = F(""); + content += SuplaSaveResult(save); + content += SuplaJavaScript(); + content += F("
"); + content += F("
"); + content += F("

"); + content += S_SETTING_WIFI_SSID; + content += F("

"); + content += F(" "); + content += F("get(KEY_WIFI_PASS)->getValue() != 0) { + content += F("required>"); + } + else { + content += F("'minlength='"); + content += MIN_PASSWORD; + content += F("' length="); + content += MAX_PASSWORD; + content += F(" required>"); + } + content += F(" "); + content += F(" "); + content += F("
"); + content += F("
"); + content += F("

"); + content += S_SETTING_SUPLA; + content += F("

"); + content += F(" "); + content += F(""); + content += F("
"); + + content += F("
"); + content += F("

"); + content += S_SETTING_ADMIN; + content += F("

"); + content += F(""); + content += F(""); + content += F(""); + content += F("
"); + +#ifdef SUPLA_ROLLERSHUTTER + uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); + if (maxrollershutter >= 2) { + content += F("
"); + content += F("

"); + content += S_ROLLERSHUTTERS; + content += F("

"); + content += F(""); + content += F("
"); + } +#endif + +#ifdef SUPLA_DS18B20 + WebPageSensor->showDS18B20(content, true); +#endif + + content += F("
"); + content += F("
"); + content += F(""); + content += F("

"); + content += F(""); + content += F("

"); + content += F("
"); + content += F("
"); + return content; +} + +String SuplaWebServer::supla_webpage_upddate() { + String content = ""; + content += F("
"); + content += F("

"); + content += S_SOFTWARE_UPDATE; + content += F("

"); + content += F("
"); + content += F("
"); + content += F(""); + content += F("
"); + content += F("
"); + content += F(""); + + return content; +} + +void SuplaWebServer::supla_webpage_reboot() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(www_username, www_password)) + return httpServer.requestAuthentication(); + } + this->sendContent(supla_webpage_start(2)); + this->rebootESP(); +} + +String SuplaWebServer::deviceSettings(int save) { + String content = ""; + + content += WebServer->SuplaSaveResult(save); + content += WebServer->SuplaJavaScript(PATH_DEVICE_SETTINGS); + content += F("
"); + content += F("

"); + content += S_TEMPLATE_BOARD; + content += F("

"); + content += F(""); + content += F("


"); + content += F("
"); + content += F("

"); + content += S_DEVICE_SETTINGS; + content += F("

"); + content += F("
"); + content += F("
"); + +#if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) + content += F(""); + content += F("

"); +#endif + +#ifdef SUPLA_BUTTON + content += F(""); + content += F("

"); +#endif + +#if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) + content += F(""); + content += F("

"); +#endif + +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) + content += F(""); + content += F("

"); +#endif + +#if defined(SUPLA_MAX6675) + content += F(""); + content += F("

"); +#endif + +#ifdef SUPLA_CONFIG + content += F(""); + content += F("

"); +#endif + content += F("
"); + content += F(""); + content += F(""); + + return content; +} + +void SuplaWebServer::handleBoardSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!httpServer.authenticate(this->www_username, this->www_password)) + return httpServer.requestAuthentication(); + } + String input = INPUT_BOARD; + + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->set(KEY_BOARD, httpServer.arg(input).c_str()); + + int nr; + String key; + for (nr = 0; nr <= 17; nr++) { + key = GPIO; + key += nr; + ConfigManager->set(key.c_str(), "0,0,0,0,0"); + } + + chooseTemplateBoard(WebServer->httpServer.arg(input).toInt()); + } + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + WebServer->sendContent(deviceSettings(1)); + break; + case E_CONFIG_FILE_OPEN: + WebServer->sendContent(deviceSettings(2)); + break; + } +} + +String SuplaWebServer::selectGPIO(const char* input, uint8_t function, uint8_t nr) { + String page = ""; + page += F(""); + return page; +} + +const String SuplaWebServer::SuplaFavicon() { + // return F("\n"); + return F(""); +} + +const String SuplaWebServer::SuplaIconEdit() { + return F( + ""); +} + +const String SuplaWebServer::SuplaJavaScript(String java_return) { + String java_script = + F("\n"); + return java_script; +} + +const String SuplaWebServer::SuplaSaveResult(int save) { + if (save == 0) + return F(""); + String saveresult = ""; + saveresult += F("
"); + if (save == 1) { + saveresult += S_DATA_SAVED; + } + else if (save == 2) { + saveresult += S_RESTART_MODULE; + } + else if (save == 3) { + saveresult += S_DATA_ERASED_RESTART_DEVICE; + } + else if (save == 4) { + saveresult += S_WRITE_ERROR_UNABLE_TO_READ_FILE_FS_PARTITION_MISSING; + } + else if (save == 5) { + saveresult += S_DATA_SAVED_RESTART_MODULE; + } + else if (save == 6) { + saveresult += S_WRITE_ERROR_BAD_DATA; + } + else if (save == 7) { + saveresult += F("data saved"); + } + saveresult += F("
"); + return saveresult; +} + +void SuplaWebServer::rebootESP() { + wdt_reset(); + ESP.restart(); + while (1) wdt_reset(); +} + +void SuplaWebServer::sendContent(const String content) { + // httpServer.send(200, "text/html", ""); + const int bufferSize = 1000; + String _buffer; + int bufferCounter = 0; + int fileSize = content.length(); + +#ifdef DEBUG_MODE + Serial.print("Content size: "); + Serial.println(fileSize); +#endif + + httpServer.setContentLength(fileSize); + httpServer.chunkedResponseModeStart(200, "text/html"); + + httpServer.sendContent_P(HTTP_META); + httpServer.sendContent_P(HTTP_STYLE); + httpServer.sendContent_P(HTTP_LOGO); + + String summary = FPSTR(HTTP_SUMMARY); + summary.replace("{h}", ConfigManager->get(KEY_HOST_NAME)->getValue()); + summary.replace("{s}", ConfigESP->getLastStatusSupla()); + summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); + summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); + summary.replace("{m}", ConfigESP->getMacAddress(true)); + httpServer.sendContent(summary); + + // httpServer.send(200, "text/html", ""); + for (int i = 0; i < fileSize; i++) { + _buffer += content[i]; + bufferCounter++; + + if (bufferCounter >= bufferSize) { + httpServer.sendContent(_buffer); + yield(); + bufferCounter = 0; + _buffer = ""; + } + } + if (bufferCounter > 0) { + httpServer.sendContent(_buffer); + yield(); + bufferCounter = 0; + _buffer = ""; + } + httpServer.sendContent_P(HTTP_COPYRIGHT); + + httpServer.chunkedResponseFinalize(); +} + +void SuplaWebServer::redirectToIndex() { + httpServer.sendHeader("Location", "/", true); + httpServer.send(302, "text/plain", ""); + httpServer.client().stop(); +} diff --git a/SuplaWebServer.h b/src/SuplaWebServer.h similarity index 96% rename from SuplaWebServer.h rename to src/SuplaWebServer.h index 7d1b73b6..99351509 100644 --- a/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -1,97 +1,97 @@ -/* - Copyright (C) krycha88 - - 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 2 - 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 should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef SuplaWebServer_h -#define SuplaWebServer_h - -#include -#include -#include - -#include "SuplaConfigManager.h" - -#define GUI_BLUE "#005c96" -#define GUI_GREEN "#00D151" - -#define DEFAULT_LOGIN "admin" -#define DEFAULT_PASSWORD "password" - -#define MAX_GPIO 13 -#define OFF_GPIO 17 - -#define PATH_START "/" -#define PATH_SAVE_LOGIN "savelogin" -#define UPDATE_PATH "/firmware" -#define PATH_UPDATE "update" -#define PATH_REBOT "rbt" -#define PATH_DEVICE_SETTINGS "devicesettings" -#define PATH_DEFAULT_SETTINGS "defaultsettings" -#define PATH_LOGIN_SETTINGS "loginsettings" -#define PATH_SAVE_BOARD "saveboard" - -#define INPUT_WIFI_SSID "sid" -#define INPUT_WIFI_PASS "wpw" -#define INPUT_EMAIL "eml" -#define INPUT_SERVER "svr" -#define INPUT_HOSTNAME "shn" -#define INPUT_MODUL_LOGIN "mlg" -#define INPUT_MODUL_PASS "mps" -#define INPUT_ROLLERSHUTTER "irsr" -#define INPUT_BOARD "board" - -class SuplaWebServer : public Supla::Element { - public: - String selectGPIO(const char* input, uint8_t function, uint8_t nr = 0); - SuplaWebServer(); - void begin(); - - char www_username[MAX_MLOGIN]; - char www_password[MAX_MPASSWORD]; - char* update_path = (char*)UPDATE_PATH; - - const String SuplaFavicon(); - const String SuplaIconEdit(); - const String SuplaJavaScript(String java_return = PATH_START); - const String SuplaSaveResult(int save); - - void sendContent(const String content); - void rebootESP(); - - ESP8266WebServer httpServer = {80}; - ESP8266HTTPUpdateServer httpUpdater; - - private: - void iterateAlways(); - void handle(); - void handleSave(); - void handleWizardSave(); - void handleFirmwareUp(); - void handleDeviceSettings(); - void handleBoardSave(); - void handleDefaultSettings(); - void handleLoginSettings(); - void createWebServer(); - - String supla_webpage_start(int save); - String supla_webpage_upddate(); - void supla_webpage_reboot(); - String deviceSettings(int save); - String loginSettings(); - - void redirectToIndex(); -}; - -#endif // SuplaWebServer_h +/* + Copyright (C) krycha88 + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SuplaWebServer_h +#define SuplaWebServer_h + +#include +#include +#include + +#include "SuplaConfigManager.h" + +#define GUI_BLUE "#005c96" +#define GUI_GREEN "#00D151" + +#define DEFAULT_LOGIN "admin" +#define DEFAULT_PASSWORD "password" + +#define MAX_GPIO 13 +#define OFF_GPIO 17 + +#define PATH_START "/" +#define PATH_SAVE_LOGIN "savelogin" +#define UPDATE_PATH "/firmware" +#define PATH_UPDATE "update" +#define PATH_REBOT "rbt" +#define PATH_DEVICE_SETTINGS "devicesettings" +#define PATH_DEFAULT_SETTINGS "defaultsettings" +#define PATH_LOGIN_SETTINGS "loginsettings" +#define PATH_SAVE_BOARD "saveboard" + +#define INPUT_WIFI_SSID "sid" +#define INPUT_WIFI_PASS "wpw" +#define INPUT_EMAIL "eml" +#define INPUT_SERVER "svr" +#define INPUT_HOSTNAME "shn" +#define INPUT_MODUL_LOGIN "mlg" +#define INPUT_MODUL_PASS "mps" +#define INPUT_ROLLERSHUTTER "irsr" +#define INPUT_BOARD "board" + +class SuplaWebServer : public Supla::Element { + public: + String selectGPIO(const char* input, uint8_t function, uint8_t nr = 0); + SuplaWebServer(); + void begin(); + + char www_username[MAX_MLOGIN]; + char www_password[MAX_MPASSWORD]; + char* update_path = (char*)UPDATE_PATH; + + const String SuplaFavicon(); + const String SuplaIconEdit(); + const String SuplaJavaScript(String java_return = PATH_START); + const String SuplaSaveResult(int save); + + void sendContent(const String content); + void rebootESP(); + + ESP8266WebServer httpServer = {80}; + ESP8266HTTPUpdateServer httpUpdater; + + private: + void iterateAlways(); + void handle(); + void handleSave(); + void handleWizardSave(); + void handleFirmwareUp(); + void handleDeviceSettings(); + void handleBoardSave(); + void handleDefaultSettings(); + void handleLoginSettings(); + void createWebServer(); + + String supla_webpage_start(int save); + String supla_webpage_upddate(); + void supla_webpage_reboot(); + String deviceSettings(int save); + String loginSettings(); + + void redirectToIndex(); +}; + +#endif // SuplaWebServer_h diff --git a/language/en.h b/src/language/en.h similarity index 100% rename from language/en.h rename to src/language/en.h diff --git a/language/pl.h b/src/language/pl.h similarity index 100% rename from language/pl.h rename to src/language/pl.h From 3c9570d3b944331f4b88a8be7e0337d209fa4318 Mon Sep 17 00:00:00 2001 From: Espablo Date: Mon, 16 Nov 2020 21:46:13 +0100 Subject: [PATCH 002/233] SuplaDevice lib --- lib/SuplaDevice/examples/Afore/Afore.ino | 77 + lib/SuplaDevice/examples/DHT/DHT.ino | 92 + .../DallasTemperature/DallasTemperature.ino | 94 + lib/SuplaDevice/examples/Fronius/Fronius.ino | 76 + .../HC_SR04_Distance_sensor.ino | 81 + .../ImpulseCounter/ImpulseCounter.ino | 89 + .../examples/Pzem_V_2/Pzem_V_2.ino | 68 + .../examples/Pzem_V_3/Pzem_V_3.ino | 68 + lib/SuplaDevice/examples/RGBW/RGBW.ino | 123 + .../examples/RollerShutter/RollerShutter.ino | 92 + lib/SuplaDevice/library.properties | 12 + lib/SuplaDevice/src/SuplaDevice.cpp | 515 ++++ lib/SuplaDevice/src/SuplaDevice.h | 120 + lib/SuplaDevice/src/SuplaSomfy.cpp | 173 ++ lib/SuplaDevice/src/SuplaSomfy.h | 93 + lib/SuplaDevice/src/crc16.h | 14 + .../src/supla-common/IEEE754tools.h | 69 + lib/SuplaDevice/src/supla-common/eh.h | 68 + lib/SuplaDevice/src/supla-common/lck.c | 198 ++ lib/SuplaDevice/src/supla-common/lck.h | 50 + lib/SuplaDevice/src/supla-common/log.cpp | 293 ++ lib/SuplaDevice/src/supla-common/log.h | 58 + lib/SuplaDevice/src/supla-common/proto.c | 409 +++ lib/SuplaDevice/src/supla-common/proto.h | 1666 +++++++++++ lib/SuplaDevice/src/supla-common/srpc.c | 2566 +++++++++++++++++ lib/SuplaDevice/src/supla-common/srpc.h | 398 +++ lib/SuplaDevice/src/supla/actions.h | 82 + lib/SuplaDevice/src/supla/channel.cpp | 286 ++ lib/SuplaDevice/src/supla/channel.h | 78 + .../src/supla/channel_extended.cpp | 28 + lib/SuplaDevice/src/supla/channel_extended.h | 34 + lib/SuplaDevice/src/supla/clock/clock.cpp | 157 + lib/SuplaDevice/src/supla/clock/clock.h | 52 + .../src/supla/control/bistable_relay.cpp | 148 + .../src/supla/control/bistable_relay.h | 67 + .../supla/control/bistable_roller_shutter.cpp | 73 + .../supla/control/bistable_roller_shutter.h | 46 + lib/SuplaDevice/src/supla/control/button.cpp | 186 ++ lib/SuplaDevice/src/supla/control/button.h | 80 + .../src/supla/control/dimmer_base.h | 37 + .../src/supla/control/light_relay.cpp | 109 + .../src/supla/control/light_relay.h | 43 + lib/SuplaDevice/src/supla/control/relay.cpp | 197 ++ lib/SuplaDevice/src/supla/control/relay.h | 87 + lib/SuplaDevice/src/supla/control/rgb_base.h | 36 + .../src/supla/control/rgbw_base.cpp | 427 +++ lib/SuplaDevice/src/supla/control/rgbw_base.h | 89 + .../src/supla/control/roller_shutter.cpp | 509 ++++ .../src/supla/control/roller_shutter.h | 112 + .../src/supla/control/virtual_relay.h | 70 + lib/SuplaDevice/src/supla/element.cpp | 112 + lib/SuplaDevice/src/supla/element.h | 90 + lib/SuplaDevice/src/supla/events.h | 41 + lib/SuplaDevice/src/supla/io.cpp | 57 + lib/SuplaDevice/src/supla/io.h | 44 + lib/SuplaDevice/src/supla/local_action.cpp | 46 + lib/SuplaDevice/src/supla/local_action.h | 49 + lib/SuplaDevice/src/supla/network/ENC28J60.h | 113 + .../src/supla/network/esp32_wifi.h | 133 + lib/SuplaDevice/src/supla/network/esp_wifi.h | 233 ++ .../src/supla/network/ethernet_shield.h | 130 + lib/SuplaDevice/src/supla/network/network.cpp | 210 ++ lib/SuplaDevice/src/supla/network/network.h | 147 + lib/SuplaDevice/src/supla/pv/afore.cpp | 138 + lib/SuplaDevice/src/supla/pv/afore.h | 62 + lib/SuplaDevice/src/supla/pv/fronius.cpp | 190 ++ lib/SuplaDevice/src/supla/pv/fronius.h | 63 + lib/SuplaDevice/src/supla/sensor/BME280.h | 104 + lib/SuplaDevice/src/supla/sensor/DHT.h | 104 + lib/SuplaDevice/src/supla/sensor/DS18B20.h | 198 ++ lib/SuplaDevice/src/supla/sensor/HC_SR04.h | 70 + lib/SuplaDevice/src/supla/sensor/MAX6675_K.h | 81 + lib/SuplaDevice/src/supla/sensor/PzemV2.h | 88 + lib/SuplaDevice/src/supla/sensor/PzemV3.h | 84 + lib/SuplaDevice/src/supla/sensor/SHT3x.h | 78 + lib/SuplaDevice/src/supla/sensor/Si7021.h | 88 + .../src/supla/sensor/Si7021_sonoff.h | 154 + lib/SuplaDevice/src/supla/sensor/binary.h | 65 + lib/SuplaDevice/src/supla/sensor/distance.h | 57 + .../src/supla/sensor/electricity_meter.h | 264 ++ .../src/supla/sensor/esp_free_heap.h | 40 + .../sensor/general_purpose_measurement_base.h | 51 + .../src/supla/sensor/impulse_counter.cpp | 114 + .../src/supla/sensor/impulse_counter.h | 71 + .../src/supla/sensor/normally_open.h | 33 + .../sensor/one_phase_electricity_meter.h | 42 + lib/SuplaDevice/src/supla/sensor/pressure.h | 58 + lib/SuplaDevice/src/supla/sensor/rain.h | 58 + .../src/supla/sensor/therm_hygro_meter.h | 54 + .../supla/sensor/therm_hygro_press_meter.h | 71 + .../src/supla/sensor/thermometer.h | 56 + .../src/supla/sensor/three_phase_PzemV3.h | 108 + .../src/supla/sensor/virtual_binary.cpp | 63 + .../src/supla/sensor/virtual_binary.h | 47 + lib/SuplaDevice/src/supla/sensor/weight.h | 58 + lib/SuplaDevice/src/supla/sensor/wind.h | 58 + lib/SuplaDevice/src/supla/status.cpp | 15 + lib/SuplaDevice/src/supla/status.h | 57 + lib/SuplaDevice/src/supla/storage/eeprom.cpp | 83 + lib/SuplaDevice/src/supla/storage/eeprom.h | 39 + lib/SuplaDevice/src/supla/storage/fram_spi.h | 99 + lib/SuplaDevice/src/supla/storage/storage.cpp | 318 ++ lib/SuplaDevice/src/supla/storage/storage.h | 100 + lib/SuplaDevice/src/supla/supla_lib_config.h | 12 + lib/SuplaDevice/src/supla/timer.cpp | 115 + lib/SuplaDevice/src/supla/timer.h | 24 + lib/SuplaDevice/src/supla/tools.cpp | 42 + lib/SuplaDevice/src/supla/tools.h | 27 + lib/SuplaDevice/src/supla/triggerable.h | 28 + lib/SuplaDevice/src/supla/uptime.cpp | 63 + lib/SuplaDevice/src/supla/uptime.h | 47 + 111 files changed, 15939 insertions(+) create mode 100644 lib/SuplaDevice/examples/Afore/Afore.ino create mode 100644 lib/SuplaDevice/examples/DHT/DHT.ino create mode 100644 lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino create mode 100644 lib/SuplaDevice/examples/Fronius/Fronius.ino create mode 100644 lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino create mode 100644 lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino create mode 100644 lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino create mode 100644 lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino create mode 100644 lib/SuplaDevice/examples/RGBW/RGBW.ino create mode 100644 lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino create mode 100644 lib/SuplaDevice/library.properties create mode 100644 lib/SuplaDevice/src/SuplaDevice.cpp create mode 100644 lib/SuplaDevice/src/SuplaDevice.h create mode 100644 lib/SuplaDevice/src/SuplaSomfy.cpp create mode 100644 lib/SuplaDevice/src/SuplaSomfy.h create mode 100644 lib/SuplaDevice/src/crc16.h create mode 100644 lib/SuplaDevice/src/supla-common/IEEE754tools.h create mode 100644 lib/SuplaDevice/src/supla-common/eh.h create mode 100644 lib/SuplaDevice/src/supla-common/lck.c create mode 100644 lib/SuplaDevice/src/supla-common/lck.h create mode 100644 lib/SuplaDevice/src/supla-common/log.cpp create mode 100644 lib/SuplaDevice/src/supla-common/log.h create mode 100644 lib/SuplaDevice/src/supla-common/proto.c create mode 100644 lib/SuplaDevice/src/supla-common/proto.h create mode 100644 lib/SuplaDevice/src/supla-common/srpc.c create mode 100644 lib/SuplaDevice/src/supla-common/srpc.h create mode 100644 lib/SuplaDevice/src/supla/actions.h create mode 100644 lib/SuplaDevice/src/supla/channel.cpp create mode 100644 lib/SuplaDevice/src/supla/channel.h create mode 100644 lib/SuplaDevice/src/supla/channel_extended.cpp create mode 100644 lib/SuplaDevice/src/supla/channel_extended.h create mode 100644 lib/SuplaDevice/src/supla/clock/clock.cpp create mode 100644 lib/SuplaDevice/src/supla/clock/clock.h create mode 100644 lib/SuplaDevice/src/supla/control/bistable_relay.cpp create mode 100644 lib/SuplaDevice/src/supla/control/bistable_relay.h create mode 100644 lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp create mode 100644 lib/SuplaDevice/src/supla/control/bistable_roller_shutter.h create mode 100644 lib/SuplaDevice/src/supla/control/button.cpp create mode 100644 lib/SuplaDevice/src/supla/control/button.h create mode 100644 lib/SuplaDevice/src/supla/control/dimmer_base.h create mode 100644 lib/SuplaDevice/src/supla/control/light_relay.cpp create mode 100644 lib/SuplaDevice/src/supla/control/light_relay.h create mode 100644 lib/SuplaDevice/src/supla/control/relay.cpp create mode 100644 lib/SuplaDevice/src/supla/control/relay.h create mode 100644 lib/SuplaDevice/src/supla/control/rgb_base.h create mode 100644 lib/SuplaDevice/src/supla/control/rgbw_base.cpp create mode 100644 lib/SuplaDevice/src/supla/control/rgbw_base.h create mode 100644 lib/SuplaDevice/src/supla/control/roller_shutter.cpp create mode 100644 lib/SuplaDevice/src/supla/control/roller_shutter.h create mode 100644 lib/SuplaDevice/src/supla/control/virtual_relay.h create mode 100644 lib/SuplaDevice/src/supla/element.cpp create mode 100644 lib/SuplaDevice/src/supla/element.h create mode 100644 lib/SuplaDevice/src/supla/events.h create mode 100644 lib/SuplaDevice/src/supla/io.cpp create mode 100644 lib/SuplaDevice/src/supla/io.h create mode 100644 lib/SuplaDevice/src/supla/local_action.cpp create mode 100644 lib/SuplaDevice/src/supla/local_action.h create mode 100644 lib/SuplaDevice/src/supla/network/ENC28J60.h create mode 100644 lib/SuplaDevice/src/supla/network/esp32_wifi.h create mode 100644 lib/SuplaDevice/src/supla/network/esp_wifi.h create mode 100644 lib/SuplaDevice/src/supla/network/ethernet_shield.h create mode 100644 lib/SuplaDevice/src/supla/network/network.cpp create mode 100644 lib/SuplaDevice/src/supla/network/network.h create mode 100644 lib/SuplaDevice/src/supla/pv/afore.cpp create mode 100644 lib/SuplaDevice/src/supla/pv/afore.h create mode 100644 lib/SuplaDevice/src/supla/pv/fronius.cpp create mode 100644 lib/SuplaDevice/src/supla/pv/fronius.h create mode 100644 lib/SuplaDevice/src/supla/sensor/BME280.h create mode 100644 lib/SuplaDevice/src/supla/sensor/DHT.h create mode 100644 lib/SuplaDevice/src/supla/sensor/DS18B20.h create mode 100644 lib/SuplaDevice/src/supla/sensor/HC_SR04.h create mode 100644 lib/SuplaDevice/src/supla/sensor/MAX6675_K.h create mode 100644 lib/SuplaDevice/src/supla/sensor/PzemV2.h create mode 100644 lib/SuplaDevice/src/supla/sensor/PzemV3.h create mode 100644 lib/SuplaDevice/src/supla/sensor/SHT3x.h create mode 100644 lib/SuplaDevice/src/supla/sensor/Si7021.h create mode 100644 lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h create mode 100644 lib/SuplaDevice/src/supla/sensor/binary.h create mode 100644 lib/SuplaDevice/src/supla/sensor/distance.h create mode 100644 lib/SuplaDevice/src/supla/sensor/electricity_meter.h create mode 100644 lib/SuplaDevice/src/supla/sensor/esp_free_heap.h create mode 100644 lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h create mode 100644 lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/impulse_counter.h create mode 100644 lib/SuplaDevice/src/supla/sensor/normally_open.h create mode 100644 lib/SuplaDevice/src/supla/sensor/one_phase_electricity_meter.h create mode 100644 lib/SuplaDevice/src/supla/sensor/pressure.h create mode 100644 lib/SuplaDevice/src/supla/sensor/rain.h create mode 100644 lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h create mode 100644 lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h create mode 100644 lib/SuplaDevice/src/supla/sensor/thermometer.h create mode 100644 lib/SuplaDevice/src/supla/sensor/three_phase_PzemV3.h create mode 100644 lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/virtual_binary.h create mode 100644 lib/SuplaDevice/src/supla/sensor/weight.h create mode 100644 lib/SuplaDevice/src/supla/sensor/wind.h create mode 100644 lib/SuplaDevice/src/supla/status.cpp create mode 100644 lib/SuplaDevice/src/supla/status.h create mode 100644 lib/SuplaDevice/src/supla/storage/eeprom.cpp create mode 100644 lib/SuplaDevice/src/supla/storage/eeprom.h create mode 100644 lib/SuplaDevice/src/supla/storage/fram_spi.h create mode 100644 lib/SuplaDevice/src/supla/storage/storage.cpp create mode 100644 lib/SuplaDevice/src/supla/storage/storage.h create mode 100644 lib/SuplaDevice/src/supla/supla_lib_config.h create mode 100644 lib/SuplaDevice/src/supla/timer.cpp create mode 100644 lib/SuplaDevice/src/supla/timer.h create mode 100644 lib/SuplaDevice/src/supla/tools.cpp create mode 100644 lib/SuplaDevice/src/supla/tools.h create mode 100644 lib/SuplaDevice/src/supla/triggerable.h create mode 100644 lib/SuplaDevice/src/supla/uptime.cpp create mode 100644 lib/SuplaDevice/src/supla/uptime.h diff --git a/lib/SuplaDevice/examples/Afore/Afore.ino b/lib/SuplaDevice/examples/Afore/Afore.ino new file mode 100644 index 00000000..eaace6d9 --- /dev/null +++ b/lib/SuplaDevice/examples/Afore/Afore.ino @@ -0,0 +1,77 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(115200); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // CHANNEL0 + // Put IP address of your Afore inverter, then port, and last parametere is base64 encoded "login:password" + // You can use any online base64 encoder to convert your login and password, i.e. https://www.base64encode.org/ + new Supla::PV::Afore(IPAddress(192, 168, 0, 59), 80, "bG9naW46cGFzc3dvcmQ="); + + /* + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key +} + +void loop() { + SuplaDevice.iterate(); +} + diff --git a/lib/SuplaDevice/examples/DHT/DHT.ino b/lib/SuplaDevice/examples/DHT/DHT.ino new file mode 100644 index 00000000..b91c0fea --- /dev/null +++ b/lib/SuplaDevice/examples/DHT/DHT.ino @@ -0,0 +1,92 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +/* + * This example requires DHT sensor library installed. + * https://github.com/adafruit/DHT-sensor-library + */ + +#define DHT1PIN 24 +#define DHT1TYPE DHT22 +#define DHT2PIN 25 +#define DHT2TYPE DHT22 + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // This example adds two DHT22 sensors. + + // CHANNEL0 - DHT22 Sensor + new Supla::Sensor::DHT(DHT1PIN, DHT1TYPE); + + // CHANNEL1 - DHT22 Sensor + new Supla::Sensor::DHT(DHT2PIN, DHT2TYPE); + + /* + * SuplaDevice Initialization. + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino b/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino new file mode 100644 index 00000000..f10308df --- /dev/null +++ b/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino @@ -0,0 +1,94 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +/* + * This example requires Dallas Temperature Control library installed. + * https://github.com/milesburton/Arduino-Temperature-Control-Library + */ +// Add include to DS sensor +#include + + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // CHANNEL0-3 - Thermometer DS18B20 + // 4 DS18B20 thermometers at pin 23. DS address can be omitted when there is only one device at a pin + DeviceAddress ds1addr = {0x28, 0xFF, 0xC8, 0xAB, 0x6E, 0x18, 0x01, 0xFC}; + DeviceAddress ds2addr = {0x28, 0xFF, 0x54, 0x73, 0x6E, 0x18, 0x01, 0x77}; + DeviceAddress ds3addr = {0x28, 0xFF, 0x55, 0xCA, 0x6B, 0x18, 0x01, 0x8D}; + DeviceAddress ds4addr = {0x28, 0xFF, 0x4F, 0xAB, 0x6E, 0x18, 0x01, 0x66}; + + new Supla::Sensor::DS18B20(23, ds1addr); + new Supla::Sensor::DS18B20(23, ds2addr); + new Supla::Sensor::DS18B20(23, ds3addr); + new Supla::Sensor::DS18B20(23, ds4addr); + + + /* + * SuplaDevice Initialization. + * Server address, LocationID and LocationPassword are available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/Fronius/Fronius.ino b/lib/SuplaDevice/examples/Fronius/Fronius.ino new file mode 100644 index 00000000..98b3b7ad --- /dev/null +++ b/lib/SuplaDevice/examples/Fronius/Fronius.ino @@ -0,0 +1,76 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(115200); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // CHANNEL0 + // Put IP address of your Fronius inverter, then port (deafult is 80) + new Supla::PV::Fronius(IPAddress(192, 168, 0, 59)); + + /* + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key +} + +void loop() { + SuplaDevice.iterate(); +} + diff --git a/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino b/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino new file mode 100644 index 00000000..2de1ac58 --- /dev/null +++ b/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino @@ -0,0 +1,81 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +// Add include to HC_SR04 sensor +#include + + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + + new Supla::Sensor::HC_SR04(12,13);//(trigPin, echoPin) + + + /* + * SuplaDevice Initialization. + * Server address, LocationID and LocationPassword are available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino b/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino new file mode 100644 index 00000000..5035b970 --- /dev/null +++ b/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino @@ -0,0 +1,89 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +// Choose where Supla should store counter data in persistant memory +// We recommend to use external FRAM memory +#define STORAGE_OFFSET 100 +#include +Supla::Eeprom eeprom(STORAGE_OFFSET); +// #include +// Supla::FramSpi fram(STORAGE_OFFSET); + + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // CHANNEL0 - Impulse Counter on pin 34, counting raising edge (from LOW to HIGH), no pullup on pin, and 10 ms debounce timeout + new Supla::Sensor::ImpulseCounter(34, true, false, 10); + + // CHANNEL1 - Impulse Counter on pin 35, counting falling edge (from HIGH to LOW), with pullup on pin, and 50 ms debounce timeout + new Supla::Sensor::ImpulseCounter(35, false, true, 50); + + + /* + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} + diff --git a/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino b/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino new file mode 100644 index 00000000..e03d4aa9 --- /dev/null +++ b/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino @@ -0,0 +1,68 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + // this example will work only on esp8266 and esp32 boards. On Arduino mega it will not fly. + //dependence: Arduino communication library for Peacefair PZEM-004T Energy monitor https://github.com/olehs/PZEM004T + +#include +#include +#include + +// ESP8266 based board: +#include +Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + new Supla::Sensor::PZEMv2(5, 4); // (RX,TX) + + + /* + * SuplaDevice Initialization. + * Server address, is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino b/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino new file mode 100644 index 00000000..fd435453 --- /dev/null +++ b/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino @@ -0,0 +1,68 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + // this example will work only on esp8266 and esp32 boards. On Arduino mega it will not fly. + //dependence: Arduino library for the Updated PZEM-004T v3.0 Power and Energy meter https://github.com/mandulaj/PZEM-004T-v30 + +#include +#include +#include + +// ESP8266 based board: +#include +Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + new Supla::Sensor::PZEMv3(5, 4); // (RX,TX) + + + /* + * SuplaDevice Initialization. + * Server address, is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/RGBW/RGBW.ino b/lib/SuplaDevice/examples/RGBW/RGBW.ino new file mode 100644 index 00000000..994fd701 --- /dev/null +++ b/lib/SuplaDevice/examples/RGBW/RGBW.ino @@ -0,0 +1,123 @@ +/* +Copyright (C) AC SOFTWARE SP. Z O.O. + +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 2 +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 should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + +/* + * Youtube: https://youtu.be/FE9tqzTjmA4 + * Youtube example was done on older version of SuplaDevice library + */ + +#define RED_PIN 44 +#define GREEN_PIN 45 +#define BLUE_PIN 46 +#define BRIGHTNESS_PIN 7 +#define COLOR_BRIGHTNESS_PIN 8 + +class RgbwLeds : public Supla::Control::RGBWBase { + public: + RgbwLeds(int redPin, + int greenPin, + int bluePin, + int colorBrightnessPin, + int brightnessPin) + : redPin(redPin), + greenPin(greenPin), + bluePin(bluePin), + colorBrightnessPin(colorBrightnessPin), + brightnessPin(brightnessPin) { + } + + void setRGBWValueOnDevice(uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t colorBrightness, + uint8_t brightness) { + analogWrite(brightnessPin, (brightness * 255) / 100); + analogWrite(colorBrightnessPin, (colorBrightness * 255) / 100); + analogWrite(redPin, red); + analogWrite(greenPin, green); + analogWrite(bluePin, blue); + } + + protected: + int redPin; + int greenPin; + int bluePin; + int brightnessPin; + int colorBrightnessPin; +}; + +void setup() { + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + // CHANNEL0 - RGB controller and dimmer (RGBW) + new RgbwLeds( + RED_PIN, GREEN_PIN, BLUE_PIN, COLOR_BRIGHTNESS_PIN, BRIGHTNESS_PIN); + + /* + * SuplaDevice Initialization. + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at + * https://cloud.supla.org/account/create SUPLA and SUPLA CLOUD are free of + * charge + * + */ + + SuplaDevice.begin( + GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino b/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino new file mode 100644 index 00000000..9747b4e6 --- /dev/null +++ b/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino @@ -0,0 +1,92 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include + +// Choose where Supla should store roller shutter data in persistant memory +// We recommend to use external FRAM memory +#define STORAGE_OFFSET 100 +#include +Supla::Eeprom eeprom(STORAGE_OFFSET); +// #include +// Supla::FramSpi fram(STORAGE_OFFSET); + + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +// #include +// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + + +void setup() { + + Serial.begin(9600); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + Supla::Control::RollerShutter *rs = new Supla::Control::RollerShutter(30, 31, false); + Supla::Control::Button *buttonOpen = new Supla::Control::Button(28, true, true); + Supla::Control::Button *buttonClose = new Supla::Control::Button(29, true, true); + + buttonOpen->addAction(Supla::OPEN_OR_STOP, *rs, Supla::ON_PRESS); + buttonClose->addAction(Supla::CLOSE_OR_STOP, *rs, Supla::ON_PRESS); + + /* + * SuplaDevice Initialization. + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/library.properties b/lib/SuplaDevice/library.properties new file mode 100644 index 00000000..a1277c9e --- /dev/null +++ b/lib/SuplaDevice/library.properties @@ -0,0 +1,12 @@ +name=SuplaDevice +author=Przemyslaw Zygmunt +email=Przemyslaw Zygmunt +maintainer=Krzysztof Lewandowski +sentence=Library enables you to connect the device to SUPLA system. +paragraph=The Library supports Ethernet modules based on ENC28J60 and the popular Ethernet Shield based on W5100. Regarding the ENC28J60 chip, additional UIPEthernet library must be installed. WiFi interface on ESP8266 based devices is also supported. +url=https://www.supla.org +architectures=avr,esp32,esp8266 +version=2.3 +dependencies= +core-dependencies=arduino (>=1.5.0) +category=Communication diff --git a/lib/SuplaDevice/src/SuplaDevice.cpp b/lib/SuplaDevice/src/SuplaDevice.cpp new file mode 100644 index 00000000..b4ddfbab --- /dev/null +++ b/lib/SuplaDevice/src/SuplaDevice.cpp @@ -0,0 +1,515 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include "SuplaDevice.h" +#include "supla-common/IEEE754tools.h" +#include "supla-common/log.h" +#include "supla-common/srpc.h" +#include "supla/channel.h" +#include "supla/element.h" +#include "supla/io.h" +#include "supla/storage/storage.h" +#include "supla/timer.h" + +void SuplaDeviceClass::status(int status, const char *msg) { + if (impl_arduino_status != NULL) { + impl_arduino_status(status, msg); + } else { + if (currentStatus != status) { + currentStatus = status; + supla_log(LOG_DEBUG, "Current status: [%d] %s", status, msg); + } + } +} + +SuplaDeviceClass::SuplaDeviceClass() + : port(-1), + connectionFailCounter(0), + networkIsNotReadyCounter(0), + currentStatus(STATUS_UNKNOWN) { + srpc = NULL; + registered = 0; + last_iterate_time = 0; + wait_for_iterate = 0; +} + +SuplaDeviceClass::~SuplaDeviceClass() { +} + +void SuplaDeviceClass::setStatusFuncImpl( + _impl_arduino_status impl_arduino_status) { + this->impl_arduino_status = impl_arduino_status; +} + +bool SuplaDeviceClass::isInitialized(bool msg) { + if (srpc != NULL) { + if (msg) + status(STATUS_ALREADY_INITIALIZED, "SuplaDevice is already initialized"); + + return true; + } + + return false; +} + +bool SuplaDeviceClass::begin(char GUID[SUPLA_GUID_SIZE], + const char *Server, + const char *email, + char authkey[SUPLA_AUTHKEY_SIZE], + unsigned char version) { + setGUID(GUID); + setServer(Server); + setEmail(email); + setAuthKey(authkey); + + return begin(version); +} + +bool SuplaDeviceClass::begin(unsigned char version) { + if (isInitialized(true)) return false; + supla_log(LOG_DEBUG, "Supla - starting initialization"); + + Supla::Storage::Init(); + + if (Supla::Network::Instance() == NULL) { + status(STATUS_MISSING_NETWORK_INTERFACE, "Network Interface not defined!"); + return false; + } + + // Supla::Storage::LoadDeviceConfig(); + // Supla::Storage::LoadElementConfig(); + + // Pefrorm dry run of write state to validate stored state section with current + // device configuration + Serial.println(F("Validating storage state section with current device configuration")); + Supla::Storage::PrepareState(true); + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onSaveState(); + } + // If state storage validation was successful, perform read state + if (Supla::Storage::FinalizeSaveState()) { + Serial.println(F("Storage state section validation completed. Loading elements state...")); + // Iterate all elements and load state + Supla::Storage::PrepareState(); + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onLoadState(); + } + } + + // Initialize elements + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onInit(); + } + + // Enable timers + Supla::initTimers(); + + + bool emptyGuidDetected = true; + for (int i = 0; i < SUPLA_GUID_SIZE; i++) { + if (Supla::Channel::reg_dev.GUID[i] != 0) { + emptyGuidDetected = false; + } + } + if (emptyGuidDetected) { + status(STATUS_INVALID_GUID, "Invalid GUID"); + return false; + } + + if (Supla::Channel::reg_dev.ServerName[0] == NULL) { + status(STATUS_UNKNOWN_SERVER_ADDRESS, "Unknown server address"); + return false; + } + + if (Supla::Channel::reg_dev.Email[0] == NULL) { + status(STATUS_MISSING_CREDENTIALS, "Unknown email address"); + return false; + } + + bool emptyAuthKeyDetected = true; + for (int i = 0; i < SUPLA_AUTHKEY_SIZE; i++) { + if (Supla::Channel::reg_dev.AuthKey[i] != 0) { + emptyAuthKeyDetected = false; + break; + } + } + if (emptyAuthKeyDetected) { + status(STATUS_MISSING_CREDENTIALS, "Unknown AuthKey"); + return false; + } + + if (strnlen(Supla::Channel::reg_dev.Name, SUPLA_DEVICE_NAME_MAXSIZE) == 0) { +#if defined(ARDUINO_ARCH_ESP8266) + setString( + Supla::Channel::reg_dev.Name, "ESP8266", SUPLA_DEVICE_NAME_MAXSIZE); +#elif defined(ARDUINO_ARCH_ESP32) + setString(Supla::Channel::reg_dev.Name, "ESP32", SUPLA_DEVICE_NAME_MAXSIZE); +#else + setString( + Supla::Channel::reg_dev.Name, "ARDUINO", SUPLA_DEVICE_NAME_MAXSIZE); +#endif + } + + if (strnlen(Supla::Channel::reg_dev.SoftVer, SUPLA_SOFTVER_MAXSIZE) == 0) { + setString(Supla::Channel::reg_dev.SoftVer, + "User SW, lib 2.3.2", + SUPLA_SOFTVER_MAXSIZE); + } + + Serial.println(F("Initializing network layer")); + Supla::Network::Setup(); + + TsrpcParams srpc_params; + srpc_params_init(&srpc_params); + srpc_params.data_read = &Supla::data_read; + srpc_params.data_write = &Supla::data_write; + srpc_params.on_remote_call_received = &Supla::message_received; + srpc_params.user_params = this; + + srpc = srpc_init(&srpc_params); + Supla::Network::SetSrpc(srpc); + + // Set Supla protocol interface version + srpc_set_proto_version(srpc, version); + + supla_log(LOG_DEBUG, "Using Supla protocol version %d", version); + + status(STATUS_INITIALIZED, "SuplaDevice initialized"); + return true; +} + +void SuplaDeviceClass::setName(const char *Name) { + if (isInitialized(true)) return; + setString(Supla::Channel::reg_dev.Name, Name, SUPLA_DEVICE_NAME_MAXSIZE); +} + +void SuplaDeviceClass::setString(char *dst, const char *src, int max_size) { + if (src == NULL) { + dst[0] = 0; + return; + } + + int size = strlen(src); + + if (size + 1 > max_size) size = max_size - 1; + + memcpy(dst, src, size); +} + +void SuplaDeviceClass::onTimer(void) { + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onTimer(); + } +} + +void SuplaDeviceClass::onFastTimer(void) { + // Iteration over all impulse counters will count incomming impulses. It is + // after SuplaDevice initialization (because we have to read stored counter + // values) and before any other operation like connection to Supla cloud + // (because we want to count impulses even when we have connection issues. + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onFastTimer(); + } +} + +void SuplaDeviceClass::iterate(void) { + if (!isInitialized(false)) return; + + unsigned long _millis = millis(); + unsigned long time_diff = abs(_millis - last_iterate_time); + + uptime.iterate(_millis); + + // Iterate all elements + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->iterateAlways(); + } + + // Iterate all elements and saves state + if (Supla::Storage::SaveStateAllowed(_millis)) { + Supla::Storage::PrepareState(); + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + element->onSaveState(); + } + Supla::Storage::FinalizeSaveState(); + } + + if (wait_for_iterate != 0 && _millis < wait_for_iterate) { + return; + + } else { + wait_for_iterate = 0; + } + + // Restart network after >1 min of failed connection attempts + if (connectionFailCounter > 30) { + connectionFailCounter = 0; + supla_log(LOG_DEBUG, + "Connection fail counter overflow. Trying to setup network " + "interface again"); + Supla::Network::Setup(); + return; + } + + if (!Supla::Network::IsReady()) { + uptime.setConnectionLostCause( + SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST); + wait_for_iterate = millis() + 100; + status(STATUS_NETWORK_DISCONNECTED, "No connection to network"); + networkIsNotReadyCounter++; + if (networkIsNotReadyCounter > 20) { + networkIsNotReadyCounter = 0; + connectionFailCounter++; + } + return; + } + networkIsNotReadyCounter = 0; + + if (!Supla::Network::Connected()) { + status(STATUS_SERVER_DISCONNECTED, "Not connected to Supla server"); + + uptime.setConnectionLostCause( + SUPLA_LASTCONNECTIONRESETCAUSE_SERVER_CONNECTION_LOST); + + registered = 0; + + int result = + Supla::Network::Connect(Supla::Channel::reg_dev.ServerName, port); + if (1 == result) { + uptime.resetConnectionUptime(); + connectionFailCounter = 0; + supla_log(LOG_DEBUG, "Connected to Supla Server"); + } else { + supla_log(LOG_DEBUG, + "Connection fail (%d). Server: %s", + result, + Supla::Channel::reg_dev.ServerName); + + Supla::Network::Disconnect(); + wait_for_iterate = millis() + 2000; + connectionFailCounter++; + return; + } + } + + Supla::Network::Iterate(); + + if (srpc_iterate(srpc) == SUPLA_RESULT_FALSE) { + status(STATUS_ITERATE_FAIL, "Iterate fail"); + Supla::Network::Disconnect(); + + wait_for_iterate = millis() + 5000; + return; + } + + if (registered == 0) { + registered = -1; + status(STATUS_REGISTER_IN_PROGRESS, "Register in progress"); + if (!srpc_ds_async_registerdevice_e(srpc, &Supla::Channel::reg_dev)) { + supla_log(LOG_DEBUG, "Fatal SRPC failure!"); + } + Supla::Channel::clearAllUpdateReady(); + + } else if (registered == 1) { + if (Supla::Network::Ping() == false) { + uptime.setConnectionLostCause( + SUPLA_LASTCONNECTIONRESETCAUSE_ACTIVITY_TIMEOUT); + supla_log(LOG_DEBUG, "TIMEOUT - lost connection with server"); + Supla::Network::Disconnect(); + } + + if (time_diff > 0) { + // Iterate all elements + for (auto element = Supla::Element::begin(); element != nullptr; + element = element->next()) { + if (!element->iterateConnected(srpc)) { + break; + } + } + + last_iterate_time = millis(); + } + } +} + +void SuplaDeviceClass::onVersionError(TSDC_SuplaVersionError *version_error) { + status(STATUS_PROTOCOL_VERSION_ERROR, "Protocol version error"); + Supla::Network::Disconnect(); + + wait_for_iterate = millis() + 5000; +} + +void SuplaDeviceClass::onRegisterResult( + TSD_SuplaRegisterDeviceResult *register_device_result) { + _supla_int_t activity_timeout = 0; + + switch (register_device_result->result_code) { + // OK scenario + case SUPLA_RESULTCODE_TRUE: + activity_timeout = register_device_result->activity_timeout; + Supla::Network::Instance()->setActivityTimeout(activity_timeout); + registered = 1; + supla_log(LOG_DEBUG, + "Device registered (activity timeout %d s, server version: %d, " + "server min version: %d)", + register_device_result->activity_timeout, + register_device_result->version, + register_device_result->version_min); + last_iterate_time = millis(); + status(STATUS_REGISTERED_AND_READY, "Registered and ready."); + + if (activity_timeout != ACTIVITY_TIMEOUT) { + supla_log( + LOG_DEBUG, "Changing activity timeout to %d", ACTIVITY_TIMEOUT); + TDCS_SuplaSetActivityTimeout at; + at.activity_timeout = ACTIVITY_TIMEOUT; + srpc_dcs_async_set_activity_timeout(srpc, &at); + } + + return; + + // NOK scenarios + case SUPLA_RESULTCODE_BAD_CREDENTIALS: + status(STATUS_BAD_CREDENTIALS, "Bad credentials!"); + break; + + case SUPLA_RESULTCODE_TEMPORARILY_UNAVAILABLE: + status(STATUS_TEMPORARILY_UNAVAILABLE, "Temporarily unavailable!"); + break; + + case SUPLA_RESULTCODE_LOCATION_CONFLICT: + status(STATUS_LOCATION_CONFLICT, "Location conflict!"); + break; + + case SUPLA_RESULTCODE_CHANNEL_CONFLICT: + status(STATUS_CHANNEL_CONFLICT, "Channel conflict!"); + break; + case SUPLA_RESULTCODE_DEVICE_DISABLED: + status(STATUS_DEVICE_IS_DISABLED, "Device is disabled!"); + break; + + case SUPLA_RESULTCODE_LOCATION_DISABLED: + status(STATUS_LOCATION_IS_DISABLED, "Location is disabled!"); + break; + + case SUPLA_RESULTCODE_DEVICE_LIMITEXCEEDED: + status(STATUS_DEVICE_LIMIT_EXCEEDED, "Device limit exceeded!"); + break; + + case SUPLA_RESULTCODE_GUID_ERROR: + status(STATUS_INVALID_GUID, "Incorrect device GUID!"); + break; + + case SUPLA_RESULTCODE_AUTHKEY_ERROR: + status(STATUS_INVALID_GUID, "Incorrect AuthKey!"); + break; + + case SUPLA_RESULTCODE_REGISTRATION_DISABLED: + status(STATUS_INVALID_GUID, "Registration disabled!"); + break; + + case SUPLA_RESULTCODE_NO_LOCATION_AVAILABLE: + status(STATUS_INVALID_GUID, "No location available!"); + break; + + case SUPLA_RESULTCODE_USER_CONFLICT: + status(STATUS_INVALID_GUID, "User conflict!"); + break; + + default: + supla_log(LOG_ERR, + "Register result code %i", + register_device_result->result_code); + break; + } + + Supla::Network::Disconnect(); + wait_for_iterate = millis() + 5000; +} + +void SuplaDeviceClass::channelSetActivityTimeoutResult( + TSDC_SuplaSetActivityTimeoutResult *result) { + Supla::Network::Instance()->setActivityTimeout(result->activity_timeout); + supla_log( + LOG_DEBUG, "Activity timeout set to %d s", result->activity_timeout); +} + +void SuplaDeviceClass::setServerPort(int value) { + port = value; +} + +void SuplaDeviceClass::setSwVersion(const char *swVersion) { + setString(Supla::Channel::reg_dev.SoftVer, swVersion, SUPLA_SOFTVER_MAXSIZE); +} + +int SuplaDeviceClass::getCurrentStatus() { + return currentStatus; +} + +void SuplaDeviceClass::fillStateData(TDSC_ChannelState &channelState) { + channelState.Fields |= SUPLA_CHANNELSTATE_FIELD_UPTIME | + SUPLA_CHANNELSTATE_FIELD_CONNECTIONUPTIME; + + channelState.Uptime = uptime.getUptime(); + channelState.ConnectionUptime = uptime.getConnectionUptime(); + if (uptime.getLastResetCause() > 0) { + channelState.Fields |= SUPLA_CHANNELSTATE_FIELD_LASTCONNECTIONRESETCAUSE; + channelState.LastConnectionResetCause = uptime.getLastResetCause(); + } +} + +void SuplaDeviceClass::setGUID(char GUID[SUPLA_GUID_SIZE]) { + memcpy(Supla::Channel::reg_dev.GUID, GUID, SUPLA_GUID_SIZE); +} + +void SuplaDeviceClass::setAuthKey(char authkey[SUPLA_AUTHKEY_SIZE]) { + memcpy(Supla::Channel::reg_dev.AuthKey, authkey, SUPLA_AUTHKEY_SIZE); +} + +void SuplaDeviceClass::setEmail(const char *email) { + setString(Supla::Channel::reg_dev.Email, email, SUPLA_EMAIL_MAXSIZE); +} + +void SuplaDeviceClass::setServer(const char *server) { + setString( + Supla::Channel::reg_dev.ServerName, server, SUPLA_SERVER_NAME_MAXSIZE); +} + +void SuplaDeviceClass::onGetUserLocaltimeResult(TSDC_UserLocalTimeResult *result) { + if (clock) { + clock->parseLocaltimeFromServer(result); + } +} + +void SuplaDeviceClass::addClock(Supla::Clock *_clock) { + Serial.println(F("Clock class added")); + clock = _clock; +} + +Supla::Clock * SuplaDeviceClass::getClock() { + return clock; +} + +SuplaDeviceClass SuplaDevice; diff --git a/lib/SuplaDevice/src/SuplaDevice.h b/lib/SuplaDevice/src/SuplaDevice.h new file mode 100644 index 00000000..5c1067fe --- /dev/null +++ b/lib/SuplaDevice/src/SuplaDevice.h @@ -0,0 +1,120 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef SUPLADEVICE_H +#define SUPLADEVICE_H + +#include + +#include "supla-common/proto.h" +#include "supla/network/network.h" +#include "supla/uptime.h" +#include "supla/clock/clock.h" + +#define ACTIVITY_TIMEOUT 30 + +#define STATUS_UNKNOWN -1 +#define STATUS_ALREADY_INITIALIZED 2 +#define STATUS_MISSING_NETWORK_INTERFACE 3 +#define STATUS_INVALID_GUID 4 +#define STATUS_UNKNOWN_SERVER_ADDRESS 5 +#define STATUS_UNKNOWN_LOCATION_ID 6 +#define STATUS_INITIALIZED 7 +#define STATUS_CHANNEL_LIMIT_EXCEEDED 8 +#define STATUS_SERVER_DISCONNECTED 9 +#define STATUS_REGISTER_IN_PROGRESS 10 +#define STATUS_ITERATE_FAIL 11 +#define STATUS_PROTOCOL_VERSION_ERROR 12 +#define STATUS_BAD_CREDENTIALS 13 +#define STATUS_TEMPORARILY_UNAVAILABLE 14 +#define STATUS_LOCATION_CONFLICT 15 +#define STATUS_CHANNEL_CONFLICT 16 +#define STATUS_REGISTERED_AND_READY 17 +#define STATUS_DEVICE_IS_DISABLED 18 +#define STATUS_LOCATION_IS_DISABLED 19 +#define STATUS_DEVICE_LIMIT_EXCEEDED 20 +#define STATUS_NETWORK_DISCONNECTED 21 +#define STATUS_REGISTRATION_DISABLED 22 +#define STATUS_MISSING_CREDENTIALS 23 + +typedef void (*_impl_arduino_status)(int status, const char *msg); + +class SuplaDeviceClass { + protected: + void *srpc; + char registered; + int port; + int connectionFailCounter; + int networkIsNotReadyCounter; + + unsigned long last_iterate_time; + unsigned long wait_for_iterate; + + _impl_arduino_status impl_arduino_status; + int currentStatus; + + Supla::Uptime uptime; + Supla::Clock *clock; + + bool isInitialized(bool msg); + void setString(char *dst, const char *src, int max_size); + + private: + void status(int status, const char *msg); + + public: + SuplaDeviceClass(); + ~SuplaDeviceClass(); + + void fillStateData(TDSC_ChannelState &channelState); + void addClock(Supla::Clock *clock); + Supla::Clock *getClock(); + + bool begin(char GUID[SUPLA_GUID_SIZE], + const char *Server, + const char *email, + char authkey[SUPLA_AUTHKEY_SIZE], + unsigned char version = 12); + + bool begin(unsigned char version = 12); + + void setName(const char *Name); + void setGUID(char GUID[SUPLA_GUID_SIZE]); + void setAuthKey(char authkey[SUPLA_AUTHKEY_SIZE]); + void setEmail(const char *email); + void setServer(const char *server); + + // Timer with 100 Hz frequency (10 ms) + void onTimer(void); + // TImer with 2000 Hz frequency (0.5 ms) + void onFastTimer(void); + void iterate(void); + + void setStatusFuncImpl(_impl_arduino_status impl_arduino_status); + void setServerPort(int value); + + void onVersionError(TSDC_SuplaVersionError *version_error); + void onRegisterResult(TSD_SuplaRegisterDeviceResult *register_device_result); + void channelSetActivityTimeoutResult( + TSDC_SuplaSetActivityTimeoutResult *result); + void onGetUserLocaltimeResult(TSDC_UserLocalTimeResult *result); + + void setSwVersion(const char *); + int getCurrentStatus(); +}; + +extern SuplaDeviceClass SuplaDevice; +#endif diff --git a/lib/SuplaDevice/src/SuplaSomfy.cpp b/lib/SuplaDevice/src/SuplaSomfy.cpp new file mode 100644 index 00000000..f08b98c8 --- /dev/null +++ b/lib/SuplaDevice/src/SuplaSomfy.cpp @@ -0,0 +1,173 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "SuplaSomfy.h" + +/* + Library for Somfy RTS remote controller + Author: Maciej Królewski + Using documantation: https://pushstack.wordpress.com/somfy-rts-protocol/ +*/ + +SuplaSomfy::SuplaSomfy(uint8_t dataPin) { + pinMode(dataPin, OUTPUT); + digitalWrite(dataPin, LOW); + + _dataPin = dataPin; +} + +SuplaSomfy::~SuplaSomfy(void) { +} + +void SuplaSomfy::SendBitZero(void) { + digitalWrite(_dataPin, HIGH); + delayMicroseconds(BASIC_TIME); + digitalWrite(_dataPin, LOW); + delayMicroseconds(BASIC_TIME); +} + +void SuplaSomfy::SendBitOne(void) { + digitalWrite(_dataPin, LOW); + delayMicroseconds(BASIC_TIME); + digitalWrite(_dataPin, HIGH); + delayMicroseconds(BASIC_TIME); +} + +uint8_t SuplaSomfy::Checksum(somfy_frame_t *frame) { + uint8_t checksum = 0; + for (uint8_t i = 0; i < 7; i++) { + checksum = checksum ^ frame[i] ^ (frame[i] >> 4); + } + + return checksum & 0xF; +} + +void SuplaSomfy::Obfuscation(somfy_frame_t *frame) { + for (uint8_t i = 1; i < 7; i++) { + frame[i] ^= frame[i - 1]; + } +} + +void SuplaSomfy::SendCommand(somfy_frame_t *frame, uint8_t sync) { + // Only with the first frame + if (sync == 2) { + // Wake-up pulse + digitalWrite(_dataPin, HIGH); + delayMicroseconds(9415); + // Silence pulse + digitalWrite(_dataPin, LOW); + delayMicroseconds(89565); + } + + // Hardware sync (Two sync for the first frame, seven for the next frame) + for (uint8_t i = 0; i < sync; i++) { + digitalWrite(_dataPin, HIGH); + delayMicroseconds(4 * BASIC_TIME); + digitalWrite(_dataPin, LOW); + delayMicroseconds(4 * BASIC_TIME); + } + + // Software sync + digitalWrite(_dataPin, HIGH); + delayMicroseconds(4550); + digitalWrite(_dataPin, LOW); + delayMicroseconds(BASIC_TIME); + + // Send data bits one by one, starting with MSB + for (uint8_t i = 0; i < FRAME_SIZE; i++) { + for (int8_t j = 7; j >= 0; j--) { + if (bitRead(frame[i], j) == 1) { + SendBitOne(); + } else { + SendBitZero(); + } + } + } + + digitalWrite(_dataPin, LOW); + // Between frame silence + delayMicroseconds(30415); +} + +void SuplaSomfy::SetRemote(somfy_remote_t remote) { + _remote = remote; +} + +somfy_remote_t SuplaSomfy::GetRemote() { + return _remote; +} + +void SuplaSomfy::PushButton(ControlButtons pushButton) { + somfy_frame_t *frame = (somfy_frame_t *)malloc(FRAME_SIZE); + + frame[0] = 0xA7; // Encryption key + frame[1] = pushButton << 4; // MSB - Button pressed, LSB - Checksum + frame[2] = _remote.rollingCode.svalue.byte1; // Rolling code (big endian) + frame[3] = _remote.rollingCode.svalue.byte2; // Rolling code + frame[4] = _remote.remoteControl.svalue.byte1; // Remote address + frame[5] = _remote.remoteControl.svalue.byte2; // Remote address + frame[6] = _remote.remoteControl.svalue.byte3; // Remote address + +#if defined DEBUG_SOMFY + Serial.print(F("Frame : ")); + PrintHex8(frame, FRAME_SIZE); +#endif + + // Calculate checksum frame + frame[1] |= Checksum(frame); + +#if defined DEBUG_SOMFY + Serial.println(""); + Serial.print(F("With checksum : ")); + PrintHex8(frame, FRAME_SIZE); +#endif + + // Obfuscation frame + Obfuscation(frame); + +#if defined DEBUG_SOMFY + Serial.println(""); + Serial.print(F("Obfuscated : ")); + PrintHex8(frame, FRAME_SIZE); +#endif + +#if defined DEBUG_SOMFY + Serial.println(""); + Serial.print(F("Rolling Code : ")); + Serial.println(_remote.rollingCode.ivalue); + Serial.println(""); +#endif + + SendCommand(frame, 2); + for (uint8_t i = 0; i < 2; i++) { + SendCommand(frame, 7); + } + + free(frame); + _remote.rollingCode.ivalue++; +} + +#if defined DEBUG_SOMFY +void SuplaSomfy::PrintHex8(uint8_t *data, uint8_t length) { + Serial.print("0x"); + for (int i = 0; i < length; i++) { + if (data[i] < 0x10) { + Serial.print("0"); + } + Serial.print(data[i], HEX); + Serial.print(" "); + } +} +#endif diff --git a/lib/SuplaDevice/src/SuplaSomfy.h b/lib/SuplaDevice/src/SuplaSomfy.h new file mode 100644 index 00000000..c47d9004 --- /dev/null +++ b/lib/SuplaDevice/src/SuplaSomfy.h @@ -0,0 +1,93 @@ +/* + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Library for Somfy RTS remote controller + Author: Maciej Królewski + Using documantation: https://pushstack.wordpress.com/somfy-rts-protocol/ +*/ + +#ifndef SuplaSomfy_h +#define SuplaSomfy_h + +#include "Arduino.h" + +#define DEBUG_SOMFY + +// ----- DO NOT CHANGE ----- +#define FRAME_SIZE 7 // Somfy RTS frame size +#define BASIC_TIME 604 // Basic time [us] +// ------------------------- + +typedef uint8_t somfy_frame_t; +typedef union { + struct { + uint8_t byte1; + uint8_t byte2; + } svalue; + uint8_t tvalue[2]; + uint16_t ivalue; +} somfy_rollingcode_t; +typedef union { + struct { + uint8_t byte1; + uint8_t byte2; + uint8_t byte3; + } svalue; + uint8_t tvalue[3]; + uint32_t ivalue : 24; +} somfy_remotesn_t; + +enum /*class*/ ControlButtons { + STOP = 0x1, //[My] Stop or move to favourite position + UP = 0x2, //[Up] Move Up + MYUP = 0x3, //[My + Up] Set upper motor limit in initial programming mode + DOWN = 0x4, //[Down] Move Down + MYDOWN = + 0x5, //[My + Down] Set lower motor limit in initial programming mode + UPDOWN = 0x6, //[Up + Down] Change motor limit and initial programming mode + PROG = 0x8, //[Prog] Registering / Deregistering remotes + SUN = 0x9, //[Sun + Flag] Enable sun and wind detector (SUN and FLAG TIME on + //the Telis Soliris RC) + FLAG = 0xA //[Flag] Disable sun detector (FLAG TIME on the Telis + //Soliris RC) +}; + +struct somfy_remote_t { + somfy_rollingcode_t rollingCode; + somfy_remotesn_t remoteControl; +}; + +class SuplaSomfy { + private: + uint8_t _dataPin; + somfy_remote_t _remote; + + void SendBitZero(void); + void SendBitOne(void); + uint8_t Checksum(somfy_frame_t *frame); + void Obfuscation(somfy_frame_t *frame); + void SendCommand(somfy_frame_t *frame, uint8_t sync); + + public: + SuplaSomfy(uint8_t dataPin); + ~SuplaSomfy(void); + void SetRemote(somfy_remote_t remote); + somfy_remote_t GetRemote(void); + void PushButton(ControlButtons pushButton); + +#if defined DEBUG_SOMFY + void PrintHex8(uint8_t *data, uint8_t length); +#endif +}; + +#endif diff --git a/lib/SuplaDevice/src/crc16.h b/lib/SuplaDevice/src/crc16.h new file mode 100644 index 00000000..5713f7bd --- /dev/null +++ b/lib/SuplaDevice/src/crc16.h @@ -0,0 +1,14 @@ + +uint16_t crc16_update(uint16_t crc, uint8_t a) { + int i; + + crc ^= a; + for (i = 0; i < 8; ++i) { + if (crc & 1) + crc = (crc >> 1) ^ 0xA001; + else + crc = (crc >> 1); + } + + return crc; +} diff --git a/lib/SuplaDevice/src/supla-common/IEEE754tools.h b/lib/SuplaDevice/src/supla-common/IEEE754tools.h new file mode 100644 index 00000000..ef72ed96 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/IEEE754tools.h @@ -0,0 +1,69 @@ +// +// FILE: IEEE754tools.h +// AUTHOR: Rob Tillaart +// VERSION: 0.1.00 +// PURPOSE: IEEE754 tools +// +// http://playground.arduino.cc//Main/IEEE754tools +// +// Released to the public domain +// not tested, use with care +// + +#ifndef IEEE754tools_h +#define IEEE754tools_h + + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// IEEE754 float layout; +struct IEEEfloat +{ + uint32_t m:23; + uint8_t e:8; + uint8_t s:1; +}; + +// for packing and unpacking a float +typedef union _FLOATCONV +{ + IEEEfloat p; + float f; + uint8_t b[4]; +} _FLOATCONV; + +// Arduino UNO double layout: +// the UNO has no 64 bit double, it is only able to map 23 bits of the mantisse +// a filler is added. +struct _DBL +{ + uint32_t filler:29; + uint32_t m:23; + uint16_t e:11; + uint8_t s:1; +}; + + +// for packing and unpacking a double +typedef union _DBLCONV +{ + // IEEEdouble p; + _DBL p; + double d; // !! is a 32bit float for UNO. + uint8_t b[4]; +} _DBLCONV; + +// +// converts a float to a packed array of 8 bytes representing a 64 bit double +// restriction exponent and mantisse. +// +// float; array of 8 bytes; LSBFIRST; MSBFIRST +// +//void float2DoublePacked(float number, byte* bar, int byteOrder=LSBFIRST); + + +#endif diff --git a/lib/SuplaDevice/src/supla-common/eh.h b/lib/SuplaDevice/src/supla-common/eh.h new file mode 100644 index 00000000..dd5e14b6 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/eh.h @@ -0,0 +1,68 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef EH_H_ +#define EH_H_ + +#if !defined(ESP8266) && !defined(__AVR__) && !defined(_WIN32) && \ + !defined(ESP32) +#include +#endif + +#if !defined(__AVR__) && !defined(_WIN32) +#include +#endif + +#ifdef __AVR__ +#include "proto.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int nfds; + +#ifndef _WIN32 + +#ifdef __linux__ + int epoll_fd; + int fd1; +#else + int fd1[2]; +#endif + + int fd2; + int fd3; + + struct timeval tv; + +#endif +} TEventHandler; + +TEventHandler *eh_init(void); +void eh_add_fd(TEventHandler *eh, int fd); +void eh_raise_event(TEventHandler *eh); +int eh_wait(TEventHandler *eh, int usec); +void eh_free(TEventHandler *eh); + +#ifdef __cplusplus +} +#endif + +#endif /* EH_H_ */ diff --git a/lib/SuplaDevice/src/supla-common/lck.c b/lib/SuplaDevice/src/supla-common/lck.c new file mode 100644 index 00000000..18cbeb69 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/lck.c @@ -0,0 +1,198 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "lck.h" + +#ifdef __LCK_DEBUG +#include +#include +#endif /*__LCK_DEBUG*/ + +#if defined(__AVR__) || defined(ARDUINO_ARCH_ESP8266) || \ + defined(ARDUINO_ARCH_ESP32) +#define __SINGLE_THREAD +#else + +#ifdef _WIN32 +#include +#else +#include +#include +#endif /*_WIN32*/ + +#endif // defined(__AVR__) || defined(ARDUINO_ARCH_ESP8266) + // || defined(ARDUINO_ARCH_ESP32) + +#include + +#define MUTEX_COUNT 4 + +#ifndef __SINGLE_THREAD + +typedef struct { +#ifdef _WIN32 + CRITICAL_SECTION critSec; +#else + pthread_mutex_t mutex; +#ifdef __LCK_DEBUG + + pthread_t thread; + int count; + int lineNumber; + char fileName[100]; + +#endif /*__LCK_DEBUG*/ +#endif /*_WIN32*/ +} TLckData; +#endif + +#ifdef __LCK_DEBUG +void *ptrs[500]; + +void lck_debug_init(void) { memset(ptrs, 0, sizeof(ptrs)); } + +void lck_debug_dump(void) { + printf("LCK DEBUG DUMP\n"); + int a; + int n = sizeof(ptrs) / sizeof(void *); + TLckData *l = 0; + + for (a = 0; a < n; a++) { + if ((l = (TLckData *)ptrs[a]) != 0 && l->count != 0) { + printf("%p:%p %s:%i count=%i\n", (void *)l, (void *)l->thread, + l->fileName, l->lineNumber, l->count); + } + } + + printf("<<-----\n"); +} + +#endif /*__LCK_DEBUG*/ + +void *lck_init(void) { +#ifdef __SINGLE_THREAD + return NULL; +#else + TLckData *lck = malloc(sizeof(TLckData)); + + if (lck != NULL) { +#ifdef _WIN32 + InitializeCriticalSectionEx(&lck->critSec, 4000, + CRITICAL_SECTION_NO_DEBUG_INFO); +#else + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&lck->mutex, &attr); + +#endif /*_WIN32*/ + } + +#ifdef __LCK_DEBUG + memset(lck, 0, sizeof(TLckData)); + int a; + int n = sizeof(ptrs) / sizeof(void *); + for (a = 0; a < n; a++) { + if (ptrs[a] == 0) { + ptrs[a] = lck; + break; + } + } +#endif /*__LCK_DEBUG*/ + + return lck; +#endif /*__SINGLE_THREAD*/ +} + +#ifdef __LCK_DEBUG + +void __lck_lock(void *lck, const char *file, int line) { + _lck_lock(lck); + + ((TLckData *)lck)->thread = pthread_self(); + ((TLckData *)lck)->count++; + if (((TLckData *)lck)->count == 1) { + snprintf(((TLckData *)lck)->fileName, sizeof(((TLckData *)lck)->fileName), + "%s", file); + ((TLckData *)lck)->lineNumber = line; + } +} + +void _lck_lock(void *lck) { +#else +void lck_lock(void *lck) { +#endif /*__LCK_DEBUG*/ +#ifndef __SINGLE_THREAD + if (lck != NULL) { +#ifdef _WIN32 + EnterCriticalSection(&((TLckData *)lck)->critSec); // NOLINT +#else + pthread_mutex_lock(&((TLckData *)lck)->mutex); // NOLINT +#endif /*_WIN32*/ + } + +#endif /*__SINGLE_THREAD*/ +} + +void lck_unlock(void *lck) { +#ifdef __LCK_DEBUG + ((TLckData *)lck)->count--; +#endif /*__LCK_DEBUG*/ +#ifndef __SINGLE_THREAD + if (lck != NULL) { +#ifdef _WIN32 + LeaveCriticalSection(&((TLckData *)lck)->critSec); // NOLINT +#else + pthread_mutex_unlock(&((TLckData *)lck)->mutex); // NOLINT +#endif /*_WIN32*/ + } + +#endif /*__SINGLE_THREAD*/ +} + +int lck_unlock_r(void *lck, int result) { +#ifndef __SINGLE_THREAD + lck_unlock(lck); +#endif /*__SINGLE_THREAD*/ + return result; +} + +void lck_free(void *lck) { +#ifdef __LCK_DEBUG + int a; + int n = sizeof(ptrs) / sizeof(void *); + for (a = 0; a < n; a++) { + if (ptrs[a] == lck) { + ptrs[a] = 0; + break; + } + } +#endif /*__LCK_DEBUG*/ + +#ifndef __SINGLE_THREAD + if (lck != NULL) { +#ifdef _WIN32 + DeleteCriticalSection(&((TLckData *)lck)->critSec); // NOLINT +#else + pthread_mutex_destroy(&((TLckData *)lck)->mutex); // NOLINT +#endif /*_WIN32*/ + free(lck); + } +#endif /*__SINGLE_THREAD*/ +} diff --git a/lib/SuplaDevice/src/supla-common/lck.h b/lib/SuplaDevice/src/supla-common/lck.h new file mode 100644 index 00000000..ce655366 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/lck.h @@ -0,0 +1,50 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LCK_H_ +#define LCK_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __DEBUG +// #define __LCK_DEBUG +#endif + +#ifdef __LCK_DEBUG +#define lck_lock(ptr) __lck_lock(ptr, __FILE__, __LINE__) +void _lck_lock(void *lck); +void __lck_lock(void *lck, const char *file, int line); +void lck_debug_init(void); +void lck_debug_dump(void); +#else +void lck_lock(void *lck); +#endif /*__LCK_DEBUG*/ + +char lck_lock_with_timeout(void *lck, int timeout_sec); +void lck_unlock(void *lck); +int lck_unlock_r(void *lck, int result); +void *lck_init(void); +void lck_free(void *lck); + +#ifdef __cplusplus +} +#endif + +#endif /* LCK_H_ */ diff --git a/lib/SuplaDevice/src/supla-common/log.cpp b/lib/SuplaDevice/src/supla-common/log.cpp new file mode 100644 index 00000000..4dc045b6 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/log.cpp @@ -0,0 +1,293 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "log.h" +#include +#include +#include + +#if defined(_WIN32) +#include +#include +#elif defined(ARDUINO_ARCH_ESP8266) || defined(__AVR__) || defined(ARDUINO_ARCH_ESP32) +#include +#else +#include +#include +#include +#include +#include +#endif /*defined(_WIN32)*/ + +#include + +#if defined(ESP8266) || defined(ESP32) + +#include +#if !defined(ARDUINO_ARCH_ESP32) +#include +#endif + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#include +#if defined(ARDUINO_ARCH_ESP266) +#include +#endif +#else +#include +#include "espmissingincludes.h" +#endif /*ARDUINO_ARCH_ESP8266 || ARDUINO_ARCH_ESP8266 */ + +#else + +#ifndef __AVR__ +#include "cfg.h" +#endif /*__AVR__*/ + +#endif /*ESP8266 || ESP32 */ + +#ifdef __ANDROID__ +#include +#endif /*__ANDROID__*/ + +#ifdef __LOG_CALLBACK +_supla_log_callback __supla_log_callback = NULL; + +void supla_log_set_callback(_supla_log_callback callback) { + __supla_log_callback = callback; +} +#endif /*__LOG_CALLBACK*/ + +char supla_log_string(char **buffer, int *size, va_list va, const char *__fmt) { + char *nb; + int n; + + if (*size == 0) { + *size = strnlen(__fmt, 10240) + 10; + *buffer = (char *)malloc(*size); + } + + if (*buffer == NULL) { + *size = 0; + return 0; + } + +#ifdef _WIN32 + n = vsnprintf_s(*buffer, *size, _TRUNCATE, __fmt, va); +#else + n = vsnprintf(*buffer, *size, __fmt, va); +#endif /*_WIN32*/ + + if (n < 0 || n >= *size) { + if (n > -1) /* glibc 2.1 */ + *size = n + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + (*size) *= 2; /* twice the old size */ + + if ((nb = (char *)realloc(*buffer, *size)) == NULL) { + free(*buffer); + *buffer = NULL; + *size = 0; + } else { + *buffer = nb; + return 1; + } + } + + if (*buffer != NULL) { + (*buffer)[(*size) - 1] = 0; + } + + return 0; +} + +#ifdef _WIN32 + +void supla_vlog(int __pri, const char *message) { + WCHAR wstr[2048]; + size_t convertedChars; + + mbstowcs_s(&convertedChars, wstr, 2048, message, strnlen(message, 10240)); + + OutputDebugStringW(wstr); + OutputDebugStringW(L"\n"); +} + +#elif defined(ESP8266) || defined(__AVR__) || defined(ESP32) +void supla_vlog(int __pri, const char *message) { +#if (defined(ESP8266) || defined(ESP32)) && !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32) +#ifndef ESP8266_LOG_DISABLED + os_printf("%s\r\n", message); +#endif +#else + Serial.println(message); +#endif +} +#else +void supla_vlog(int __pri, const char *message) { +#ifdef __ANDROID__ + switch (__pri) { + case LOG_CRIT: + case LOG_EMERG: + __pri = ANDROID_LOG_FATAL; + break; + case LOG_ERR: + __pri = ANDROID_LOG_ERROR; + break; + case LOG_ALERT: + case LOG_WARNING: + __pri = ANDROID_LOG_WARN; + break; + case LOG_NOTICE: + __pri = ANDROID_LOG_DEFAULT; + break; + case LOG_INFO: + __pri = ANDROID_LOG_INFO; + break; + case LOG_DEBUG: + __pri = ANDROID_LOG_DEBUG; + break; + } + __android_log_write(ANDROID_LOG_DEBUG, "LibSuplaClient", message); +#else + +#ifndef __LOG_CALLBACK + struct timeval now; +#endif + +#if defined(ESP8266) || defined(__AVR__) || defined(ESP32) + if (message == NULL) return; +#else + if (message == NULL || (debug_mode == 0 && __pri == LOG_DEBUG)) return; +#endif + +#ifdef __LOG_CALLBACK + if (__supla_log_callback) __supla_log_callback(__pri, message); +#else + if (run_as_daemon == 1) { + syslog(__pri, "%s", message); + } else { + switch (__pri) { + case LOG_EMERG: + printf("EMERG"); + break; + case LOG_ALERT: + printf("ALERT"); + break; + case LOG_CRIT: + printf("CRIT"); + break; + case LOG_ERR: + printf("ERR"); + break; + case LOG_WARNING: + printf("WARNING"); + break; + case LOG_NOTICE: + printf("NOTICE"); + break; + case LOG_INFO: + printf("INFO"); + break; + case LOG_DEBUG: + printf("DEBUG"); + break; + } + +#if defined(ESP8266) || defined(__AVR__) || defined(ESP32) + os_printf("%s\r\n", message); +#else + gettimeofday(&now, NULL); + printf("[%li.%li] ", (unsigned long)now.tv_sec, (unsigned long)now.tv_usec); + printf("%s", message); + printf("\n"); + fflush(stdout); +#endif + } +#endif + +#endif +} +#endif + +void supla_log(int __pri, const char *__fmt, ...) { + va_list ap; + char *buffer = NULL; + int size = 0; + +#if defined(ESP8266) || defined(__AVR__) || defined(_WIN32) || defined(ESP32) + if (__fmt == NULL) return; +#else + if (__fmt == NULL || (debug_mode == 0 && __pri == LOG_DEBUG)) return; +#endif + + while (1) { + va_start(ap, __fmt); + if (0 == supla_log_string(&buffer, &size, ap, __fmt)) { + va_end(ap); + break; + } else { + va_end(ap); + } + va_end(ap); + } + + if (buffer == NULL) return; + + supla_vlog(__pri, buffer); + free(buffer); +} + +void supla_write_state_file(const char *file, int __pri, const char *__fmt, + ...) { + char *buffer = NULL; + int size = 0; + + va_list ap; + + while (1) { + va_start(ap, __fmt); + if (0 == supla_log_string(&buffer, &size, ap, __fmt)) { + va_end(ap); + break; + } else { + va_end(ap); + } + } + + if (buffer == NULL) return; + + if (__pri > -1) { + supla_vlog(__pri, buffer); + } + +#if !defined(ESP8266) && !defined(__AVR__) && !defined(WIN32) && !defined(ESP32) + + int fd; + + if (file != 0 && strnlen(file, 1024) > 0) { + fd = open(file, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR); + if (fd != -1) { + write(fd, buffer, strnlen(buffer, size)); + close(fd); + } + } +#endif + + free(buffer); +} diff --git a/lib/SuplaDevice/src/supla-common/log.h b/lib/SuplaDevice/src/supla-common/log.h new file mode 100644 index 00000000..dc28158a --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/log.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef suplalog_H_ +#define suplalog_H_ + +#if defined(ESP8266) || defined(__AVR__) || defined(_WIN32) || defined(ESP32) + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#else + +#include + +#endif // defined(ESP8266) || defined(__AVR__) + // || defined(_WIN32) || defined(ESP32) + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __LOG_CALLBACK +typedef int (*_supla_log_callback)(int __pri, const char *message); + +void supla_log_set_callback(_supla_log_callback callback); +#endif /*__LOG_CALLBACK*/ + +void supla_log(int __pri, const char *__fmt, ...); +void supla_write_state_file(const char *file, int __pri, const char *__fmt, + ...); + +#ifdef __cplusplus +} +#endif /*__cplusplus*/ + +#endif /* suplalog_H_ */ diff --git a/lib/SuplaDevice/src/supla-common/proto.c b/lib/SuplaDevice/src/supla-common/proto.c new file mode 100644 index 00000000..26d19a5c --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/proto.c @@ -0,0 +1,409 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "proto.h" +#include +#include +#include +#include "log.h" + +#if defined(ESP8266) || defined(ESP32) + +#include +#if !defined(ARDUINO_ARCH_ESP32) +#include +#endif +#define BUFFER_MIN_SIZE 512 +#define BUFFER_MAX_SIZE 2048 + +#if !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32) +#include +#include "espmissingincludes.h" +#endif + +#elif defined(__AVR__) + +#define BUFFER_MIN_SIZE 32 +#define BUFFER_MAX_SIZE 1024 + +#endif /*ESP8266*/ + +#ifndef BUFFER_MIN_SIZE +#define BUFFER_MIN_SIZE 0 +#endif /*BUFFER_MIN_SIZE*/ + +#ifndef BUFFER_MAX_SIZE +#define BUFFER_MAX_SIZE 131072 +#endif /*BUFFER_MAX_SIZE*/ + +char sproto_tag[SUPLA_TAG_SIZE] = {'S', 'U', 'P', 'L', 'A'}; + +typedef struct { + unsigned char begin_tag; + unsigned _supla_int_t size; + unsigned _supla_int_t data_size; + + char *buffer; +} TSuplaProtoInBuffer; + +#ifndef SPROTO_WITHOUT_OUT_BUFFER +typedef struct { + unsigned _supla_int_t size; + unsigned _supla_int_t data_size; + + char *buffer; +} TSuplaProtoOutBuffer; +#endif + +typedef struct { + unsigned _supla_int_t next_rr_id; + unsigned char version; + TSuplaProtoInBuffer in; +#ifndef SPROTO_WITHOUT_OUT_BUFFER + TSuplaProtoOutBuffer out; +#endif +} TSuplaProtoData; + +void *sproto_init(void) { + TSuplaProtoData *spd = malloc(sizeof(TSuplaProtoData)); + if (spd) { + memset(spd, 0, sizeof(TSuplaProtoData)); + spd->version = SUPLA_PROTO_VERSION; + return (spd); + } + + return (NULL); +} + +void sproto_free(void *spd_ptr) { + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + if (spd != NULL) { + if (spd->in.buffer != NULL) free(spd->in.buffer); +#ifndef SPROTO_WITHOUT_OUT_BUFFER + if (spd->out.buffer != NULL) free(spd->out.buffer); +#endif + + free(spd); + } +} + +unsigned char sproto_buffer_append(void *spd_ptr, char **buffer, + unsigned _supla_int_t *buffer_size, + unsigned _supla_int_t *buffer_data_size, + char *data, + unsigned _supla_int_t data_size) { + unsigned _supla_int_t size = *buffer_size; + + if (size < BUFFER_MIN_SIZE) { + size = BUFFER_MIN_SIZE; + } + + if (data_size > size - (*buffer_data_size)) { + size += data_size - (size - (*buffer_data_size)); + } + + if (size >= BUFFER_MAX_SIZE) return (SUPLA_RESULT_BUFFER_OVERFLOW); + + if (size != (*buffer_size)) { + char *new_buffer = (char *)realloc(*buffer, size); + + if (size > 0 && new_buffer == NULL) { + return (SUPLA_RESULT_FALSE); + } + +#ifndef ESP8266 +#ifndef ESP32 +#ifndef __AVR__ + if (errno == ENOMEM) return (SUPLA_RESULT_FALSE); +#endif +#endif +#endif + + *buffer = new_buffer; + } + + memcpy(&(*buffer)[(*buffer_data_size)], data, data_size); + + (*buffer_size) = size; + (*buffer_data_size) += data_size; + + return (SUPLA_RESULT_TRUE); +} + +char sproto_in_buffer_append(void *spd_ptr, char *data, + unsigned _supla_int_t data_size) { + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + return sproto_buffer_append(spd_ptr, &spd->in.buffer, &spd->in.size, + &spd->in.data_size, data, data_size); +} + +#ifndef SPROTO_WITHOUT_OUT_BUFFER +char sproto_out_buffer_append(void *spd_ptr, TSuplaDataPacket *sdp) { + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + unsigned _supla_int_t sdp_size = sizeof(TSuplaDataPacket); + unsigned _supla_int_t packet_size = + sdp_size - SUPLA_MAX_DATA_SIZE + sdp->data_size; + + if (packet_size > sdp_size) return SUPLA_RESULT_DATA_TOO_LARGE; + + if (SUPLA_RESULT_TRUE == + sproto_buffer_append(spd_ptr, &spd->out.buffer, &spd->out.size, + &spd->out.data_size, (char *)sdp, packet_size)) { + return sproto_buffer_append(spd_ptr, &spd->out.buffer, &spd->out.size, + &spd->out.data_size, sproto_tag, + SUPLA_TAG_SIZE); + } + + return (SUPLA_RESULT_FALSE); +} + +unsigned _supla_int_t sproto_pop_out_data(void *spd_ptr, char *buffer, + unsigned _supla_int_t buffer_size) { + unsigned _supla_int_t a; + unsigned _supla_int_t b; + + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + + if (spd->out.data_size <= 0 || buffer_size == 0 || buffer == NULL) return (0); + + if (spd->out.data_size < buffer_size) buffer_size = spd->out.data_size; + + memcpy(buffer, spd->out.buffer, buffer_size); + + b = 0; + + for (a = buffer_size; a < spd->out.data_size; a++) { + spd->out.buffer[b] = spd->out.buffer[a]; + b++; + } + + spd->out.data_size -= buffer_size; + + if (spd->out.data_size < spd->out.size) { + b = spd->out.size; + + spd->out.size = spd->out.data_size; + if (spd->out.size < BUFFER_MIN_SIZE) spd->out.size = BUFFER_MIN_SIZE; + + if (b != spd->out.size) { + char *new_out_buffer = (char *)realloc(spd->out.buffer, spd->out.size); + + if (new_out_buffer == NULL && spd->out.size > 0) { + spd->out.size = b; + } else { + spd->out.buffer = new_out_buffer; + } + } + } + + return (buffer_size); +} +#endif /*SPROTO_WITHOUT_OUT_BUFFER*/ + +char sproto_out_dataexists(void *spd_ptr) { +#ifdef SPROTO_WITHOUT_OUT_BUFFER + return SUPLA_RESULT_FALSE; +#else + return ((TSuplaProtoData *)spd_ptr)->out.data_size > 0 ? SUPLA_RESULT_TRUE + : SUPLA_RESULT_FALSE; +#endif +} + +char sproto_in_dataexists(void *spd_ptr) { + return ((TSuplaProtoData *)spd_ptr)->in.data_size > 0 ? SUPLA_RESULT_TRUE + : SUPLA_RESULT_FALSE; +} + +void sproto_shrink_in_buffer(TSuplaProtoInBuffer *in, + unsigned _supla_int_t size) { + unsigned _supla_int_t old_size = in->size; + _supla_int_t a, b; + + in->begin_tag = 0; + + if (size > in->data_size) size = in->data_size; + + b = 0; + + for (a = size; a < in->data_size; a++) { + in->buffer[b] = in->buffer[a]; + b++; + } + + in->data_size -= size; + + if (in->data_size < in->size) { + in->size = in->data_size; + + if (in->size < BUFFER_MIN_SIZE) in->size = BUFFER_MIN_SIZE; + + if (old_size != in->size) { + char *new_in_buffer = (char *)realloc(in->buffer, in->size); + + if (new_in_buffer == NULL && in->size > 0) { + in->size = old_size; + } else { + in->buffer = new_in_buffer; + } + } + } +} + +char sproto_pop_in_sdp(void *spd_ptr, TSuplaDataPacket *sdp) { + unsigned _supla_int_t header_size; + TSuplaDataPacket *_sdp; + + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + + if (spd->in.begin_tag == 0 && spd->in.data_size >= SUPLA_TAG_SIZE) { + if (memcmp(spd->in.buffer, sproto_tag, SUPLA_TAG_SIZE) == 0) { + spd->in.begin_tag = 1; + } else { + sproto_shrink_in_buffer(&spd->in, spd->in.data_size); + return SUPLA_RESULT_DATA_ERROR; + } + } + + if (spd->in.begin_tag == 1) { + header_size = sizeof(TSuplaDataPacket) - SUPLA_MAX_DATA_SIZE; + if ((spd->in.data_size - SUPLA_TAG_SIZE) >= header_size) { + _sdp = (TSuplaDataPacket *)spd->in.buffer; + + if (_sdp->version > SUPLA_PROTO_VERSION || + _sdp->version < SUPLA_PROTO_VERSION_MIN) { + sdp->version = _sdp->version; + sproto_shrink_in_buffer(&spd->in, spd->in.data_size); + + return SUPLA_RESULT_VERSION_ERROR; + } + + if ((header_size + _sdp->data_size) > sizeof(TSuplaDataPacket)) { + sproto_shrink_in_buffer(&spd->in, spd->in.data_size); + return SUPLA_RESULT_DATA_ERROR; + } + + if ((header_size + _sdp->data_size + SUPLA_TAG_SIZE) > spd->in.data_size) + return SUPLA_RESULT_FALSE; + + if (header_size + _sdp->data_size >= spd->in.size || + memcmp(&spd->in.buffer[header_size + _sdp->data_size], sproto_tag, + SUPLA_TAG_SIZE) != 0) { + sproto_shrink_in_buffer(&spd->in, spd->in.data_size); + + return SUPLA_RESULT_DATA_ERROR; + } + + memcpy(sdp, spd->in.buffer, header_size + _sdp->data_size); + sproto_shrink_in_buffer(&spd->in, + header_size + _sdp->data_size + SUPLA_TAG_SIZE); + + return (SUPLA_RESULT_TRUE); + } + } + + return (SUPLA_RESULT_FALSE); +} + +void sproto_set_version(void *spd_ptr, unsigned char version) { + if (version >= SUPLA_PROTO_VERSION_MIN && version <= SUPLA_PROTO_VERSION) { + ((TSuplaProtoData *)spd_ptr)->version = version; + } else { + ((TSuplaProtoData *)spd_ptr)->version = SUPLA_PROTO_VERSION; + } +} + +unsigned char sproto_get_version(void *spd_ptr) { + return ((TSuplaProtoData *)spd_ptr)->version; +} + +void sproto_sdp_init(void *spd_ptr, TSuplaDataPacket *sdp) { + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + + memset(sdp, 0, sizeof(TSuplaDataPacket)); + memcpy(sdp->tag, sproto_tag, SUPLA_TAG_SIZE); + + spd->next_rr_id++; + + if (spd->next_rr_id == 0) spd->next_rr_id++; + + sdp->rr_id = spd->next_rr_id; + sdp->version = spd->version; +} + +TSuplaDataPacket *sproto_sdp_malloc(void *spd_ptr) { + TSuplaDataPacket *result = malloc(sizeof(TSuplaDataPacket)); + + if (result) sproto_sdp_init(spd_ptr, result); + + return result; +} + +void sproto_sdp_free(TSuplaDataPacket *sdp) { free(sdp); } + +char sproto_set_data(TSuplaDataPacket *sdp, char *data, + unsigned _supla_int_t data_size, + unsigned _supla_int_t call_type) { + if (data_size > SUPLA_MAX_DATA_SIZE || (data_size > 0 && data == 0)) + return SUPLA_RESULT_FALSE; + + if (data_size > 0) memcpy(sdp->data, data, data_size); + + sdp->data_size = data_size; + sdp->call_type = call_type; + return SUPLA_RESULT_TRUE; +} + +void sproto_log_summary(void *spd_ptr) { + if (spd_ptr == NULL) { + supla_log(LOG_DEBUG, "SPROTO - Not initialized!"); + return; + } + + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + + supla_log(LOG_DEBUG, "BUFFER IN"); + supla_log(LOG_DEBUG, " size: %i", spd->in.size); + supla_log(LOG_DEBUG, " data_size: %i", spd->in.data_size); + supla_log(LOG_DEBUG, " begin_tag: %i", spd->in.begin_tag); +#ifndef SPROTO_WITHOUT_OUT_BUFFER + supla_log(LOG_DEBUG, "BUFFER OUT"); + supla_log(LOG_DEBUG, " size: %i", spd->out.size); + supla_log(LOG_DEBUG, " data_size: %i", spd->out.data_size); +#endif /*SPROTO_WITHOUT_OUT_BUFFER*/ +} + +void sproto_buffer_dump(void *spd_ptr, unsigned char in) { + _supla_int_t a; + char *buffer = NULL; + _supla_int_t size = 0; + + TSuplaProtoData *spd = (TSuplaProtoData *)spd_ptr; + + if (in != 0) { + buffer = spd->in.buffer; + size = spd->in.data_size; +#ifndef SPROTO_WITHOUT_OUT_BUFFER + } else { + buffer = spd->out.buffer; + size = spd->out.data_size; +#endif /*SPROTO_WITHOUT_OUT_BUFFER*/ + } + + for (a = 0; a < size; a++) + supla_log(LOG_DEBUG, "%c [%i]", buffer[a], buffer[a]); +} diff --git a/lib/SuplaDevice/src/supla-common/proto.h b/lib/SuplaDevice/src/supla-common/proto.h new file mode 100644 index 00000000..b8c56a02 --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/proto.h @@ -0,0 +1,1666 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef supla_proto_H_ +#define supla_proto_H_ + +#ifdef _WIN32 + +#include +#define _supla_int_t int +#define _supla_int16_t short +#define _supla_int64_t __int64 +#define _supla_timeval timeval + +#elif defined(__AVR__) + +#define SPROTO_WITHOUT_OUT_BUFFER + +struct _supla_timeval { + long tv_sec[2]; + long tv_usec[2]; +}; + +#define timeval _supla_timeval + +#define _supla_int16_t int +#define _supla_int_t long +#define _supla_int64_t long long + +#elif defined(ESP8266) || defined(ESP32) + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#define SPROTO_WITHOUT_OUT_BUFFER +#endif /*ARDUINO_ARCH_ESP8266*/ + +struct _supla_timeval { + long long tv_sec; + long long tv_usec; +}; + +#define _supla_int16_t short +#define _supla_int_t int +#define _supla_int64_t long long +#elif defined(__arm__) + +struct _supla_timeval { + long long tv_sec; + long long tv_usec; +}; + +#include +#define _supla_int16_t short +#define _supla_int_t int +#define _supla_int64_t long long + +#else /*__arm__*/ +#include +#define _supla_int16_t short +#define _supla_int_t int +#define _supla_int64_t long long +#define _supla_timeval timeval +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define SUPLA_TAG_SIZE 5 +extern char sproto_tag[SUPLA_TAG_SIZE]; + +// DCS - device/client -> server +// SDC - server -> device/client +// DS - device -> server +// SD - server -> device +// CS - client -> server +// SC - server -> client + +#define SUPLA_PROTO_VERSION 13 +#define SUPLA_PROTO_VERSION_MIN 1 +#if defined(ARDUINO_ARCH_AVR) // Arduino IDE for Arduino HW +#define SUPLA_MAX_DATA_SIZE 1248 // Registration header + 32 channels x 21 B +#elif defined(ARDUINO_ARCH_ESP8266) || \ + defined(ARDUINO_ARCH_ESP32) // Arduino IDE for ESP8266 +#define SUPLA_MAX_DATA_SIZE 3264 // Registration header + 128 channels x 21 B +#elif defined(ESP8266) +#define SUPLA_MAX_DATA_SIZE 1536 +#else +#define SUPLA_MAX_DATA_SIZE 10240 +#endif +#define SUPLA_RC_MAX_DEV_COUNT 50 +#define SUPLA_SOFTVER_MAXSIZE 21 + +#define SUPLA_GUID_SIZE 16 +#define SUPLA_GUID_HEXSIZE 33 +#define SUPLA_LOCATION_PWD_MAXSIZE 33 +#define SUPLA_ACCESSID_PWD_MAXSIZE 33 +#define SUPLA_LOCATION_CAPTION_MAXSIZE 401 +#define SUPLA_LOCATIONPACK_MAXCOUNT 20 +#define SUPLA_CHANNEL_CAPTION_MAXSIZE 401 +#define SUPLA_CHANNELPACK_MAXCOUNT 20 +#define SUPLA_URL_HOST_MAXSIZE 101 +#define SUPLA_URL_PATH_MAXSIZE 101 +#define SUPLA_SERVER_NAME_MAXSIZE 65 +#define SUPLA_EMAIL_MAXSIZE 256 // ver. >= 7 +#define SUPLA_PASSWORD_MAXSIZE 64 // ver. >= 10 +#define SUPLA_AUTHKEY_SIZE 16 // ver. >= 7 +#define SUPLA_AUTHKEY_HEXSIZE 33 // ver. >= 7 +#define SUPLA_OAUTH_TOKEN_MAXSIZE 256 // ver. >= 10 +#define SUPLA_CHANNELGROUP_PACK_MAXCOUNT 20 // ver. >= 9 +#define SUPLA_CHANNELGROUP_CAPTION_MAXSIZE 401 // ver. >= 9 +#define SUPLA_CHANNELVALUE_PACK_MAXCOUNT 20 // ver. >= 9 +#define SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXCOUNT 5 // ver. >= 10 +#define SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXDATASIZE \ + (SUPLA_MAX_DATA_SIZE - 50) // ver. >= 10 +#define SUPLA_CALCFG_DATA_MAXSIZE 128 // ver. >= 10 +#define SUPLA_TIMEZONE_MAXSIZE 51 // ver. >= 11 + +#ifndef SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT +#define SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT 100 // ver. >= 9 +#endif /*SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT*/ + +#define SUPLA_DCS_CALL_GETVERSION 10 +#define SUPLA_SDC_CALL_GETVERSION_RESULT 20 +#define SUPLA_SDC_CALL_VERSIONERROR 30 +#define SUPLA_DCS_CALL_PING_SERVER 40 +#define SUPLA_SDC_CALL_PING_SERVER_RESULT 50 +#define SUPLA_DS_CALL_REGISTER_DEVICE 60 +#define SUPLA_DS_CALL_REGISTER_DEVICE_B 65 // ver. >= 2 +#define SUPLA_DS_CALL_REGISTER_DEVICE_C 67 // ver. >= 6 +#define SUPLA_DS_CALL_REGISTER_DEVICE_D 68 // ver. >= 7 +#define SUPLA_DS_CALL_REGISTER_DEVICE_E 69 // ver. >= 10 +#define SUPLA_SD_CALL_REGISTER_DEVICE_RESULT 70 +#define SUPLA_CS_CALL_REGISTER_CLIENT 80 +#define SUPLA_CS_CALL_REGISTER_CLIENT_B 85 // ver. >= 6 +#define SUPLA_CS_CALL_REGISTER_CLIENT_C 86 // ver. >= 7 +#define SUPLA_CS_CALL_REGISTER_CLIENT_D 87 // ver. >= 12 +#define SUPLA_SC_CALL_REGISTER_CLIENT_RESULT 90 +#define SUPLA_SC_CALL_REGISTER_CLIENT_RESULT_B 92 // ver. >= 9 +#define SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED 100 +#define SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_B 102 // ver. >= 12 +#define SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_C 103 // ver. >= 12 +#define SUPLA_DS_CALL_DEVICE_CHANNEL_EXTENDEDVALUE_CHANGED 105 // ver. >= 10 +#define SUPLA_SD_CALL_CHANNEL_SET_VALUE 110 +#define SUPLA_SD_CALL_CHANNELGROUP_SET_VALUE 115 // ver. >= 13 +#define SUPLA_DS_CALL_CHANNEL_SET_VALUE_RESULT 120 +#define SUPLA_SC_CALL_LOCATION_UPDATE 130 +#define SUPLA_SC_CALL_LOCATIONPACK_UPDATE 140 +#define SUPLA_SC_CALL_CHANNEL_UPDATE 150 +#define SUPLA_SC_CALL_CHANNELPACK_UPDATE 160 +#define SUPLA_SC_CALL_CHANNEL_VALUE_UPDATE 170 +#define SUPLA_CS_CALL_GET_NEXT 180 +#define SUPLA_SC_CALL_EVENT 190 +#define SUPLA_CS_CALL_CHANNEL_SET_VALUE 200 +#define SUPLA_CS_CALL_CHANNEL_SET_VALUE_B 205 // ver. >= 3 +#define SUPLA_DCS_CALL_SET_ACTIVITY_TIMEOUT 210 // ver. >= 2 +#define SUPLA_SDC_CALL_SET_ACTIVITY_TIMEOUT_RESULT 220 // ver. >= 2 +#define SUPLA_DS_CALL_GET_FIRMWARE_UPDATE_URL 300 // ver. >= 5 +#define SUPLA_SD_CALL_GET_FIRMWARE_UPDATE_URL_RESULT 310 // ver. >= 5 +#define SUPLA_DCS_CALL_GET_REGISTRATION_ENABLED 320 // ver. >= 7 +#define SUPLA_SDC_CALL_GET_REGISTRATION_ENABLED_RESULT 330 // ver. >= 7 +#define SUPLA_CS_CALL_OAUTH_TOKEN_REQUEST 340 // ver. >= 10 +#define SUPLA_SC_CALL_OAUTH_TOKEN_REQUEST_RESULT 350 // ver. >= 10 +#define SUPLA_SC_CALL_CHANNELPACK_UPDATE_B 360 // ver. >= 8 +#define SUPLA_SC_CALL_CHANNELPACK_UPDATE_C 361 // ver. >= 10 +#define SUPLA_SC_CALL_CHANNEL_UPDATE_B 370 // ver. >= 8 +#define SUPLA_SC_CALL_CHANNEL_UPDATE_C 371 // ver. >= 10 +#define SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE 380 // ver. >= 9 +#define SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE_B 381 // ver. >= 10 +#define SUPLA_SC_CALL_CHANNELGROUP_RELATION_PACK_UPDATE 390 // ver. >= 9 +#define SUPLA_SC_CALL_CHANNELVALUE_PACK_UPDATE 400 // ver. >= 9 +#define SUPLA_SC_CALL_CHANNELEXTENDEDVALUE_PACK_UPDATE 405 // ver. >= 10 +#define SUPLA_CS_CALL_SET_VALUE 410 // ver. >= 9 +#define SUPLA_CS_CALL_SUPERUSER_AUTHORIZATION_REQUEST 420 // ver. >= 10 +#define SUPLA_CS_CALL_GET_SUPERUSER_AUTHORIZATION_RESULT 425 // ver. >= 12 +#define SUPLA_SC_CALL_SUPERUSER_AUTHORIZATION_RESULT 430 // ver. >= 10 +#define SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST 440 // ver. >= 10 +#define SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST_B 445 // ver. >= 11 +#define SUPLA_SC_CALL_DEVICE_CALCFG_RESULT 450 // ver. >= 10 +#define SUPLA_SD_CALL_DEVICE_CALCFG_REQUEST 460 // ver. >= 10 +#define SUPLA_DS_CALL_DEVICE_CALCFG_RESULT 470 // ver. >= 10 +#define SUPLA_DCS_CALL_GET_USER_LOCALTIME 480 // ver. >= 11 +#define SUPLA_DCS_CALL_GET_USER_LOCALTIME_RESULT 490 // ver. >= 11 +#define SUPLA_CSD_CALL_GET_CHANNEL_STATE 500 // ver. >= 12 +#define SUPLA_DSC_CALL_CHANNEL_STATE_RESULT 510 // ver. >= 12 +#define SUPLA_CS_CALL_GET_CHANNEL_BASIC_CFG 520 // ver. >= 12 +#define SUPLA_SC_CALL_CHANNEL_BASIC_CFG_RESULT 530 // ver. >= 12 +#define SUPLA_CS_CALL_SET_CHANNEL_FUNCTION 540 // ver. >= 12 +#define SUPLA_SC_CALL_SET_CHANNEL_FUNCTION_RESULT 550 // ver. >= 12 +#define SUPLA_CS_CALL_CLIENTS_RECONNECT_REQUEST 560 // ver. >= 12 +#define SUPLA_SC_CALL_CLIENTS_RECONNECT_REQUEST_RESULT 570 // ver. >= 12 +#define SUPLA_CS_CALL_SET_REGISTRATION_ENABLED 580 // ver. >= 12 +#define SUPLA_SC_CALL_SET_REGISTRATION_ENABLED_RESULT 590 // ver. >= 12 +#define SUPLA_CS_CALL_DEVICE_RECONNECT_REQUEST 600 // ver. >= 12 +#define SUPLA_SC_CALL_DEVICE_RECONNECT_REQUEST_RESULT 610 // ver. >= 12 +#define SUPLA_DS_CALL_GET_CHANNEL_FUNCTIONS 620 // ver. >= 12 +#define SUPLA_SD_CALL_GET_CHANNEL_FUNCTIONS_RESULT 630 // ver. >= 12 +#define SUPLA_CS_CALL_SET_CHANNEL_CAPTION 640 // ver. >= 12 +#define SUPLA_SC_CALL_SET_CHANNEL_CAPTION_RESULT 650 // ver. >= 12 + +#define SUPLA_RESULT_CALL_NOT_ALLOWED -5 +#define SUPLA_RESULT_DATA_TOO_LARGE -4 +#define SUPLA_RESULT_BUFFER_OVERFLOW -3 +#define SUPLA_RESULT_DATA_ERROR -2 +#define SUPLA_RESULT_VERSION_ERROR -1 +#define SUPLA_RESULT_FALSE 0 +#define SUPLA_RESULT_TRUE 1 + +#define SUPLA_RESULTCODE_NONE 0 +#define SUPLA_RESULTCODE_UNSUPORTED 1 +#define SUPLA_RESULTCODE_FALSE 2 +#define SUPLA_RESULTCODE_TRUE 3 +#define SUPLA_RESULTCODE_TEMPORARILY_UNAVAILABLE 4 +#define SUPLA_RESULTCODE_BAD_CREDENTIALS 5 +#define SUPLA_RESULTCODE_LOCATION_CONFLICT 6 +#define SUPLA_RESULTCODE_CHANNEL_CONFLICT 7 +#define SUPLA_RESULTCODE_DEVICE_DISABLED 8 +#define SUPLA_RESULTCODE_ACCESSID_DISABLED 9 +#define SUPLA_RESULTCODE_LOCATION_DISABLED 10 +#define SUPLA_RESULTCODE_CLIENT_DISABLED 11 +#define SUPLA_RESULTCODE_CLIENT_LIMITEXCEEDED 12 +#define SUPLA_RESULTCODE_DEVICE_LIMITEXCEEDED 13 +#define SUPLA_RESULTCODE_GUID_ERROR 14 +#define SUPLA_RESULTCODE_HOSTNOTFOUND 15 // ver. >= 5 +#define SUPLA_RESULTCODE_CANTCONNECTTOHOST 16 // ver. >= 5 +#define SUPLA_RESULTCODE_REGISTRATION_DISABLED 17 // ver. >= 7 +#define SUPLA_RESULTCODE_ACCESSID_NOT_ASSIGNED 18 // ver. >= 7 +#define SUPLA_RESULTCODE_AUTHKEY_ERROR 19 // ver. >= 7 +#define SUPLA_RESULTCODE_NO_LOCATION_AVAILABLE 20 // ver. >= 7 +#define SUPLA_RESULTCODE_USER_CONFLICT 21 // Deprecated +#define SUPLA_RESULTCODE_UNAUTHORIZED 22 // ver. >= 10 +#define SUPLA_RESULTCODE_AUTHORIZED 23 // ver. >= 10 +#define SUPLA_RESULTCODE_NOT_ALLOWED 24 // ver. >= 12 +#define SUPLA_RESULTCODE_CHANNELNOTFOUND 25 // ver. >= 12 +#define SUPLA_RESULTCODE_UNKNOWN_ERROR 26 // ver. >= 12 +#define SUPLA_RESULTCODE_DENY_CHANNEL_BELONG_TO_GROUP 27 // ver. >= 12 +#define SUPLA_RESULTCODE_DENY_CHANNEL_HAS_SCHEDULE 28 // ver. >= 12 +#define SUPLA_RESULTCODE_DENY_CHANNEL_IS_ASSOCIETED_WITH_SCENE 29 // ver. >= 12 + +#define SUPLA_OAUTH_RESULTCODE_ERROR 0 // ver. >= 10 +#define SUPLA_OAUTH_RESULTCODE_SUCCESS 1 // ver. >= 10 +#define SUPLA_OAUTH_TEMPORARILY_UNAVAILABLE 2 // ver. >= 10 + +#define SUPLA_DEVICE_NAME_MAXSIZE 201 +#define SUPLA_CLIENT_NAME_MAXSIZE 201 +#define SUPLA_SENDER_NAME_MAXSIZE 201 + +#ifdef __AVR__ +#ifdef __AVR_ATmega2560__ +#define SUPLA_CHANNELMAXCOUNT 32 +#else +#define SUPLA_CHANNELMAXCOUNT 1 +#endif +#else +#define SUPLA_CHANNELMAXCOUNT 128 +#endif + +#define SUPLA_CHANNELVALUE_SIZE 8 +#define SUPLA_CHANNELEXTENDEDVALUE_SIZE 1024 + +#define SUPLA_CHANNELTYPE_SENSORNO 1000 +#define SUPLA_CHANNELTYPE_SENSORNC 1010 // DEPRECATED +#define SUPLA_CHANNELTYPE_DISTANCESENSOR 1020 // ver. >= 5 +#define SUPLA_CHANNELTYPE_CALLBUTTON 1500 // ver. >= 4 +#define SUPLA_CHANNELTYPE_RELAYHFD4 2000 +#define SUPLA_CHANNELTYPE_RELAYG5LA1A 2010 +#define SUPLA_CHANNELTYPE_2XRELAYG5LA1A 2020 +#define SUPLA_CHANNELTYPE_RELAY 2900 +#define SUPLA_CHANNELTYPE_THERMOMETERDS18B20 3000 +#define SUPLA_CHANNELTYPE_DHT11 3010 // ver. >= 4 +#define SUPLA_CHANNELTYPE_DHT22 3020 // ver. >= 4 +#define SUPLA_CHANNELTYPE_DHT21 3022 // ver. >= 5 +#define SUPLA_CHANNELTYPE_AM2302 3030 // ver. >= 4 +#define SUPLA_CHANNELTYPE_AM2301 3032 // ver. >= 5 + +#define SUPLA_CHANNELTYPE_THERMOMETER 3034 // ver. >= 8 +#define SUPLA_CHANNELTYPE_HUMIDITYSENSOR 3036 // ver. >= 8 +#define SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR 3038 // ver. >= 8 +#define SUPLA_CHANNELTYPE_WINDSENSOR 3042 // ver. >= 8 +#define SUPLA_CHANNELTYPE_PRESSURESENSOR 3044 // ver. >= 8 +#define SUPLA_CHANNELTYPE_RAINSENSOR 3048 // ver. >= 8 +#define SUPLA_CHANNELTYPE_WEIGHTSENSOR 3050 // ver. >= 8 +#define SUPLA_CHANNELTYPE_WEATHER_STATION 3100 // ver. >= 8 + +#define SUPLA_CHANNELTYPE_DIMMER 4000 // ver. >= 4 +#define SUPLA_CHANNELTYPE_RGBLEDCONTROLLER 4010 // ver. >= 4 +#define SUPLA_CHANNELTYPE_DIMMERANDRGBLED 4020 // ver. >= 4 + +#define SUPLA_CHANNELTYPE_ELECTRICITY_METER 5000 // ver. >= 10 +#define SUPLA_CHANNELTYPE_IMPULSE_COUNTER 5010 // ver. >= 10 + +#define SUPLA_CHANNELTYPE_THERMOSTAT 6000 // ver. >= 11 +#define SUPLA_CHANNELTYPE_THERMOSTAT_HEATPOL_HOMEPLUS 6010 // ver. >= 11 + +#define SUPLA_CHANNELTYPE_VALVE_OPENCLOSE 7000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_VALVE_PERCENTAGE 7010 // ver. >= 12 +#define SUPLA_CHANNELTYPE_BRIDGE 8000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_GENERAL_PURPOSE_MEASUREMENT 9000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_ENGINE 10000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_ACTIONTRIGGER 11000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_SMARTGLASS 12000 // ver. >= 12 + +#define SUPLA_CHANNELDRIVER_MCP23008 2 + +#define SUPLA_CHANNELFNC_NONE 0 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEGATEWAYLOCK 10 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEGATE 20 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEGARAGEDOOR 30 +#define SUPLA_CHANNELFNC_THERMOMETER 40 +#define SUPLA_CHANNELFNC_HUMIDITY 42 +#define SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE 45 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_GATEWAY 50 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_GATE 60 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_GARAGEDOOR 70 +#define SUPLA_CHANNELFNC_NOLIQUIDSENSOR 80 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEDOORLOCK 90 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_DOOR 100 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER 110 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_ROLLERSHUTTER 120 +#define SUPLA_CHANNELFNC_POWERSWITCH 130 +#define SUPLA_CHANNELFNC_LIGHTSWITCH 140 +#define SUPLA_CHANNELFNC_RING 150 +#define SUPLA_CHANNELFNC_ALARM 160 +#define SUPLA_CHANNELFNC_NOTIFICATION 170 +#define SUPLA_CHANNELFNC_DIMMER 180 +#define SUPLA_CHANNELFNC_RGBLIGHTING 190 +#define SUPLA_CHANNELFNC_DIMMERANDRGBLIGHTING 200 +#define SUPLA_CHANNELFNC_DEPTHSENSOR 210 // ver. >= 5 +#define SUPLA_CHANNELFNC_DISTANCESENSOR 220 // ver. >= 5 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_WINDOW 230 // ver. >= 8 +#define SUPLA_CHANNELFNC_MAILSENSOR 240 // ver. >= 8 +#define SUPLA_CHANNELFNC_WINDSENSOR 250 // ver. >= 8 +#define SUPLA_CHANNELFNC_PRESSURESENSOR 260 // ver. >= 8 +#define SUPLA_CHANNELFNC_RAINSENSOR 270 // ver. >= 8 +#define SUPLA_CHANNELFNC_WEIGHTSENSOR 280 // ver. >= 8 +#define SUPLA_CHANNELFNC_WEATHER_STATION 290 // ver. >= 8 +#define SUPLA_CHANNELFNC_STAIRCASETIMER 300 // ver. >= 8 +#define SUPLA_CHANNELFNC_ELECTRICITY_METER 310 // ver. >= 10 +#define SUPLA_CHANNELFNC_IC_ELECTRICITY_METER 315 // ver. >= 12 +#define SUPLA_CHANNELFNC_IC_GAS_METER 320 // ver. >= 10 +#define SUPLA_CHANNELFNC_IC_WATER_METER 330 // ver. >= 10 +#define SUPLA_CHANNELFNC_IC_HEAT_METER 340 // ver. >= 10 +#define SUPLA_CHANNELFNC_THERMOSTAT 400 // ver. >= 11 +#define SUPLA_CHANNELFNC_THERMOSTAT_HEATPOL_HOMEPLUS 410 // ver. >= 11 +#define SUPLA_CHANNELFNC_VALVE_OPENCLOSE 500 // ver. >= 12 +#define SUPLA_CHANNELFNC_VALVE_PERCENTAGE 510 // ver. >= 12 +#define SUPLA_CHANNELFNC_GENERAL_PURPOSE_MEASUREMENT 520 // ver. >= 12 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEENGINESPEED 600 // ver. >= 12 +#define SUPLA_CHANNELFNC_ACTIONTRIGGER 700 // ver. >= 12 +#define SUPLA_CHANNELFNC_SMARTGLASS 800 // ver. >= 12 + +#define SUPLA_BIT_FUNC_CONTROLLINGTHEGATEWAYLOCK 0x00000001 +#define SUPLA_BIT_FUNC_CONTROLLINGTHEGATE 0x00000002 +#define SUPLA_BIT_FUNC_CONTROLLINGTHEGARAGEDOOR 0x00000004 +#define SUPLA_BIT_FUNC_CONTROLLINGTHEDOORLOCK 0x00000008 +#define SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER 0x00000010 +#define SUPLA_BIT_FUNC_POWERSWITCH 0x00000020 +#define SUPLA_BIT_FUNC_LIGHTSWITCH 0x00000040 +#define SUPLA_BIT_FUNC_STAIRCASETIMER 0x00000080 // ver. >= 8 +#define SUPLA_BIT_FUNC_THERMOMETER 0x00000100 // ver. >= 12 +#define SUPLA_BIT_FUNC_HUMIDITYANDTEMPERATURE 0x00000200 // ver. >= 12 +#define SUPLA_BIT_FUNC_HUMIDITY 0x00000400 // ver. >= 12 +#define SUPLA_BIT_FUNC_WINDSENSOR 0x00000800 // ver. >= 12 +#define SUPLA_BIT_FUNC_PRESSURESENSOR 0x00001000 // ver. >= 12 +#define SUPLA_BIT_FUNC_RAINSENSOR 0x00002000 // ver. >= 12 +#define SUPLA_BIT_FUNC_WEIGHTSENSOR 0x00004000 // ver. >= 12 + +#define SUPLA_EVENT_CONTROLLINGTHEGATEWAYLOCK 10 +#define SUPLA_EVENT_CONTROLLINGTHEGATE 20 +#define SUPLA_EVENT_CONTROLLINGTHEGARAGEDOOR 30 +#define SUPLA_EVENT_CONTROLLINGTHEDOORLOCK 40 +#define SUPLA_EVENT_CONTROLLINGTHEROLLERSHUTTER 50 +#define SUPLA_EVENT_POWERONOFF 60 +#define SUPLA_EVENT_LIGHTONOFF 70 +#define SUPLA_EVENT_STAIRCASETIMERONOFF 80 // ver. >= 9 +#define SUPLA_EVENT_VALVEOPENCLOSE 90 // ver. >= 12 +#define SUPLA_EVENT_SET_BRIDGE_VALUE_FAILED 100 // ver. >= 12 + +#define SUPLA_URL_PROTO_HTTP 0x01 +#define SUPLA_URL_PROTO_HTTPS 0x02 + +#define SUPLA_PLATFORM_UNKNOWN 0 +#define SUPLA_PLATFORM_ESP8266 1 + +#define SUPLA_TARGET_CHANNEL 0 +#define SUPLA_TARGET_GROUP 1 +#define SUPLA_TARGET_IODEVICE 2 + +#define SUPLA_MFR_UNKNOWN 0 +#define SUPLA_MFR_ACSOFTWARE 1 +#define SUPLA_MFR_TRANSCOM 2 +#define SUPLA_MFR_LOGI 3 +#define SUPLA_MFR_ZAMEL 4 +#define SUPLA_MFR_NICE 5 +#define SUPLA_MFR_ITEAD 6 +#define SUPLA_MFR_DOYLETRATT 7 +#define SUPLA_MFR_HEATPOL 8 +#define SUPLA_MFR_FAKRO 9 +#define SUPLA_MFR_PEVEKO 10 +#define SUPLA_MFR_WEKTA 11 +#define SUPLA_MFR_STA_SYSTEM 12 + +#define SUPLA_CHANNEL_FLAG_ZWAVE_BRIDGE 0x0001 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_IR_BRIDGE 0x0002 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_RF_BRIDGE 0x0004 // ver. >= 12 +// Free bit for future use: 0x0008 +#define SUPLA_CHANNEL_FLAG_CHART_TYPE_BAR 0x0010 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CHART_DS_TYPE_DIFFERENTAL 0x0020 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CHART_INTERPOLATE_MEASUREMENTS 0x0040 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CAP_ACTION1 0x0080 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CAP_ACTION2 0x0100 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CAP_ACTION3 0x0200 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CAP_ACTION4 0x0400 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_CAP_ACTION5 0x0800 // ver. >= 12 +// Free bits for future use: 0x1000, 0x2000, 0x4000, 0x8000 +#define SUPLA_CHANNEL_FLAG_CHANNELSTATE 0x00010000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_PHASE1_UNSUPPORTED 0x00020000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_PHASE2_UNSUPPORTED 0x00040000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_PHASE3_UNSUPPORTED 0x00080000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_TIME_SETTING_NOT_AVAILABLE 0x00100000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_RSA_ENCRYPTED_PIN_REQUIRED 0x00200000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_OFFLINE_DURING_REGISTRATION 0x00400000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_ZIGBEE_BRIDGE 0x00800000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_COUNTDOWN_TIMER_SUPPORTED 0x01000000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_LIGHTSOURCELIFESPAN_SETTABLE \ + 0x02000000 // ver. >= 12 +#define SUPLA_CHANNEL_FLAG_POSSIBLE_SLEEP_MODE 0x04000000 // ver. >= 12 + +#pragma pack(push, 1) + +typedef struct { + char tag[SUPLA_TAG_SIZE]; + unsigned char version; + unsigned _supla_int_t rr_id; // Request/Response ID + unsigned _supla_int_t call_type; + unsigned _supla_int_t data_size; + char data[SUPLA_MAX_DATA_SIZE]; // Last variable in struct! +} TSuplaDataPacket; + +typedef struct { + // server -> device|client + unsigned char proto_version_min; + unsigned char proto_version; + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; +} TSDC_SuplaGetVersionResult; + +typedef struct { + // server -> device|client + unsigned char server_version_min; + unsigned char server_version; +} TSDC_SuplaVersionError; + +typedef struct { + // device|client -> server + struct _supla_timeval now; +} TDCS_SuplaPingServer; + +// Compatibility with ESP8266 +struct timeval_compat { + _supla_int_t tv_sec; + _supla_int_t tv_usec; +}; + +typedef struct { + // device|client -> server + struct timeval_compat now; +} TDCS_SuplaPingServer_COMPAT; + +typedef struct { + // server -> device|client + struct _supla_timeval now; +} TSDC_SuplaPingServerResult; + +typedef struct { + // device|client -> server + unsigned char activity_timeout; +} TDCS_SuplaSetActivityTimeout; + +typedef struct { + // server -> device|client + unsigned char activity_timeout; + unsigned char min; + unsigned char max; +} TSDC_SuplaSetActivityTimeoutResult; + +typedef struct { + char value[SUPLA_CHANNELVALUE_SIZE]; + char sub_value[SUPLA_CHANNELVALUE_SIZE]; // For example sensor value +} TSuplaChannelValue; + +#ifdef USE_DEPRECATED_EMEV_V1 +#define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V1 10 +#endif /*USE_DEPRECATED_EMEV_V1*/ +#define EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V2 12 +#define EV_TYPE_IMPULSE_COUNTER_DETAILS_V1 20 +#define EV_TYPE_THERMOSTAT_DETAILS_V1 30 +#define EV_TYPE_CHANNEL_STATE_V1 40 +#define EV_TYPE_TIMER_STATE_V1 50 +#define EV_TYPE_CHANNEL_AND_TIMER_STATE_V1 60 + +#define CALCFG_TYPE_THERMOSTAT_DETAILS_V1 10 + +typedef struct { + char type; // EV_TYPE_ + unsigned _supla_int_t size; + char value[SUPLA_CHANNELEXTENDEDVALUE_SIZE]; // Last variable in struct! +} TSuplaChannelExtendedValue; // v. >= 10 + +typedef struct { + // server -> client + char EOL; // End Of List + _supla_int_t Id; + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_LOCATION_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaLocation; + +typedef struct { + // server -> client + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaLocation + items[SUPLA_LOCATIONPACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaLocationPack; + +typedef struct { + // device -> server + unsigned char Number; + _supla_int_t Type; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannel; + +typedef struct { + // device -> server + + _supla_int_t LocationID; + char LocationPWD[SUPLA_LOCATION_PWD_MAXSIZE]; // UTF8 + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + unsigned char channel_count; + TDS_SuplaDeviceChannel + channels[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! +} TDS_SuplaRegisterDevice; + +typedef struct { + // device -> server + + unsigned char Number; + _supla_int_t Type; + + _supla_int_t FuncList; + _supla_int_t Default; + + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannel_B; // ver. >= 2 + +typedef struct { + // device -> server + + unsigned char Number; + _supla_int_t Type; + + _supla_int_t FuncList; + _supla_int_t Default; + _supla_int_t Flags; + + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannel_C; // ver. >= 10 + +typedef struct { + // device -> server + + _supla_int_t LocationID; + char LocationPWD[SUPLA_LOCATION_PWD_MAXSIZE]; // UTF8 + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + unsigned char channel_count; + TDS_SuplaDeviceChannel_B + channels[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! +} TDS_SuplaRegisterDevice_B; // ver. >= 2 + +typedef struct { + // device -> server + + _supla_int_t LocationID; + char LocationPWD[SUPLA_LOCATION_PWD_MAXSIZE]; // UTF8 + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; + + unsigned char channel_count; + TDS_SuplaDeviceChannel_B + channels[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! +} TDS_SuplaRegisterDevice_C; // ver. >= 6 + +typedef struct { + // device -> server + + char Email[SUPLA_EMAIL_MAXSIZE]; // UTF8 + char AuthKey[SUPLA_AUTHKEY_SIZE]; + + char GUID[SUPLA_GUID_SIZE]; + + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; + + unsigned char channel_count; + TDS_SuplaDeviceChannel_B + channels[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! +} TDS_SuplaRegisterDevice_D; // ver. >= 7 + +typedef struct { + // device -> server + + char Email[SUPLA_EMAIL_MAXSIZE]; // UTF8 + char AuthKey[SUPLA_AUTHKEY_SIZE]; + + char GUID[SUPLA_GUID_SIZE]; + + char Name[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; + + _supla_int_t Flags; + _supla_int16_t ManufacturerID; + _supla_int16_t ProductID; + + unsigned char channel_count; + TDS_SuplaDeviceChannel_C + channels[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! +} TDS_SuplaRegisterDevice_E; // ver. >= 10 + +typedef struct { + // server -> device + + _supla_int_t result_code; + unsigned char activity_timeout; + unsigned char version; + unsigned char version_min; +} TSD_SuplaRegisterDeviceResult; + +typedef struct { + // device -> server + + unsigned char ChannelNumber; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannelValue; + +typedef struct { + // device -> server + + unsigned char ChannelNumber; + unsigned char Offline; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannelValue_B; // v. >= 12 + +typedef struct { + // device -> server + + unsigned char ChannelNumber; + unsigned char Offline; + unsigned _supla_int_t ValidityTimeSec; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TDS_SuplaDeviceChannelValue_C; // v. >= 12 + +typedef struct { + // device -> server + + unsigned char ChannelNumber; + TSuplaChannelExtendedValue value; // Last variable in struct! +} TDS_SuplaDeviceChannelExtendedValue; // v. >= 10 + +typedef struct { + // server -> device + _supla_int_t SenderID; + unsigned char ChannelNumber; + unsigned _supla_int_t DurationMS; + + char value[SUPLA_CHANNELVALUE_SIZE]; +} TSD_SuplaChannelNewValue; + +typedef struct { + // server -> device + _supla_int_t SenderID; + _supla_int_t GroupID; + unsigned char EOL; // End Of List + unsigned char ChannelNumber; + unsigned _supla_int_t DurationMS; + + char value[SUPLA_CHANNELVALUE_SIZE]; +} TSD_SuplaChannelGroupNewValue; // v. >= 13 + +typedef struct { + // device -> server + unsigned char ChannelNumber; + _supla_int_t SenderID; + char Success; +} TDS_SuplaChannelNewValueResult; + +typedef struct { + // server -> client + + char EOL; // End Of List + _supla_int_t Id; + char online; + + TSuplaChannelValue value; +} TSC_SuplaChannelValue; + +typedef struct { + // server -> client + _supla_int_t Id; + + TSuplaChannelExtendedValue value; // Last variable in struct! +} TSC_SuplaChannelExtendedValue; + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + + TSC_SuplaChannelValue + items[SUPLA_CHANNELVALUE_PACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelValuePack; // ver. >= 9 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + unsigned _supla_int_t pack_size; + + char pack[SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXDATASIZE]; // Last variable in + // struct! +} TSC_SuplaChannelExtendedValuePack; // ver. >= 10 + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t Id; + _supla_int_t LocationID; + _supla_int_t Func; + char online; + + TSuplaChannelValue value; + + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaChannel; + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannel + items[SUPLA_CHANNELPACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelPack; + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t Id; + _supla_int_t LocationID; + _supla_int_t Func; + _supla_int_t AltIcon; + unsigned _supla_int_t Flags; + unsigned char ProtocolVersion; + char online; + + TSuplaChannelValue value; + + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaChannel_B; // ver. >= 8 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannel_B + items[SUPLA_CHANNELPACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelPack_B; // ver. >= 8 + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t Id; + _supla_int_t DeviceID; + _supla_int_t LocationID; + _supla_int_t Type; + _supla_int_t Func; + _supla_int_t AltIcon; + _supla_int_t UserIcon; + _supla_int16_t ManufacturerID; + _supla_int16_t ProductID; + + unsigned _supla_int_t Flags; + unsigned char ProtocolVersion; + char online; + + TSuplaChannelValue value; + + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaChannel_C; // ver. >= 10 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannel_C + items[SUPLA_CHANNELPACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelPack_C; // ver. >= 10 + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t Id; + _supla_int_t LocationID; + _supla_int_t Func; + _supla_int_t AltIcon; + unsigned _supla_int_t Flags; + + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNELGROUP_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaChannelGroup; // ver. >= 9 + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t Id; + _supla_int_t LocationID; + _supla_int_t Func; + _supla_int_t AltIcon; + _supla_int_t UserIcon; + unsigned _supla_int_t Flags; + + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNELGROUP_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SuplaChannelGroup_B; // ver. >= 10 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannelGroup + items[SUPLA_CHANNELGROUP_PACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelGroupPack; // ver. >= 9 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannelGroup_B + items[SUPLA_CHANNELGROUP_PACK_MAXCOUNT]; // Last variable in struct! +} TSC_SuplaChannelGroupPack_B; // ver. >= 10 + +typedef struct { + // server -> client + char EOL; // End Of List + + _supla_int_t ChannelGroupID; + _supla_int_t ChannelID; +} TSC_SuplaChannelGroupRelation; // ver. >= 9 + +typedef struct { + // server -> client + + _supla_int_t count; + _supla_int_t total_left; + TSC_SuplaChannelGroupRelation + items[SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT]; // Last variable in + // struct! +} TSC_SuplaChannelGroupRelationPack; // ver. >= 9 + +typedef struct { + // client -> server + + _supla_int_t AccessID; + char AccessIDpwd[SUPLA_ACCESSID_PWD_MAXSIZE]; // UTF8 + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_CLIENT_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; +} TCS_SuplaRegisterClient; + +typedef struct { + // client -> server + + _supla_int_t AccessID; + char AccessIDpwd[SUPLA_ACCESSID_PWD_MAXSIZE]; // UTF8 + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_CLIENT_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; +} TCS_SuplaRegisterClient_B; // ver. >= 6 + +typedef struct { + // client -> server + + char Email[SUPLA_EMAIL_MAXSIZE]; // UTF8 + char AuthKey[SUPLA_AUTHKEY_SIZE]; + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_CLIENT_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; +} TCS_SuplaRegisterClient_C; // ver. >= 7 + +typedef struct { + // client -> server + + char Email[SUPLA_EMAIL_MAXSIZE]; // UTF8 + char Password[SUPLA_PASSWORD_MAXSIZE]; // Optional - UTF8 + char AuthKey[SUPLA_AUTHKEY_SIZE]; + + char GUID[SUPLA_GUID_SIZE]; + char Name[SUPLA_CLIENT_NAME_MAXSIZE]; // UTF8 + char SoftVer[SUPLA_SOFTVER_MAXSIZE]; + + char ServerName[SUPLA_SERVER_NAME_MAXSIZE]; +} TCS_SuplaRegisterClient_D; // ver. >= 12 + +typedef struct { + // server -> client + + _supla_int_t result_code; + _supla_int_t ClientID; + _supla_int_t LocationCount; + _supla_int_t ChannelCount; + unsigned char activity_timeout; + unsigned char version; + unsigned char version_min; +} TSC_SuplaRegisterClientResult; + +typedef struct { + // server -> client + + _supla_int_t result_code; + _supla_int_t ClientID; + _supla_int_t LocationCount; + _supla_int_t ChannelCount; + _supla_int_t ChannelGroupCount; + _supla_int_t Flags; + unsigned char activity_timeout; + unsigned char version; + unsigned char version_min; +} TSC_SuplaRegisterClientResult_B; // ver. >= 9 + +typedef struct { + // client -> server + unsigned char ChannelId; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TCS_SuplaChannelNewValue; // Deprecated + +typedef struct { + // client -> server + _supla_int_t ChannelId; + char value[SUPLA_CHANNELVALUE_SIZE]; +} TCS_SuplaChannelNewValue_B; + +typedef struct { + // client -> server + _supla_int_t Id; + char Target; // SUPLA_TARGET_ + char value[SUPLA_CHANNELVALUE_SIZE]; +} TCS_SuplaNewValue; // ver. >= 9 + +typedef struct { + // server -> client + _supla_int_t Event; + _supla_int_t ChannelID; + unsigned _supla_int_t DurationMS; + + _supla_int_t SenderID; + unsigned _supla_int_t + SenderNameSize; // including the terminating null byte ('\0') + char + SenderName[SUPLA_SENDER_NAME_MAXSIZE]; // Last variable in struct! | UTF8 +} TSC_SuplaEvent; + +typedef struct { + char Platform; + + _supla_int_t Param1; + _supla_int_t Param2; + _supla_int_t Param3; + _supla_int_t Param4; +} TDS_FirmwareUpdateParams; + +typedef struct { + char available_protocols; + char host[SUPLA_URL_HOST_MAXSIZE]; + _supla_int_t port; + char path[SUPLA_URL_PATH_MAXSIZE]; +} TSD_FirmwareUpdate_Url; + +typedef struct { + char exists; + TSD_FirmwareUpdate_Url url; +} TSD_FirmwareUpdate_UrlResult; + +typedef struct { + unsigned _supla_int_t client_timestamp; // time >= now == enabled + unsigned _supla_int_t iodevice_timestamp; // time >= now == enabled +} TSDC_RegistrationEnabled; + +typedef struct { + unsigned _supla_int_t ExpiresIn; + unsigned _supla_int_t + TokenSize; // including the terminating null byte ('\0') + char Token[SUPLA_OAUTH_TOKEN_MAXSIZE]; // Last variable in struct! +} TSC_OAuthToken; // ver. >= 10 + +typedef struct { + unsigned char ResultCode; + TSC_OAuthToken Token; +} TSC_OAuthTokenRequestResult; // ver. >= 10 + +typedef struct { + // 3 phases + unsigned _supla_int16_t freq; // * 0.01 Hz + unsigned _supla_int16_t voltage[3]; // * 0.01 V + unsigned _supla_int16_t + current[3]; // * 0.001 A (0.01A FOR EM_VAR_CURRENT_OVER_65A) + _supla_int_t power_active[3]; // * 0.00001 kW + _supla_int_t power_reactive[3]; // * 0.00001 kvar + _supla_int_t power_apparent[3]; // * 0.00001 kVA + _supla_int16_t power_factor[3]; // * 0.001 + _supla_int16_t phase_angle[3]; // * 0.1 degree +} TElectricityMeter_Measurement; // v. >= 10 + +#define EM_VAR_FREQ 0x0001 +#define EM_VAR_VOLTAGE 0x0002 +#define EM_VAR_CURRENT 0x0004 +#define EM_VAR_POWER_ACTIVE 0x0008 +#define EM_VAR_POWER_REACTIVE 0x0010 +#define EM_VAR_POWER_APPARENT 0x0020 +#define EM_VAR_POWER_FACTOR 0x0040 +#define EM_VAR_PHASE_ANGLE 0x0080 +#define EM_VAR_FORWARD_ACTIVE_ENERGY 0x0100 +#define EM_VAR_REVERSE_ACTIVE_ENERGY 0x0200 +#define EM_VAR_FORWARD_REACTIVE_ENERGY 0x0400 +#define EM_VAR_REVERSE_REACTIVE_ENERGY 0x0800 +#define EM_VAR_CURRENT_OVER_65A 0x1000 +#define EM_VAR_FORWARD_ACTIVE_ENERGY_BALANCED 0x2000 +#define EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED 0x4000 +#define EM_VAR_ALL 0xFFFF + +#define EM_MEASUREMENT_COUNT 5 + +#ifdef USE_DEPRECATED_EMEV_V1 +// [IODevice->Server->Client] +typedef struct { + unsigned _supla_int64_t total_forward_active_energy[3]; // * 0.00001 kWh + unsigned _supla_int64_t total_reverse_active_energy[3]; // * 0.00001 kWh + unsigned _supla_int64_t total_forward_reactive_energy[3]; // * 0.00001 kvarh + unsigned _supla_int64_t total_reverse_reactive_energy[3]; // * 0.00001 kvarh + + // The price per unit, total cost and currency is overwritten by the server + // total_cost == SUM(total_forward_active_energy[n] * price_per_unit + _supla_int_t total_cost; // * 0.01 + _supla_int_t price_per_unit; // * 0.0001 + // Currency Code A https://www.nationsonline.org/oneworld/currencies.htm + char currency[3]; + + _supla_int_t measured_values; + _supla_int_t period; // Approximate period between measurements in seconds + _supla_int_t m_count; + TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in + // struct! +} TElectricityMeter_ExtendedValue; // v. >= 10 +#endif /*USE_DEPRECATED_EMEV_V1*/ + +// [IODevice->Server->Client] +typedef struct { + unsigned _supla_int64_t total_forward_active_energy[3]; // * 0.00001 kWh + unsigned _supla_int64_t total_reverse_active_energy[3]; // * 0.00001 kWh + unsigned _supla_int64_t total_forward_reactive_energy[3]; // * 0.00001 kvarh + unsigned _supla_int64_t total_reverse_reactive_energy[3]; // * 0.00001 kvarh + unsigned _supla_int64_t + total_forward_active_energy_balanced; // * 0.00001 kWh + // Vector phase-to-phase balancing + unsigned _supla_int64_t + total_reverse_active_energy_balanced; // * 0.00001 kWh + // Vector phase-to-phase balancing + + // The price per unit, total cost and currency is overwritten by the server + // total_cost == SUM(total_forward_active_energy[n] * price_per_unit + _supla_int_t total_cost; // * 0.01 + _supla_int_t total_cost_balanced; // * 0.01 + _supla_int_t price_per_unit; // * 0.0001 + // Currency Code A https://www.nationsonline.org/oneworld/currencies.htm + char currency[3]; + + _supla_int_t measured_values; + _supla_int_t period; // Approximate period between measurements in seconds + _supla_int_t m_count; + TElectricityMeter_Measurement m[EM_MEASUREMENT_COUNT]; // Last variable in + // struct! +} TElectricityMeter_ExtendedValue_V2; // v. >= 12 + +#define EM_VALUE_FLAG_PHASE1_ON 0x01 +#define EM_VALUE_FLAG_PHASE2_ON 0x02 +#define EM_VALUE_FLAG_PHASE3_ON 0x04 + +// [IODevice->Server->Client] +typedef struct { + char flags; + unsigned _supla_int_t total_forward_active_energy; // * 0.01 kW +} TElectricityMeter_Value; // v. >= 10 + +typedef struct { + // The price per unit, total cost and currency is overwritten by the server + // total_cost = calculated_value * price_per_unit + _supla_int_t total_cost; // * 0.01 + _supla_int_t price_per_unit; // * 0.0001 + // Currency Code A https://www.nationsonline.org/oneworld/currencies.htm + char currency[3]; + char custom_unit[9]; // UTF8 including the terminating null byte ('\0') + + _supla_int_t impulses_per_unit; + unsigned _supla_int64_t counter; + _supla_int64_t calculated_value; // * 0.001 +} TSC_ImpulseCounter_ExtendedValue; // v. >= 10 + +typedef struct { + unsigned _supla_int64_t counter; +} TDS_ImpulseCounter_Value; + +typedef struct { + unsigned _supla_int64_t calculated_value; // * 0.001 +} TSC_ImpulseCounter_Value; // v. >= 10 + +typedef struct { + char Email[SUPLA_EMAIL_MAXSIZE]; // UTF8 + char Password[SUPLA_PASSWORD_MAXSIZE]; // UTF8 +} TCS_SuperUserAuthorizationRequest; // v. >= 10 + +typedef struct { + _supla_int_t Result; +} TSC_SuperUserAuthorizationResult; // v. >= 10 + +#define SUPLA_CALCFG_RESULT_FALSE 0 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_TRUE 1 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_DONE 2 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_IN_PROGRESS 3 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_NODE_FOUND 4 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_SENDER_CONFLICT 100 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_TIMEOUT 101 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_NOT_SUPPORTED 102 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_ID_NOT_EXISTS 103 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_UNAUTHORIZED 104 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_DEBUG 105 // ver. >= 12 +#define SUPLA_CALCFG_RESULT_NOT_SUPPORTED_IN_SLAVE_MODE 106 // ver. >= 12 + +#define SUPLA_CALCFG_CMD_GET_CHANNEL_FUNCLIST 1000 // v. >= 11 +#define SUPLA_CALCFG_CMD_CANCEL_ALL_CLIENT_COMMANDS 1010 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_RESET_AND_CLEAR 2000 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_ADD_NODE 2010 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_REMOVE_NODE 2020 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_GET_NODE_LIST 2030 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_GET_ASSIGNED_NODE_ID 2040 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_ASSIGN_NODE_ID 2050 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_GET_WAKE_UP_SETTINGS 2060 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_SET_WAKE_UP_TIME 2070 // v. >= 12 +#define SUPLA_CALCFG_CMD_ZWAVE_CONFIG_MODE_ACTIVE 4000 // v. >= 12 +#define SUPLA_CALCFG_CMD_DEBUG_STRING 5000 // v. >= 12 +#define SUPLA_CALCFG_CMD_PROGRESS_REPORT 5001 // v. >= 12 +#define SUPLA_CALCFG_CMD_SET_LIGHTSOURCE_LIFESPAN 6000 // v. >= 12 + +#define CALCFG_ZWAVE_SCREENTYPE_UNKNOWN 0 +#define CALCFG_ZWAVE_SCREENTYPE_MULTILEVEL 1 +#define CALCFG_ZWAVE_SCREENTYPE_BINARY 2 +#define CALCFG_ZWAVE_SCREENTYPE_MULTILEVEL_AUTOSHADE 3 +#define CALCFG_ZWAVE_SCREENTYPE_MULTILEVEL_COLOR_CONTROL 4 +#define CALCFG_ZWAVE_SCREENTYPE_BINARY_COLOR_CONTROL 5 +#define CALCFG_ZWAVE_SCREENTYPE_SENSOR 6 + +#define ZWAVE_NODE_NAME_MAXSIZE 50 + +#define ZWAVE_NODE_FLAG_CHANNEL_ASSIGNED 0x1 +#define ZWAVE_NODE_FLAG_WAKEUP_TIME_SETTABLE 0x2 + +typedef struct { + unsigned char Id; + unsigned char Flags; + union { + unsigned char ChannelNumber; + _supla_int_t ChannelID; + }; + unsigned char ScreenType; + unsigned char NameSize; // including the terminating null byte ('\0') + char Name[ZWAVE_NODE_NAME_MAXSIZE]; // UTF8. Last variable in struct! +} TCalCfg_ZWave_Node; // v. >= 12 + +/* +typedef struct { + unsigned int MinimumSec : 24; + unsigned int MaximumSec : 24; + unsigned int ValueSec : 24; + unsigned int IntervalStepSec : 24; +} TCalCfg_ZWave_WakeupSettingsReport; + +typedef struct { + unsigned int TimeSec : 24; +} TCalCfg_ZWave_WakeUpTime; + +*/ + +typedef struct { + _supla_int_t Command; + unsigned char Progress; // 0 - 100% +} TCalCfg_ProgressReport; + +typedef struct { + unsigned char ResetCounter; // 0 - NO, 1 - YES + unsigned char SetTime; // 0 - NO, 1 - YES + unsigned short LightSourceLifespan; // 0 - 65535 hours +} TCalCfg_LightSourceLifespan; + +// CALCFG == CALIBRATION / CONFIG +typedef struct { + _supla_int_t ChannelID; + _supla_int_t Command; + _supla_int_t DataType; + unsigned _supla_int_t DataSize; + char Data[SUPLA_CALCFG_DATA_MAXSIZE]; // Last variable in struct! +} TCS_DeviceCalCfgRequest; // v. >= 10 + +// CALCFG == CALIBRATION / CONFIG +typedef struct { + _supla_int_t Id; + char Target; // SUPLA_TARGET_ + _supla_int_t Command; + _supla_int_t DataType; + unsigned _supla_int_t DataSize; + char Data[SUPLA_CALCFG_DATA_MAXSIZE]; // Last variable in struct! +} TCS_DeviceCalCfgRequest_B; // v. >= 11 + +typedef struct { + _supla_int_t ChannelID; + _supla_int_t Command; + _supla_int_t Result; + unsigned _supla_int_t DataSize; + char Data[SUPLA_CALCFG_DATA_MAXSIZE]; // Last variable in struct! +} TSC_DeviceCalCfgResult; // v. >= 10 + +typedef struct { + _supla_int_t SenderID; + _supla_int_t ChannelNumber; + _supla_int_t Command; + char SuperUserAuthorized; + _supla_int_t DataType; + unsigned _supla_int_t DataSize; + char Data[SUPLA_CALCFG_DATA_MAXSIZE]; // Last variable in struct! +} TSD_DeviceCalCfgRequest; // v. >= 10 + +typedef struct { + _supla_int_t ReceiverID; + _supla_int_t ChannelNumber; + _supla_int_t Command; + _supla_int_t Result; + unsigned _supla_int_t DataSize; + char Data[SUPLA_CALCFG_DATA_MAXSIZE]; // Last variable in struct! +} TDS_DeviceCalCfgResult; // v. >= 10 + +#define RGBW_BRIGHTNESS_ONOFF 0x1 +#define RGBW_COLOR_ONOFF 0x2 + +typedef struct { + char brightness; + char colorBrightness; + char R; + char G; + char B; + char onOff; +} TRGBW_Value; // v. >= 10 + +#define SMARTGLASS_FLAG_HORIZONATAL 0x1 + +typedef struct { + unsigned char sectionCount; // 1 - 16 + unsigned char flags; + unsigned short opaqueSections; +} TSmartglass_Value; + +typedef struct { + unsigned char sec; // 0-59 + unsigned char min; // 0-59 + unsigned char hour; // 0-24 + unsigned char dayOfWeek; // 1 = Sunday, 2 = Monday, …, 7 = Saturday +} TThermostat_Time; // v. >= 11 + +#define THERMOSTAT_SCHEDULE_DAY_SUNDAY 0x01 +#define THERMOSTAT_SCHEDULE_DAY_MONDAY 0x02 +#define THERMOSTAT_SCHEDULE_DAY_TUESDAY 0x04 +#define THERMOSTAT_SCHEDULE_DAY_WEDNESDAY 0x08 +#define THERMOSTAT_SCHEDULE_DAY_THURSDAY 0x10 +#define THERMOSTAT_SCHEDULE_DAY_FRIDAY 0x20 +#define THERMOSTAT_SCHEDULE_DAY_SATURDAY 0x40 +#define THERMOSTAT_SCHEDULE_DAY_ALL 0xFF + +#define THERMOSTAT_SCHEDULE_HOURVALUE_TYPE_TEMPERATURE 0 +#define THERMOSTAT_SCHEDULE_HOURVALUE_TYPE_PROGRAM 1 + +typedef struct { + unsigned char ValueType; // THERMOSTAT_SCHEDULE_HOURVALUE_TYPE_ + char HourValue[7][24]; // 7 days x 24h + // 0 = Sunday, 1 = Monday, …, 6 = Saturday +} TThermostat_Schedule; // v. >= 11 + +typedef struct { + unsigned char ValueType; // THERMOSTAT_SCHEDULE_HOURVALUE_TYPE_ + unsigned char WeekDays; // THERMOSTAT_SCHEDULE_DAY_ + char HourValue[24]; +} TThermostatValueGroup; // v. >= 11 + +typedef struct { + TThermostatValueGroup Group[4]; +} TThermostat_ScheduleCfg; // v. >= 11 + +#define TEMPERATURE_INDEX1 0x0001 +#define TEMPERATURE_INDEX2 0x0002 +#define TEMPERATURE_INDEX3 0x0004 +#define TEMPERATURE_INDEX4 0x0008 +#define TEMPERATURE_INDEX5 0x0010 +#define TEMPERATURE_INDEX6 0x0020 +#define TEMPERATURE_INDEX7 0x0040 +#define TEMPERATURE_INDEX8 0x0080 +#define TEMPERATURE_INDEX9 0x0100 +#define TEMPERATURE_INDEX10 0x0200 + +typedef struct { + _supla_int16_t Index; // BIT0 Temperature[0], BIT1 Temperature[1] etc... + unsigned _supla_int16_t Temperature[10]; +} TThermostatTemperatureCfg; + +// Thermostat configuration commands - ver. >= 11 +#define SUPLA_THERMOSTAT_CMD_TURNON 1 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_AUTO 2 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_COOL 3 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_HEAT 4 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_NORMAL 5 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_ECO 6 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_TURBO 7 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_DRY 8 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_FANONLY 9 +#define SUPLA_THERMOSTAT_CMD_SET_MODE_PURIFIER 10 +#define SUPLA_THERMOSTAT_CMD_SET_SCHEDULE 11 +#define SUPLA_THERMOSTAT_CMD_SET_TIME 12 +#define SUPLA_THERMOSTAT_CMD_SET_TEMPERATURE 13 + +// Thermostat capability flags - ver. >= 11 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_ONOFF 0x0001 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_AUTO 0x0002 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_COOL 0x0004 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_HEAT 0x0008 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_ECO 0x0010 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_DRY 0x0020 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_FANONLY 0x0040 +#define SUPLA_THERMOSTAT_CAP_FLAG_MODE_PURIFIER 0x0080 +#define SUPLA_THERMOSTAT_CAP_FLAG_SCHEDULE 0x0100 + +// Thermostat value flags - ver. >= 11 +#define SUPLA_THERMOSTAT_VALUE_FLAG_ON 0x0001 +#define SUPLA_THERMOSTAT_VALUE_FLAG_AUTO_MODE 0x0002 +#define SUPLA_THERMOSTAT_VALUE_FLAG_COOL_MODE 0x0004 +#define SUPLA_THERMOSTAT_VALUE_FLAG_HEAT_MODE 0x0008 +#define SUPLA_THERMOSTAT_VALUE_FLAG_ECO_MODE 0x0010 +#define SUPLA_THERMOSTAT_VALUE_FLAG_DRY_MODE 0x0020 +#define SUPLA_THERMOSTAT_VALUE_FLAG_FANONLY_MODE 0x0040 +#define SUPLA_THERMOSTAT_VALUE_FLAG_PURIFIER_MODE 0x0080 + +// Thermostat fields - ver. >= 11 +#define THERMOSTAT_FIELD_MeasuredTemperatures 0x01 +#define THERMOSTAT_FIELD_PresetTemperatures 0x02 +#define THERMOSTAT_FIELD_Flags 0x04 +#define THERMOSTAT_FIELD_Values 0x08 +#define THERMOSTAT_FIELD_Time 0x10 +#define THERMOSTAT_FIELD_Schedule 0x20 + +typedef struct { + unsigned char Fields; + _supla_int16_t MeasuredTemperature[10]; // * 0.01 + _supla_int16_t PresetTemperature[10]; // * 0.01 + _supla_int16_t Flags[8]; + _supla_int16_t Values[8]; + TThermostat_Time Time; + TThermostat_Schedule Schedule; // 7 days x 24h (4bit/hour) +} TThermostat_ExtendedValue; // v. >= 11 + +typedef struct { + unsigned char IsOn; + unsigned char Flags; + _supla_int16_t MeasuredTemperature; // * 0.01 + _supla_int16_t PresetTemperature; // * 0.01 +} TThermostat_Value; // v. >= 11 + +typedef struct { + unsigned _supla_int16_t year; + unsigned char month; + unsigned char day; + unsigned char dayOfWeek; // 1 = Sunday, 2 = Monday, …, 7 = Saturday + unsigned char hour; + unsigned char min; + unsigned char sec; + unsigned _supla_int_t + timezoneSize; // including the terminating null byte ('\0') + char timezone[SUPLA_TIMEZONE_MAXSIZE]; // Last variable in struct! +} TSDC_UserLocalTimeResult; + +typedef struct { + _supla_int_t SenderID; + union { + _supla_int_t ChannelID; // Client -> Server + unsigned char ChannelNumber; // Server -> Device + }; +} TCSD_ChannelStateRequest; // v. >= 12 Client -> Server -> Device + +#define SUPLA_CHANNELSTATE_FIELD_IPV4 0x0001 +#define SUPLA_CHANNELSTATE_FIELD_MAC 0x0002 +#define SUPLA_CHANNELSTATE_FIELD_BATTERYLEVEL 0x0004 +#define SUPLA_CHANNELSTATE_FIELD_BATTERYPOWERED 0x0008 +#define SUPLA_CHANNELSTATE_FIELD_WIFIRSSI 0x0010 +#define SUPLA_CHANNELSTATE_FIELD_WIFISIGNALSTRENGTH 0x0020 +#define SUPLA_CHANNELSTATE_FIELD_BRIDGENODESIGNALSTRENGTH 0x0040 +#define SUPLA_CHANNELSTATE_FIELD_UPTIME 0x0080 +#define SUPLA_CHANNELSTATE_FIELD_CONNECTIONUPTIME 0x0100 +#define SUPLA_CHANNELSTATE_FIELD_BATTERYHEALTH 0x0200 +#define SUPLA_CHANNELSTATE_FIELD_BRIDGENODEONLINE 0x0400 +#define SUPLA_CHANNELSTATE_FIELD_LASTCONNECTIONRESETCAUSE 0x0800 +#define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCELIFESPAN 0x1000 +#define SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCEOPERATINGTIME 0x2000 + +#define SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN 0 +#define SUPLA_LASTCONNECTIONRESETCAUSE_ACTIVITY_TIMEOUT 1 +#define SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST 2 +#define SUPLA_LASTCONNECTIONRESETCAUSE_SERVER_CONNECTION_LOST 3 + +typedef struct { + _supla_int_t ReceiverID; // Not used in extended values + union { + // Not used in extended values + _supla_int_t ChannelID; // Server -> Client + unsigned char ChannelNumber; // Device -> Server + }; + _supla_int_t Fields; + _supla_int_t defaultIconField; // SUPLA_CHANNELSTATE_FIELD_* + unsigned _supla_int_t IPv4; + unsigned char MAC[6]; + unsigned char BatteryLevel; // 0 - 100% + unsigned char BatteryPowered; // true(1)/false(0) + char WiFiRSSI; + unsigned char WiFiSignalStrength; // 0 - 100% + unsigned char BridgeNodeOnline; // 0/1 + unsigned char BridgeNodeSignalStrength; // 0 - 100% + unsigned _supla_int_t Uptime; // sec. + unsigned _supla_int_t ConnectionUptime; // sec. + unsigned char BatteryHealth; + unsigned char LastConnectionResetCause; // SUPLA_LASTCONNECTIONRESETCAUSE_* + unsigned short LightSourceLifespan; // 0 - 65535 hours + union { + short LightSourceLifespanLeft; // -327,67 - 100.00% LightSourceLifespan * + // 0.01 + _supla_int_t LightSourceOperatingTime; // -3932100sec. - 3932100sec. + }; + char EmptySpace[2]; // Empty space for future use +} TDSC_ChannelState; // v. >= 12 Device -> Server -> Client + +#define TChannelState_ExtendedValue TDSC_ChannelState + +typedef struct { + _supla_int_t ChannelID; +} TCS_ChannelBasicCfgRequest; // v. >= 12 + +typedef struct { + union { + // Remaining time to turn off + unsigned _supla_int_t RemainingTimeMs; + unsigned _supla_int_t RemainingTimeTs; // Unix timestamp - Filled by server + }; + + unsigned char TargetValue[SUPLA_CHANNELVALUE_SIZE]; + + _supla_int_t SenderID; + unsigned _supla_int_t + SenderNameSize; // including the terminating null byte ('\0') + char SenderName[SUPLA_SENDER_NAME_MAXSIZE]; // Last variable in struct! + // UTF8 | Filled by server +} TTimerState_ExtendedValue; + +typedef struct { + TChannelState_ExtendedValue Channel; + TTimerState_ExtendedValue Timer; // Last variable in struct! +} TChannelAndTimerState_ExtendedValue; + +typedef struct { + char DeviceName[SUPLA_DEVICE_NAME_MAXSIZE]; // UTF8 + char DeviceSoftVer[SUPLA_SOFTVER_MAXSIZE]; + _supla_int_t DeviceID; + _supla_int_t DeviceFlags; + _supla_int16_t ManufacturerID; + _supla_int16_t ProductID; + + _supla_int_t ID; + unsigned char Number; + _supla_int_t Type; + _supla_int_t Func; + _supla_int_t FuncList; + + unsigned _supla_int_t ChannelFlags; + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_ChannelBasicCfg; // v. >= 12 + +typedef struct { + _supla_int_t ChannelID; + _supla_int_t Func; +} TCS_SetChannelFunction; // v. >= 12 + +typedef struct { + _supla_int_t ChannelID; + _supla_int_t Func; + unsigned char ResultCode; +} TSC_SetChannelFunctionResult; // v. >= 12 + +typedef struct { + _supla_int_t ChannelID; + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TCS_SetChannelCaption; // v. >= 12 + +typedef struct { + _supla_int_t ChannelID; + unsigned char ResultCode; + unsigned _supla_int_t + CaptionSize; // including the terminating null byte ('\0') + char Caption[SUPLA_CHANNEL_CAPTION_MAXSIZE]; // Last variable in struct! +} TSC_SetChannelCaptionResult; // v. >= 12 + +typedef struct { + unsigned char ResultCode; +} TSC_ClientsReconnectRequestResult; // v. >= 12 + +typedef struct { + // Disabled: 0 + // Ignore: <0 + _supla_int_t IODeviceRegistrationTimeSec; + _supla_int_t ClientRegistrationTimeSec; +} TCS_SetRegistrationEnabled; // v. >= 12 + +typedef struct { + unsigned char ResultCode; +} TSC_SetRegistrationEnabledResult; // v. >= 12 + +typedef struct { + int DeviceID; +} TCS_DeviceReconnectRequest; // v. >= 12 + +typedef struct { + int DeviceID; + unsigned char ResultCode; +} TSC_DeviceReconnectRequestResult; // v. >= 12 + +typedef struct { + // server -> device + + unsigned char ChannelCount; + _supla_int_t Functions[SUPLA_CHANNELMAXCOUNT]; // Last variable in struct! + // Functions[ChannelNumber] +} TSD_ChannelFunctions; // ver. >= 12 + +#define SUPLA_VALVE_FLAG_FLOODING 0x1 +#define SUPLA_VALVE_FLAG_MANUALLY_CLOSED 0x2 + +typedef struct { + union { + unsigned char closed; + unsigned char closed_percent; + }; + + unsigned char flags; +} TValve_Value; + +#pragma pack(pop) + +void *sproto_init(void); +void sproto_free(void *spd_ptr); + +#ifndef SPROTO_WITHOUT_OUT_BUFFER +char sproto_out_buffer_append(void *spd_ptr, TSuplaDataPacket *sdp); +unsigned _supla_int_t sproto_pop_out_data(void *spd_ptr, char *buffer, + unsigned _supla_int_t buffer_size); +#endif /*SPROTO_WITHOUT_OUT_BUFFER*/ +char sproto_out_dataexists(void *spd_ptr); +char sproto_in_buffer_append(void *spd_ptr, char *data, + unsigned _supla_int_t data_size); + +char sproto_pop_in_sdp(void *spd_ptr, TSuplaDataPacket *sdp); +char sproto_in_dataexists(void *spd_ptr); + +unsigned char sproto_get_version(void *spd_ptr); +void sproto_set_version(void *spd_ptr, unsigned char version); +void sproto_sdp_init(void *spd_ptr, TSuplaDataPacket *sdp); +char sproto_set_data(TSuplaDataPacket *sdp, char *data, + unsigned _supla_int_t data_size, + unsigned _supla_int_t call_type); +TSuplaDataPacket *sproto_sdp_malloc(void *spd_ptr); +void sproto_sdp_free(TSuplaDataPacket *sdp); + +void sproto_log_summary(void *spd_ptr); +void sproto_buffer_dump(void *spd_ptr, unsigned char in); + +#ifdef __cplusplus +} +#endif + +#endif /* supla_proto_H_ */ diff --git a/lib/SuplaDevice/src/supla-common/srpc.c b/lib/SuplaDevice/src/supla-common/srpc.c new file mode 100644 index 00000000..80f94ffd --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/srpc.c @@ -0,0 +1,2566 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "srpc.h" +#include +#include +#include "lck.h" +#include "log.h" +#include "proto.h" + +#if defined(ESP8266) || defined(ESP32) + +#include +#if !defined(ESP32) +#include +#endif + +#if defined(ARDUINO_ARCH_ESP8266) +#include +#define __EH_DISABLED +#elif defined(ARDUINO_ARCH_ESP32) +#define __EH_DISABLED +#else +#include +#include "espmissingincludes.h" +#endif + +#ifndef SRPC_BUFFER_SIZE +#define SRPC_BUFFER_SIZE 1024 +#endif /*SRPC_BUFFER_SIZE*/ + +#ifndef SRPC_QUEUE_SIZE +#define SRPC_QUEUE_SIZE 2 +#endif /*SRPC_QUEUE_SIZE*/ + +#ifndef SRPC_QUEUE_MIN_ALLOC_COUNT +#define SRPC_QUEUE_MIN_ALLOC_COUNT 2 +#endif /* SRPC_QUEUE_MIN_ALLOC_COUNT */ + +#elif defined(__AVR__) + +#define SRPC_BUFFER_SIZE 32 +#define SRPC_QUEUE_SIZE 1 +#define SRPC_QUEUE_MIN_ALLOC_COUNT 1 +#define __EH_DISABLED + +#else +#include +#endif + +#ifndef SRPC_BUFFER_SIZE +#define SRPC_BUFFER_SIZE 32768 +#endif /*SRPC_BUFFER_SIZE*/ + +#ifndef SRPC_QUEUE_SIZE +#define SRPC_QUEUE_SIZE 10 +#endif /*SRPC_QUEUE_SIZE*/ + +#ifndef SRPC_QUEUE_MIN_ALLOC_COUNT +#define SRPC_QUEUE_MIN_ALLOC_COUNT 0 +#endif /*SRPC_QUEUE_MIN_ALLOC_COUNT*/ + +typedef struct { + unsigned char item_count; + unsigned char alloc_count; + + TSuplaDataPacket *item[SRPC_QUEUE_SIZE]; +} Tsrpc_Queue; + +typedef struct { + void *proto; + TsrpcParams params; + + TSuplaDataPacket sdp; + +#ifndef SRPC_WITHOUT_IN_QUEUE + Tsrpc_Queue in_queue; +#endif /*SRPC_WITHOUT_IN_QUEUE*/ + +#ifndef SRPC_WITHOUT_OUT_QUEUE + Tsrpc_Queue out_queue; +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ + + void *lck; +} Tsrpc; + +void SRPC_ICACHE_FLASH srpc_params_init(TsrpcParams *params) { + memset(params, 0, sizeof(TsrpcParams)); +} + +void *SRPC_ICACHE_FLASH srpc_init(TsrpcParams *params) { + Tsrpc *srpc = (Tsrpc *)malloc(sizeof(Tsrpc)); + + if (srpc == NULL) return NULL; + + memset(srpc, 0, sizeof(Tsrpc)); + srpc->proto = sproto_init(); + +#ifndef ESP8266 +#ifndef ESP32 +#ifndef __AVR__ + assert(params != 0); + assert(params->data_read != 0); + assert(params->data_write != 0); +#endif +#endif +#endif + + memcpy(&srpc->params, params, sizeof(TsrpcParams)); + + srpc->lck = lck_init(); + + return srpc; +} + +void SRPC_ICACHE_FLASH srpc_queue_free(Tsrpc_Queue *queue) { + _supla_int_t a; + for (a = 0; a < SRPC_QUEUE_SIZE; a++) { + if (queue->item[a] != NULL) { + free(queue->item[a]); + } + } + + queue->item_count = 0; + queue->alloc_count = 0; +} + +void SRPC_ICACHE_FLASH srpc_free(void *_srpc) { + if (_srpc) { + Tsrpc *srpc = (Tsrpc *)_srpc; + + sproto_free(srpc->proto); + +#ifndef SRPC_WITHOUT_IN_QUEUE + srpc_queue_free(&srpc->in_queue); +#endif /*SRPC_WITHOUT_IN_QUEUE*/ + +#ifndef SRPC_WITHOUT_OUT_QUEUE + srpc_queue_free(&srpc->out_queue); +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ + lck_free(srpc->lck); + + free(srpc); + } +} + +char SRPC_ICACHE_FLASH srpc_queue_push(Tsrpc_Queue *queue, + TSuplaDataPacket *sdp) { + if (queue->item_count >= SRPC_QUEUE_SIZE) { + return SUPLA_RESULT_FALSE; + } + + if (queue->item[queue->item_count] == NULL) { + queue->item[queue->item_count] = + (TSuplaDataPacket *)malloc(sizeof(TSuplaDataPacket)); + } + + if (queue->item[queue->item_count] == NULL) { + return SUPLA_RESULT_FALSE; + } else { + queue->alloc_count++; + } + + memcpy(queue->item[queue->item_count], sdp, sizeof(TSuplaDataPacket)); + queue->item_count++; + + return SUPLA_RESULT_TRUE; +} + +char SRPC_ICACHE_FLASH srpc_queue_pop(Tsrpc_Queue *queue, TSuplaDataPacket *sdp, + unsigned _supla_int_t rr_id) { + _supla_int_t a, b; + + for (a = 0; a < queue->item_count; a++) + if (rr_id == 0 || queue->item[a]->rr_id == rr_id) { + memcpy(sdp, queue->item[a], sizeof(TSuplaDataPacket)); + + if (queue->alloc_count > SRPC_QUEUE_MIN_ALLOC_COUNT) { + queue->alloc_count--; + free(queue->item[a]); + queue->item[a] = NULL; + } + + TSuplaDataPacket *item = queue->item[a]; + + for (b = a; b < queue->item_count - 1; b++) { + queue->item[b] = queue->item[b + 1]; + } + + queue->item_count--; + queue->item[queue->item_count] = item; + + return SUPLA_RESULT_TRUE; + } + + return SUPLA_RESULT_FALSE; +} + +char SRPC_ICACHE_FLASH srpc_in_queue_pop(Tsrpc *srpc, TSuplaDataPacket *sdp, + unsigned _supla_int_t rr_id) { +#ifdef SRPC_WITHOUT_IN_QUEUE + return 1; +#else + return srpc_queue_pop(&srpc->in_queue, sdp, rr_id); +#endif /*SRPC_WITHOUT_IN_QUEUE*/ +} + +#ifndef SRPC_WITHOUT_IN_QUEUE +char SRPC_ICACHE_FLASH srpc_in_queue_push(Tsrpc *srpc, TSuplaDataPacket *sdp) { + return srpc_queue_push(&srpc->in_queue, sdp); +} +#endif /*SRPC_WITHOUT_IN_QUEUE*/ + +char SRPC_ICACHE_FLASH srpc_out_queue_push(Tsrpc *srpc, TSuplaDataPacket *sdp) { +#ifdef SRPC_WITHOUT_OUT_QUEUE + unsigned _supla_int_t data_size = sizeof(TSuplaDataPacket); + if (sdp->data_size < SUPLA_MAX_DATA_SIZE) { + data_size -= SUPLA_MAX_DATA_SIZE - sdp->data_size; + } + srpc->params.data_write((char *)sdp, data_size, srpc->params.user_params); + srpc->params.data_write(sproto_tag, SUPLA_TAG_SIZE, srpc->params.user_params); + return 1; +#else + return srpc_queue_push(&srpc->out_queue, sdp); +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ +} + +#ifndef SRPC_WITHOUT_OUT_QUEUE +char SRPC_ICACHE_FLASH srpc_out_queue_pop(Tsrpc *srpc, TSuplaDataPacket *sdp, + unsigned _supla_int_t rr_id) { + return srpc_queue_pop(&srpc->out_queue, sdp, rr_id); +} +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ + +unsigned char SRPC_ICACHE_FLASH srpc_out_queue_item_count(void *srpc) { +#ifdef SRPC_WITHOUT_OUT_QUEUE + return 0; +#else + return ((Tsrpc *)srpc)->out_queue.item_count; +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ +} + +char SRPC_ICACHE_FLASH srpc_input_dataexists(void *_srpc) { + int result = SUPLA_RESULT_FALSE; + Tsrpc *srpc = (Tsrpc *)_srpc; + lck_lock(srpc->lck); + result = sproto_in_dataexists(srpc->proto); + return lck_unlock_r(srpc->lck, result); +} + +char SRPC_ICACHE_FLASH srpc_output_dataexists(void *_srpc) { + int result = SUPLA_RESULT_FALSE; + Tsrpc *srpc = (Tsrpc *)_srpc; + lck_lock(srpc->lck); + result = sproto_out_dataexists(srpc->proto); + return lck_unlock_r(srpc->lck, result); +} + +char SRPC_ICACHE_FLASH srpc_iterate(void *_srpc) { + Tsrpc *srpc = (Tsrpc *)_srpc; + char data_buffer[SRPC_BUFFER_SIZE]; + char result; + unsigned char version; +#ifndef __EH_DISABLED + unsigned char raise_event = 0; +#endif /*__EH_DISABLED*/ + + // --------- IN --------------- + _supla_int_t data_size = srpc->params.data_read(data_buffer, SRPC_BUFFER_SIZE, + srpc->params.user_params); + + if (data_size == 0) return SUPLA_RESULT_FALSE; + + lck_lock(srpc->lck); + + if (data_size > 0 && + SUPLA_RESULT_TRUE != (result = sproto_in_buffer_append( + srpc->proto, data_buffer, data_size))) { + supla_log(LOG_DEBUG, "sproto_in_buffer_append: %i, datasize: %i", result, + data_size); + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); + } + + if (SUPLA_RESULT_TRUE == + (result = sproto_pop_in_sdp(srpc->proto, &srpc->sdp))) { +#ifndef __EH_DISABLED + raise_event = sproto_in_dataexists(srpc->proto) == 1 ? 1 : 0; +#endif /*__EH_DISABLED*/ + +#ifdef SRPC_WITHOUT_IN_QUEUE + if (srpc->params.on_remote_call_received) { + lck_unlock(srpc->lck); + srpc->params.on_remote_call_received( + srpc, srpc->sdp.rr_id, srpc->sdp.call_type, srpc->params.user_params, + srpc->sdp.version); + lck_lock(srpc->lck); + } +#else + if (SUPLA_RESULT_TRUE == srpc_in_queue_push(srpc, &srpc->sdp)) { + if (srpc->params.on_remote_call_received) { + lck_unlock(srpc->lck); + srpc->params.on_remote_call_received( + srpc, srpc->sdp.rr_id, srpc->sdp.call_type, + srpc->params.user_params, srpc->sdp.version); + lck_lock(srpc->lck); + } + + } else { + supla_log(LOG_DEBUG, "ssrpc_in_queue_push error"); + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); + } +#endif /*SRPC_WITHOUT_IN_QUEUE*/ + + } else if (result != SUPLA_RESULT_FALSE) { + if (result == (char)SUPLA_RESULT_VERSION_ERROR) { + if (srpc->params.on_version_error) { + version = srpc->sdp.version; + lck_unlock(srpc->lck); + + srpc->params.on_version_error(srpc, version, srpc->params.user_params); + return SUPLA_RESULT_FALSE; + } + } else { + supla_log(LOG_DEBUG, "sproto_pop_in_sdp error: %i", result); + } + + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); + } + + // --------- OUT --------------- +#ifndef SRPC_WITHOUT_OUT_QUEUE + if (srpc_out_queue_pop(srpc, &srpc->sdp, 0) == SUPLA_RESULT_TRUE && + SUPLA_RESULT_TRUE != + (result = sproto_out_buffer_append(srpc->proto, &srpc->sdp)) && + result != SUPLA_RESULT_FALSE) { + supla_log(LOG_DEBUG, "sproto_out_buffer_append error: %i", result); + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); + } + + data_size = sproto_pop_out_data(srpc->proto, data_buffer, SRPC_BUFFER_SIZE); + + if (data_size != 0) { + lck_unlock(srpc->lck); + srpc->params.data_write(data_buffer, data_size, srpc->params.user_params); + lck_lock(srpc->lck); + } + +#ifndef __EH_DISABLED + if (srpc->params.eh != 0 && + (sproto_out_dataexists(srpc->proto) == 1 || + srpc_out_queue_item_count(srpc) || raise_event)) { + eh_raise_event(srpc->params.eh); + } +#endif /*__EH_DISABLED*/ + +#endif /*SRPC_WITHOUT_OUT_QUEUE*/ + return lck_unlock_r(srpc->lck, SUPLA_RESULT_TRUE); +} + +typedef unsigned _supla_int_t (*_func_srpc_pack_get_caption_size)( + void *pack, _supla_int_t idx); +typedef void *(*_func_srpc_pack_get_item_ptr)(void *pack, _supla_int_t idx); +typedef _supla_int_t (*_func_srpc_pack_get_pack_count)(void *pack); +typedef void (*_func_srpc_pack_set_pack_count)(void *pack, _supla_int_t count, + unsigned char increment); +typedef unsigned _supla_int_t (*_func_srpc_pack_get_item_caption_size)( + void *item); +typedef unsigned _supla_int_t (*_func_srpc_pack_get_caption_size)( + void *pack, _supla_int_t idx); + +void SRPC_ICACHE_FLASH srpc_getpack( + Tsrpc *srpc, TsrpcReceivedData *rd, unsigned _supla_int_t pack_sizeof, + unsigned _supla_int_t item_sizeof, unsigned _supla_int_t pack_max_count, + unsigned _supla_int_t caption_max_size, + _func_srpc_pack_get_pack_count pack_get_count, + _func_srpc_pack_set_pack_count pack_set_count, + _func_srpc_pack_get_item_ptr get_item_ptr, + _func_srpc_pack_get_item_caption_size get_item_caption_size) { + _supla_int_t header_size = pack_sizeof - (item_sizeof * pack_max_count); + _supla_int_t c_header_size = item_sizeof - caption_max_size; + _supla_int_t a, count, size, offset, pack_size; + void *pack = NULL; + + if (srpc->sdp.data_size < header_size || srpc->sdp.data_size > pack_sizeof) { + return; + } + + count = pack_get_count(srpc->sdp.data); + + if (count < 0 || count > pack_max_count) { + return; + } + + pack_size = header_size + (item_sizeof * count); + pack = (TSC_SuplaChannelPack *)malloc(pack_size); + + if (pack == NULL) return; + + memset(pack, 0, pack_size); + memcpy(pack, srpc->sdp.data, header_size); + + offset = header_size; + pack_set_count(pack, 0, 0); + + for (a = 0; a < count; a++) + if (srpc->sdp.data_size - offset >= c_header_size) { + size = get_item_caption_size(&srpc->sdp.data[offset]); + + if (size >= 0 && size <= caption_max_size && + srpc->sdp.data_size - offset >= c_header_size + size) { + memcpy(get_item_ptr(pack, a), &srpc->sdp.data[offset], + c_header_size + size); + offset += c_header_size + size; + pack_set_count(pack, 1, 1); + + } else { + break; + } + } + + if (count == pack_get_count(pack)) { + srpc->sdp.data_size = 0; + // dcs_ping is 1st variable in union + rd->data.dcs_ping = pack; + + } else { + free(pack); + } +} + +void *srpc_channelpack_get_item_ptr(void *pack, _supla_int_t idx) { + return &((TSC_SuplaChannelPack *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_channelpack_get_pack_count(void *pack) { + return ((TSC_SuplaChannelPack *)pack)->count; +} + +void srpc_channelpack_set_pack_count(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaChannelPack *)pack)->count = count; + } else { + ((TSC_SuplaChannelPack *)pack)->count += count; + } +} + +unsigned _supla_int_t srpc_channelpack_get_item_caption_size(void *item) { + return ((TSC_SuplaChannel *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getchannelpack(Tsrpc *srpc, TsrpcReceivedData *rd) { + srpc_getpack(srpc, rd, sizeof(TSC_SuplaChannelPack), sizeof(TSC_SuplaChannel), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + &srpc_channelpack_get_pack_count, + &srpc_channelpack_set_pack_count, &srpc_channelpack_get_item_ptr, + &srpc_channelpack_get_item_caption_size); +} + +void *srpc_channelpack_get_item_ptr_b(void *pack, _supla_int_t idx) { + return &((TSC_SuplaChannelPack_B *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_channelpack_get_pack_count_b(void *pack) { + return ((TSC_SuplaChannelPack_B *)pack)->count; +} + +void srpc_channelpack_set_pack_count_b(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaChannelPack_B *)pack)->count = count; + } else { + ((TSC_SuplaChannelPack_B *)pack)->count += count; + } +} + +unsigned _supla_int_t srpc_channelpack_get_item_caption_size_b(void *item) { + return ((TSC_SuplaChannel_B *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getchannelpack_b(Tsrpc *srpc, + TsrpcReceivedData *rd) { + srpc_getpack( + srpc, rd, sizeof(TSC_SuplaChannelPack_B), sizeof(TSC_SuplaChannel_B), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + &srpc_channelpack_get_pack_count_b, &srpc_channelpack_set_pack_count_b, + &srpc_channelpack_get_item_ptr_b, + &srpc_channelpack_get_item_caption_size_b); +} + +void *srpc_channelpack_get_item_ptr_c(void *pack, _supla_int_t idx) { + return &((TSC_SuplaChannelPack_C *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_channelpack_get_pack_count_c(void *pack) { + return ((TSC_SuplaChannelPack_C *)pack)->count; +} + +void srpc_channelpack_set_pack_count_c(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaChannelPack_C *)pack)->count = count; + } else { + ((TSC_SuplaChannelPack_C *)pack)->count += count; + } +} + +unsigned _supla_int_t srpc_channelpack_get_item_caption_size_c(void *item) { + return ((TSC_SuplaChannel_C *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getchannelpack_c(Tsrpc *srpc, + TsrpcReceivedData *rd) { + srpc_getpack( + srpc, rd, sizeof(TSC_SuplaChannelPack_C), sizeof(TSC_SuplaChannel_C), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + &srpc_channelpack_get_pack_count_c, &srpc_channelpack_set_pack_count_c, + &srpc_channelpack_get_item_ptr_c, + &srpc_channelpack_get_item_caption_size_c); +} + +void *srpc_channelgroup_pack_get_item_ptr(void *pack, _supla_int_t idx) { + return &((TSC_SuplaChannelGroupPack *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_channelgroup_pack_get_pack_count(void *pack) { + return ((TSC_SuplaChannelGroupPack *)pack)->count; +} + +void srpc_channelgroup_pack_set_pack_count(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaChannelGroupPack *)pack)->count = count; + } else { + ((TSC_SuplaChannelGroupPack *)pack)->count += count; + } +} + +unsigned _supla_int_t srpc_channelgroup_pack_get_item_caption_size(void *item) { + return ((TSC_SuplaChannelGroup *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getchannelgroup_pack(Tsrpc *srpc, + TsrpcReceivedData *rd) { + srpc_getpack(srpc, rd, sizeof(TSC_SuplaChannelGroupPack), + sizeof(TSC_SuplaChannelGroup), SUPLA_CHANNELGROUP_PACK_MAXCOUNT, + SUPLA_CHANNELGROUP_CAPTION_MAXSIZE, + &srpc_channelgroup_pack_get_pack_count, + &srpc_channelgroup_pack_set_pack_count, + &srpc_channelgroup_pack_get_item_ptr, + &srpc_channelgroup_pack_get_item_caption_size); +} + +void *srpc_channelgroup_pack_b_get_item_ptr(void *pack, _supla_int_t idx) { + return &((TSC_SuplaChannelGroupPack_B *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_channelgroup_pack_b_get_pack_count(void *pack) { + return ((TSC_SuplaChannelGroupPack_B *)pack)->count; +} + +void srpc_channelgroup_pack_b_set_pack_count(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaChannelGroupPack_B *)pack)->count = count; + } else { + ((TSC_SuplaChannelGroupPack_B *)pack)->count += count; + } +} + +unsigned _supla_int_t +srpc_channelgroup_pack_b_get_item_caption_size(void *item) { + return ((TSC_SuplaChannelGroup_B *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getchannelgroup_pack_b(Tsrpc *srpc, + TsrpcReceivedData *rd) { + srpc_getpack(srpc, rd, sizeof(TSC_SuplaChannelGroupPack_B), + sizeof(TSC_SuplaChannelGroup_B), + SUPLA_CHANNELGROUP_PACK_MAXCOUNT, + SUPLA_CHANNELGROUP_CAPTION_MAXSIZE, + &srpc_channelgroup_pack_b_get_pack_count, + &srpc_channelgroup_pack_b_set_pack_count, + &srpc_channelgroup_pack_b_get_item_ptr, + &srpc_channelgroup_pack_b_get_item_caption_size); +} + +void *srpc_locationpack_get_item_ptr(void *pack, _supla_int_t idx) { + return &((TSC_SuplaLocationPack *)pack)->items[idx]; // NOLINT +} + +_supla_int_t srpc_locationpack_get_pack_count(void *pack) { + return ((TSC_SuplaLocationPack *)pack)->count; +} + +void srpc_locationpack_set_pack_count(void *pack, _supla_int_t count, + unsigned char increment) { + if (increment == 0) { + ((TSC_SuplaLocationPack *)pack)->count = count; + } else { + ((TSC_SuplaLocationPack *)pack)->count += count; + } +} + +unsigned _supla_int_t srpc_locationpack_get_item_caption_size(void *item) { + return ((TSC_SuplaLocation *)item)->CaptionSize; +} + +void SRPC_ICACHE_FLASH srpc_getlocationpack(Tsrpc *srpc, + TsrpcReceivedData *rd) { + srpc_getpack( + srpc, rd, sizeof(TSC_SuplaLocationPack), sizeof(TSC_SuplaLocation), + SUPLA_LOCATIONPACK_MAXCOUNT, SUPLA_LOCATION_CAPTION_MAXSIZE, + &srpc_locationpack_get_pack_count, &srpc_locationpack_set_pack_count, + &srpc_locationpack_get_item_ptr, + &srpc_locationpack_get_item_caption_size); +} + +char SRPC_ICACHE_FLASH srpc_getdata(void *_srpc, TsrpcReceivedData *rd, + unsigned _supla_int_t rr_id) { + Tsrpc *srpc = (Tsrpc *)_srpc; + char call_with_no_data = 0; + rd->call_type = 0; + + lck_lock(srpc->lck); + + if (SUPLA_RESULT_TRUE == srpc_in_queue_pop(srpc, &srpc->sdp, rr_id)) { + rd->call_type = srpc->sdp.call_type; + rd->rr_id = srpc->sdp.rr_id; + + // first one + rd->data.dcs_ping = NULL; + + switch (srpc->sdp.call_type) { + case SUPLA_DCS_CALL_GETVERSION: + case SUPLA_CS_CALL_GET_NEXT: + case SUPLA_DCS_CALL_GET_REGISTRATION_ENABLED: + call_with_no_data = 1; + break; + + case SUPLA_SDC_CALL_GETVERSION_RESULT: + + if (srpc->sdp.data_size == sizeof(TSDC_SuplaGetVersionResult)) + rd->data.sdc_getversion_result = (TSDC_SuplaGetVersionResult *)malloc( + sizeof(TSDC_SuplaGetVersionResult)); + + break; + + case SUPLA_SDC_CALL_VERSIONERROR: + + if (srpc->sdp.data_size == sizeof(TSDC_SuplaVersionError)) + rd->data.sdc_version_error = + (TSDC_SuplaVersionError *)malloc(sizeof(TSDC_SuplaVersionError)); + + break; + + case SUPLA_DCS_CALL_PING_SERVER: + + if (srpc->sdp.data_size == sizeof(TDCS_SuplaPingServer) || + srpc->sdp.data_size == sizeof(TDCS_SuplaPingServer_COMPAT)) { + rd->data.dcs_ping = + (TDCS_SuplaPingServer *)malloc(sizeof(TDCS_SuplaPingServer)); + +#ifndef __AVR__ + if (srpc->sdp.data_size == sizeof(TDCS_SuplaPingServer_COMPAT)) { + TDCS_SuplaPingServer_COMPAT *compat = + (TDCS_SuplaPingServer_COMPAT *)srpc->sdp.data; + + rd->data.dcs_ping->now.tv_sec = compat->now.tv_sec; + rd->data.dcs_ping->now.tv_usec = compat->now.tv_usec; + call_with_no_data = 1; + } +#endif + } + break; + + case SUPLA_SDC_CALL_PING_SERVER_RESULT: + + if (srpc->sdp.data_size == sizeof(TSDC_SuplaPingServerResult)) + rd->data.sdc_ping_result = (TSDC_SuplaPingServerResult *)malloc( + sizeof(TSDC_SuplaPingServerResult)); + + break; + + case SUPLA_DCS_CALL_SET_ACTIVITY_TIMEOUT: + + if (srpc->sdp.data_size == sizeof(TDCS_SuplaSetActivityTimeout)) + rd->data.dcs_set_activity_timeout = + (TDCS_SuplaSetActivityTimeout *)malloc( + sizeof(TDCS_SuplaSetActivityTimeout)); + + break; + + case SUPLA_SDC_CALL_SET_ACTIVITY_TIMEOUT_RESULT: + + if (srpc->sdp.data_size == sizeof(TSDC_SuplaSetActivityTimeoutResult)) + rd->data.sdc_set_activity_timeout_result = + (TSDC_SuplaSetActivityTimeoutResult *)malloc( + sizeof(TSDC_SuplaSetActivityTimeoutResult)); + + break; + + case SUPLA_SDC_CALL_GET_REGISTRATION_ENABLED_RESULT: + + if (srpc->sdp.data_size == sizeof(TSDC_RegistrationEnabled)) + rd->data.sdc_reg_enabled = (TSDC_RegistrationEnabled *)malloc( + sizeof(TSDC_RegistrationEnabled)); + + break; + case SUPLA_DCS_CALL_GET_USER_LOCALTIME: + call_with_no_data = 1; + break; + case SUPLA_DCS_CALL_GET_USER_LOCALTIME_RESULT: + if (srpc->sdp.data_size <= sizeof(TSDC_UserLocalTimeResult) && + srpc->sdp.data_size >= + (sizeof(TSDC_UserLocalTimeResult) - SUPLA_TIMEZONE_MAXSIZE)) { + rd->data.sdc_user_localtime_result = + (TSDC_UserLocalTimeResult *)malloc( + sizeof(TSDC_UserLocalTimeResult)); + } + + break; + + case SUPLA_CSD_CALL_GET_CHANNEL_STATE: + if (srpc->sdp.data_size == sizeof(TCSD_ChannelStateRequest)) + rd->data.csd_channel_state_request = + (TCSD_ChannelStateRequest *)malloc( + sizeof(TCSD_ChannelStateRequest)); + break; + case SUPLA_DSC_CALL_CHANNEL_STATE_RESULT: + if (srpc->sdp.data_size == sizeof(TDSC_ChannelState)) + rd->data.dsc_channel_state = + (TDSC_ChannelState *)malloc(sizeof(TDSC_ChannelState)); + break; + +#ifndef SRPC_EXCLUDE_DEVICE + case SUPLA_DS_CALL_REGISTER_DEVICE: + + if (srpc->sdp.data_size >= + (sizeof(TDS_SuplaRegisterDevice) - + (sizeof(TDS_SuplaDeviceChannel) * SUPLA_CHANNELMAXCOUNT)) && + srpc->sdp.data_size <= sizeof(TDS_SuplaRegisterDevice)) { + rd->data.ds_register_device = (TDS_SuplaRegisterDevice *)malloc( + sizeof(TDS_SuplaRegisterDevice)); + } + + break; + + case SUPLA_DS_CALL_REGISTER_DEVICE_B: // ver. >= 2 + + if (srpc->sdp.data_size >= + (sizeof(TDS_SuplaRegisterDevice_B) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT)) && + srpc->sdp.data_size <= sizeof(TDS_SuplaRegisterDevice_B)) { + rd->data.ds_register_device_b = (TDS_SuplaRegisterDevice_B *)malloc( + sizeof(TDS_SuplaRegisterDevice_B)); + } + + break; + + case SUPLA_DS_CALL_REGISTER_DEVICE_C: // ver. >= 6 + + if (srpc->sdp.data_size >= + (sizeof(TDS_SuplaRegisterDevice_C) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT)) && + srpc->sdp.data_size <= sizeof(TDS_SuplaRegisterDevice_C)) { + rd->data.ds_register_device_c = (TDS_SuplaRegisterDevice_C *)malloc( + sizeof(TDS_SuplaRegisterDevice_C)); + } + + break; + + case SUPLA_DS_CALL_REGISTER_DEVICE_D: // ver. >= 7 + + if (srpc->sdp.data_size >= + (sizeof(TDS_SuplaRegisterDevice_D) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT)) && + srpc->sdp.data_size <= sizeof(TDS_SuplaRegisterDevice_D)) { + rd->data.ds_register_device_d = (TDS_SuplaRegisterDevice_D *)malloc( + sizeof(TDS_SuplaRegisterDevice_D)); + } + + break; + + case SUPLA_DS_CALL_REGISTER_DEVICE_E: // ver. >= 10 + + if (srpc->sdp.data_size >= + (sizeof(TDS_SuplaRegisterDevice_E) - + (sizeof(TDS_SuplaDeviceChannel_C) * SUPLA_CHANNELMAXCOUNT)) && + srpc->sdp.data_size <= sizeof(TDS_SuplaRegisterDevice_E)) { + rd->data.ds_register_device_e = (TDS_SuplaRegisterDevice_E *)malloc( + sizeof(TDS_SuplaRegisterDevice_E)); + } + + break; + + case SUPLA_SD_CALL_REGISTER_DEVICE_RESULT: + + if (srpc->sdp.data_size == sizeof(TSD_SuplaRegisterDeviceResult)) + rd->data.sd_register_device_result = + (TSD_SuplaRegisterDeviceResult *)malloc( + sizeof(TSD_SuplaRegisterDeviceResult)); + break; + + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED: + + if (srpc->sdp.data_size == sizeof(TDS_SuplaDeviceChannelValue)) + rd->data.ds_device_channel_value = + (TDS_SuplaDeviceChannelValue *)malloc( + sizeof(TDS_SuplaDeviceChannelValue)); + + break; + + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_B: + + if (srpc->sdp.data_size == sizeof(TDS_SuplaDeviceChannelValue_B)) + rd->data.ds_device_channel_value_b = + (TDS_SuplaDeviceChannelValue_B *)malloc( + sizeof(TDS_SuplaDeviceChannelValue_B)); + + break; + + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_C: + + if (srpc->sdp.data_size == sizeof(TDS_SuplaDeviceChannelValue_C)) + rd->data.ds_device_channel_value_c = + (TDS_SuplaDeviceChannelValue_C *)malloc( + sizeof(TDS_SuplaDeviceChannelValue_C)); + + break; + + case SUPLA_DS_CALL_DEVICE_CHANNEL_EXTENDEDVALUE_CHANGED: + + if (srpc->sdp.data_size <= + sizeof(TDS_SuplaDeviceChannelExtendedValue) && + srpc->sdp.data_size >= + (sizeof(TDS_SuplaDeviceChannelExtendedValue) - + SUPLA_CHANNELEXTENDEDVALUE_SIZE)) + rd->data.ds_device_channel_extendedvalue = + (TDS_SuplaDeviceChannelExtendedValue *)malloc( + sizeof(TDS_SuplaDeviceChannelExtendedValue)); + + break; + + case SUPLA_SD_CALL_CHANNEL_SET_VALUE: + + if (srpc->sdp.data_size == sizeof(TSD_SuplaChannelNewValue)) + rd->data.sd_channel_new_value = (TSD_SuplaChannelNewValue *)malloc( + sizeof(TSD_SuplaChannelNewValue)); + + break; + + case SUPLA_SD_CALL_CHANNELGROUP_SET_VALUE: + + if (srpc->sdp.data_size == sizeof(TSD_SuplaChannelGroupNewValue)) + rd->data.sd_channelgroup_new_value = + (TSD_SuplaChannelGroupNewValue *)malloc( + sizeof(TSD_SuplaChannelGroupNewValue)); + + break; + + case SUPLA_DS_CALL_CHANNEL_SET_VALUE_RESULT: + + if (srpc->sdp.data_size == sizeof(TDS_SuplaChannelNewValueResult)) + rd->data.ds_channel_new_value_result = + (TDS_SuplaChannelNewValueResult *)malloc( + sizeof(TDS_SuplaChannelNewValueResult)); + + break; + + case SUPLA_DS_CALL_GET_FIRMWARE_UPDATE_URL: + + if (srpc->sdp.data_size == sizeof(TDS_FirmwareUpdateParams)) + rd->data.ds_firmware_update_params = + (TDS_FirmwareUpdateParams *)malloc( + sizeof(TDS_FirmwareUpdateParams)); + + break; + + case SUPLA_SD_CALL_GET_FIRMWARE_UPDATE_URL_RESULT: + + if (srpc->sdp.data_size == sizeof(TSD_FirmwareUpdate_UrlResult) || + srpc->sdp.data_size == sizeof(char)) { + rd->data.sc_firmware_update_url_result = + (TSD_FirmwareUpdate_UrlResult *)malloc( + sizeof(TSD_FirmwareUpdate_UrlResult)); + + if (srpc->sdp.data_size == sizeof(char) && + rd->data.sc_firmware_update_url_result != NULL) + memset(rd->data.sc_firmware_update_url_result, 0, + sizeof(TSD_FirmwareUpdate_UrlResult)); + } + break; + case SUPLA_SD_CALL_DEVICE_CALCFG_REQUEST: + if (srpc->sdp.data_size <= sizeof(TSD_DeviceCalCfgRequest) && + srpc->sdp.data_size >= + (sizeof(TSD_DeviceCalCfgRequest) - SUPLA_CALCFG_DATA_MAXSIZE)) { + rd->data.sd_device_calcfg_request = (TSD_DeviceCalCfgRequest *)malloc( + sizeof(TSD_DeviceCalCfgRequest)); + } + break; + case SUPLA_DS_CALL_DEVICE_CALCFG_RESULT: + if (srpc->sdp.data_size <= sizeof(TDS_DeviceCalCfgResult) && + srpc->sdp.data_size >= + (sizeof(TDS_DeviceCalCfgResult) - SUPLA_CALCFG_DATA_MAXSIZE)) { + rd->data.ds_device_calcfg_result = + (TDS_DeviceCalCfgResult *)malloc(sizeof(TDS_DeviceCalCfgResult)); + } + break; + case SUPLA_DS_CALL_GET_CHANNEL_FUNCTIONS: + call_with_no_data = 1; + break; + case SUPLA_SD_CALL_GET_CHANNEL_FUNCTIONS_RESULT: + if (srpc->sdp.data_size <= sizeof(TSD_ChannelFunctions) && + srpc->sdp.data_size >= + (sizeof(TSD_ChannelFunctions) - + sizeof(_supla_int_t) * SUPLA_CHANNELMAXCOUNT)) { + rd->data.sd_channel_functions = + (TSD_ChannelFunctions *)malloc(sizeof(TSD_ChannelFunctions)); + } + break; +#endif /*#ifndef SRPC_EXCLUDE_DEVICE*/ + +#ifndef SRPC_EXCLUDE_CLIENT + case SUPLA_CS_CALL_REGISTER_CLIENT: + + if (srpc->sdp.data_size == sizeof(TCS_SuplaRegisterClient)) + rd->data.cs_register_client = (TCS_SuplaRegisterClient *)malloc( + sizeof(TCS_SuplaRegisterClient)); + + break; + + case SUPLA_CS_CALL_REGISTER_CLIENT_B: // ver. >= 6 + + if (srpc->sdp.data_size == sizeof(TCS_SuplaRegisterClient_B)) + rd->data.cs_register_client_b = (TCS_SuplaRegisterClient_B *)malloc( + sizeof(TCS_SuplaRegisterClient_B)); + + break; + + case SUPLA_CS_CALL_REGISTER_CLIENT_C: // ver. >= 7 + + if (srpc->sdp.data_size == sizeof(TCS_SuplaRegisterClient_C)) + rd->data.cs_register_client_c = (TCS_SuplaRegisterClient_C *)malloc( + sizeof(TCS_SuplaRegisterClient_C)); + + break; + + case SUPLA_CS_CALL_REGISTER_CLIENT_D: // ver. >= 12 + + if (srpc->sdp.data_size == sizeof(TCS_SuplaRegisterClient_D)) + rd->data.cs_register_client_d = (TCS_SuplaRegisterClient_D *)malloc( + sizeof(TCS_SuplaRegisterClient_D)); + + break; + + case SUPLA_SC_CALL_REGISTER_CLIENT_RESULT: + + if (srpc->sdp.data_size == sizeof(TSC_SuplaRegisterClientResult)) + rd->data.sc_register_client_result = + (TSC_SuplaRegisterClientResult *)malloc( + sizeof(TSC_SuplaRegisterClientResult)); + + break; + + case SUPLA_SC_CALL_REGISTER_CLIENT_RESULT_B: + + if (srpc->sdp.data_size == sizeof(TSC_SuplaRegisterClientResult_B)) + rd->data.sc_register_client_result_b = + (TSC_SuplaRegisterClientResult_B *)malloc( + sizeof(TSC_SuplaRegisterClientResult_B)); + + break; + + case SUPLA_SC_CALL_LOCATION_UPDATE: + + if (srpc->sdp.data_size >= + (sizeof(TSC_SuplaLocation) - SUPLA_LOCATION_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SuplaLocation)) { + rd->data.sc_location = + (TSC_SuplaLocation *)malloc(sizeof(TSC_SuplaLocation)); + } + + break; + + case SUPLA_SC_CALL_LOCATIONPACK_UPDATE: + srpc_getlocationpack(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNEL_UPDATE: + + if (srpc->sdp.data_size >= + (sizeof(TSC_SuplaChannel) - SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SuplaChannel)) { + rd->data.sc_channel = + (TSC_SuplaChannel *)malloc(sizeof(TSC_SuplaChannel)); + } + + break; + + case SUPLA_SC_CALL_CHANNEL_UPDATE_B: + + if (srpc->sdp.data_size >= + (sizeof(TSC_SuplaChannel_B) - SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SuplaChannel_B)) { + rd->data.sc_channel_b = + (TSC_SuplaChannel_B *)malloc(sizeof(TSC_SuplaChannel_B)); + } + + break; + + case SUPLA_SC_CALL_CHANNEL_UPDATE_C: + + if (srpc->sdp.data_size >= + (sizeof(TSC_SuplaChannel_C) - SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SuplaChannel_C)) { + rd->data.sc_channel_c = + (TSC_SuplaChannel_C *)malloc(sizeof(TSC_SuplaChannel_C)); + } + + break; + + case SUPLA_SC_CALL_CHANNELPACK_UPDATE: + srpc_getchannelpack(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNELPACK_UPDATE_B: + srpc_getchannelpack_b(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNELPACK_UPDATE_C: + srpc_getchannelpack_c(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNEL_VALUE_UPDATE: + + if (srpc->sdp.data_size == sizeof(TSC_SuplaChannelValue)) + rd->data.sc_channel_value = + (TSC_SuplaChannelValue *)malloc(sizeof(TSC_SuplaChannelValue)); + + break; + + case SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE: + srpc_getchannelgroup_pack(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE_B: + srpc_getchannelgroup_pack_b(srpc, rd); + break; + + case SUPLA_SC_CALL_CHANNELGROUP_RELATION_PACK_UPDATE: + if (srpc->sdp.data_size <= sizeof(TSC_SuplaChannelGroupRelationPack) && + srpc->sdp.data_size >= + (sizeof(TSC_SuplaChannelGroupRelationPack) - + (sizeof(TSC_SuplaChannelGroupRelation) * + SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT))) { + rd->data.sc_channelgroup_relation_pack = + (TSC_SuplaChannelGroupRelationPack *)malloc( + sizeof(TSC_SuplaChannelGroupRelationPack)); + } + break; + + case SUPLA_SC_CALL_CHANNELVALUE_PACK_UPDATE: + if (srpc->sdp.data_size <= sizeof(TSC_SuplaChannelValuePack) && + srpc->sdp.data_size >= (sizeof(TSC_SuplaChannelValuePack) - + (sizeof(TSC_SuplaChannelValue) * + SUPLA_CHANNELVALUE_PACK_MAXCOUNT))) { + rd->data.sc_channelvalue_pack = (TSC_SuplaChannelValuePack *)malloc( + sizeof(TSC_SuplaChannelValuePack)); + } + break; + + case SUPLA_SC_CALL_CHANNELEXTENDEDVALUE_PACK_UPDATE: + if (srpc->sdp.data_size <= sizeof(TSC_SuplaChannelExtendedValuePack) && + srpc->sdp.data_size >= + (sizeof(TSC_SuplaChannelExtendedValuePack) - + SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXDATASIZE)) { + rd->data.sc_channelextendedvalue_pack = + (TSC_SuplaChannelExtendedValuePack *)malloc( + sizeof(TSC_SuplaChannelExtendedValuePack)); + } + break; + + case SUPLA_CS_CALL_CHANNEL_SET_VALUE: + + if (srpc->sdp.data_size == sizeof(TCS_SuplaChannelNewValue)) + rd->data.cs_channel_new_value = (TCS_SuplaChannelNewValue *)malloc( + sizeof(TCS_SuplaChannelNewValue)); + + break; + + case SUPLA_CS_CALL_SET_VALUE: + + if (srpc->sdp.data_size == sizeof(TCS_SuplaNewValue)) + rd->data.cs_new_value = + (TCS_SuplaNewValue *)malloc(sizeof(TCS_SuplaNewValue)); + + break; + + case SUPLA_CS_CALL_CHANNEL_SET_VALUE_B: + + if (srpc->sdp.data_size == sizeof(TCS_SuplaChannelNewValue_B)) + rd->data.cs_channel_new_value_b = + (TCS_SuplaChannelNewValue_B *)malloc( + sizeof(TCS_SuplaChannelNewValue_B)); + + break; + + case SUPLA_SC_CALL_EVENT: + + if (srpc->sdp.data_size >= + (sizeof(TSC_SuplaEvent) - SUPLA_SENDER_NAME_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SuplaEvent)) { + rd->data.sc_event = (TSC_SuplaEvent *)malloc(sizeof(TSC_SuplaEvent)); + } + + break; + + case SUPLA_CS_CALL_OAUTH_TOKEN_REQUEST: + call_with_no_data = 1; + break; + + case SUPLA_SC_CALL_OAUTH_TOKEN_REQUEST_RESULT: + if (srpc->sdp.data_size >= (sizeof(TSC_OAuthTokenRequestResult) - + SUPLA_OAUTH_TOKEN_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_OAuthTokenRequestResult)) { + rd->data.sc_oauth_tokenrequest_result = + (TSC_OAuthTokenRequestResult *)malloc( + sizeof(TSC_OAuthTokenRequestResult)); + } + break; + case SUPLA_CS_CALL_SUPERUSER_AUTHORIZATION_REQUEST: + if (srpc->sdp.data_size == sizeof(TCS_SuperUserAuthorizationRequest)) + rd->data.cs_superuser_authorization_request = + (TCS_SuperUserAuthorizationRequest *)malloc( + sizeof(TCS_SuperUserAuthorizationRequest)); + break; + case SUPLA_CS_CALL_GET_SUPERUSER_AUTHORIZATION_RESULT: + call_with_no_data = 1; + break; + case SUPLA_SC_CALL_SUPERUSER_AUTHORIZATION_RESULT: + if (srpc->sdp.data_size == sizeof(TSC_SuperUserAuthorizationResult)) + rd->data.sc_superuser_authorization_result = + (TSC_SuperUserAuthorizationResult *)malloc( + sizeof(TSC_SuperUserAuthorizationResult)); + break; + case SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST: + if (srpc->sdp.data_size <= sizeof(TCS_DeviceCalCfgRequest) && + srpc->sdp.data_size >= + (sizeof(TCS_DeviceCalCfgRequest) - SUPLA_CALCFG_DATA_MAXSIZE)) { + rd->data.cs_device_calcfg_request = (TCS_DeviceCalCfgRequest *)malloc( + sizeof(TCS_DeviceCalCfgRequest)); + } + break; + case SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST_B: + if (srpc->sdp.data_size <= sizeof(TCS_DeviceCalCfgRequest_B) && + srpc->sdp.data_size >= (sizeof(TCS_DeviceCalCfgRequest_B) - + SUPLA_CALCFG_DATA_MAXSIZE)) { + rd->data.cs_device_calcfg_request_b = + (TCS_DeviceCalCfgRequest_B *)malloc( + sizeof(TCS_DeviceCalCfgRequest_B)); + } + break; + case SUPLA_SC_CALL_DEVICE_CALCFG_RESULT: + if (srpc->sdp.data_size <= sizeof(TSC_DeviceCalCfgResult) && + srpc->sdp.data_size >= + (sizeof(TSC_DeviceCalCfgResult) - SUPLA_CALCFG_DATA_MAXSIZE)) { + rd->data.sc_device_calcfg_result = + (TSC_DeviceCalCfgResult *)malloc(sizeof(TSC_DeviceCalCfgResult)); + } + break; + + case SUPLA_CS_CALL_GET_CHANNEL_BASIC_CFG: + if (srpc->sdp.data_size == sizeof(TCS_ChannelBasicCfgRequest)) + rd->data.cs_channel_basic_cfg_request = + (TCS_ChannelBasicCfgRequest *)malloc( + sizeof(TCS_ChannelBasicCfgRequest)); + break; + case SUPLA_SC_CALL_CHANNEL_BASIC_CFG_RESULT: + if (srpc->sdp.data_size >= + (sizeof(TSC_ChannelBasicCfg) - SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_ChannelBasicCfg)) + rd->data.sc_channel_basic_cfg = + (TSC_ChannelBasicCfg *)malloc(sizeof(TSC_ChannelBasicCfg)); + break; + + case SUPLA_CS_CALL_SET_CHANNEL_FUNCTION: + if (srpc->sdp.data_size == sizeof(TCS_SetChannelFunction)) + rd->data.cs_set_channel_function = + (TCS_SetChannelFunction *)malloc(sizeof(TCS_SetChannelFunction)); + break; + + case SUPLA_SC_CALL_SET_CHANNEL_FUNCTION_RESULT: + if (srpc->sdp.data_size == sizeof(TSC_SetChannelFunctionResult)) + rd->data.sc_set_channel_function_result = + (TSC_SetChannelFunctionResult *)malloc( + sizeof(TSC_SetChannelFunctionResult)); + break; + + case SUPLA_CS_CALL_SET_CHANNEL_CAPTION: + if (srpc->sdp.data_size >= (sizeof(TCS_SetChannelCaption) - + SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TCS_SetChannelCaption)) + rd->data.cs_set_channel_caption = + (TCS_SetChannelCaption *)malloc(sizeof(TCS_SetChannelCaption)); + break; + + case SUPLA_SC_CALL_SET_CHANNEL_CAPTION_RESULT: + if (srpc->sdp.data_size >= (sizeof(TSC_SetChannelCaptionResult) - + SUPLA_CHANNEL_CAPTION_MAXSIZE) && + srpc->sdp.data_size <= sizeof(TSC_SetChannelCaptionResult)) + rd->data.sc_set_channel_caption_result = + (TSC_SetChannelCaptionResult *)malloc( + sizeof(TSC_SetChannelCaptionResult)); + break; + + case SUPLA_CS_CALL_CLIENTS_RECONNECT_REQUEST: + call_with_no_data = 1; + break; + + case SUPLA_SC_CALL_CLIENTS_RECONNECT_REQUEST_RESULT: + if (srpc->sdp.data_size == sizeof(TSC_ClientsReconnectRequestResult)) + rd->data.sc_clients_reconnect_result = + (TSC_ClientsReconnectRequestResult *)malloc( + sizeof(TSC_ClientsReconnectRequestResult)); + break; + + case SUPLA_CS_CALL_SET_REGISTRATION_ENABLED: + if (srpc->sdp.data_size == sizeof(TCS_SetRegistrationEnabled)) + rd->data.cs_set_registration_enabled = + (TCS_SetRegistrationEnabled *)malloc( + sizeof(TCS_SetRegistrationEnabled)); + break; + + case SUPLA_SC_CALL_SET_REGISTRATION_ENABLED_RESULT: + if (srpc->sdp.data_size == sizeof(TSC_SetRegistrationEnabledResult)) + rd->data.sc_set_registration_enabled_result = + (TSC_SetRegistrationEnabledResult *)malloc( + sizeof(TSC_SetRegistrationEnabledResult)); + break; + + case SUPLA_CS_CALL_DEVICE_RECONNECT_REQUEST: + if (srpc->sdp.data_size == sizeof(TCS_DeviceReconnectRequest)) + rd->data.cs_device_reconnect_request = + (TCS_DeviceReconnectRequest *)malloc( + sizeof(TCS_DeviceReconnectRequest)); + break; + case SUPLA_SC_CALL_DEVICE_RECONNECT_REQUEST_RESULT: + if (srpc->sdp.data_size == sizeof(TSC_DeviceReconnectRequestResult)) + rd->data.sc_device_reconnect_request_result = + (TSC_DeviceReconnectRequestResult *)malloc( + sizeof(TSC_DeviceReconnectRequestResult)); + break; + +#endif /*#ifndef SRPC_EXCLUDE_CLIENT*/ + } + + if (call_with_no_data == 1) { + return lck_unlock_r(srpc->lck, SUPLA_RESULT_TRUE); + } + + if (rd->data.dcs_ping != NULL) { + if (srpc->sdp.data_size > 0) { + memcpy(rd->data.dcs_ping, srpc->sdp.data, srpc->sdp.data_size); + } + + return lck_unlock_r(srpc->lck, SUPLA_RESULT_TRUE); + } + + return lck_unlock_r(srpc->lck, SUPLA_RESULT_DATA_ERROR); + } + + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); +} + +void SRPC_ICACHE_FLASH srpc_rd_free(TsrpcReceivedData *rd) { + if (rd->call_type > 0) { + // first one + + if (rd->data.dcs_ping != NULL) free(rd->data.dcs_ping); + + rd->call_type = 0; + } +} + +unsigned char SRPC_ICACHE_FLASH +srpc_call_min_version_required(void *_srpc, unsigned _supla_int_t call_type) { + switch (call_type) { + case SUPLA_DCS_CALL_GETVERSION: + case SUPLA_SDC_CALL_GETVERSION_RESULT: + case SUPLA_SDC_CALL_VERSIONERROR: + case SUPLA_DCS_CALL_PING_SERVER: + case SUPLA_SDC_CALL_PING_SERVER_RESULT: + case SUPLA_DS_CALL_REGISTER_DEVICE: + case SUPLA_SD_CALL_REGISTER_DEVICE_RESULT: + case SUPLA_CS_CALL_REGISTER_CLIENT: + case SUPLA_SC_CALL_REGISTER_CLIENT_RESULT: + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED: + case SUPLA_SD_CALL_CHANNEL_SET_VALUE: + case SUPLA_DS_CALL_CHANNEL_SET_VALUE_RESULT: + case SUPLA_SC_CALL_LOCATION_UPDATE: + case SUPLA_SC_CALL_LOCATIONPACK_UPDATE: + case SUPLA_SC_CALL_CHANNEL_UPDATE: + case SUPLA_SC_CALL_CHANNELPACK_UPDATE: + case SUPLA_SC_CALL_CHANNEL_VALUE_UPDATE: + case SUPLA_CS_CALL_GET_NEXT: + case SUPLA_SC_CALL_EVENT: + case SUPLA_CS_CALL_CHANNEL_SET_VALUE: + return 1; + + case SUPLA_DS_CALL_REGISTER_DEVICE_B: + case SUPLA_DCS_CALL_SET_ACTIVITY_TIMEOUT: + case SUPLA_SDC_CALL_SET_ACTIVITY_TIMEOUT_RESULT: + return 2; + + case SUPLA_CS_CALL_CHANNEL_SET_VALUE_B: + return 3; + + case SUPLA_DS_CALL_GET_FIRMWARE_UPDATE_URL: + case SUPLA_SD_CALL_GET_FIRMWARE_UPDATE_URL_RESULT: + return 5; + + case SUPLA_DS_CALL_REGISTER_DEVICE_C: + case SUPLA_CS_CALL_REGISTER_CLIENT_B: + return 6; + + case SUPLA_CS_CALL_REGISTER_CLIENT_C: + case SUPLA_DS_CALL_REGISTER_DEVICE_D: + case SUPLA_DCS_CALL_GET_REGISTRATION_ENABLED: + case SUPLA_SDC_CALL_GET_REGISTRATION_ENABLED_RESULT: + return 7; + + case SUPLA_SC_CALL_CHANNELPACK_UPDATE_B: + case SUPLA_SC_CALL_CHANNEL_UPDATE_B: + return 8; + + case SUPLA_SC_CALL_REGISTER_CLIENT_RESULT_B: + case SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE: + case SUPLA_SC_CALL_CHANNELGROUP_RELATION_PACK_UPDATE: + case SUPLA_SC_CALL_CHANNELVALUE_PACK_UPDATE: + case SUPLA_CS_CALL_SET_VALUE: + return 9; + + case SUPLA_DS_CALL_DEVICE_CHANNEL_EXTENDEDVALUE_CHANGED: + case SUPLA_SC_CALL_CHANNELEXTENDEDVALUE_PACK_UPDATE: + case SUPLA_CS_CALL_OAUTH_TOKEN_REQUEST: + case SUPLA_SC_CALL_OAUTH_TOKEN_REQUEST_RESULT: + case SUPLA_DS_CALL_REGISTER_DEVICE_E: + case SUPLA_CS_CALL_SUPERUSER_AUTHORIZATION_REQUEST: + case SUPLA_SC_CALL_SUPERUSER_AUTHORIZATION_RESULT: + case SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST: + case SUPLA_SC_CALL_DEVICE_CALCFG_RESULT: + case SUPLA_SD_CALL_DEVICE_CALCFG_REQUEST: + case SUPLA_DS_CALL_DEVICE_CALCFG_RESULT: + case SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE_B: + case SUPLA_SC_CALL_CHANNEL_UPDATE_C: + case SUPLA_SC_CALL_CHANNELPACK_UPDATE_C: + return 10; + case SUPLA_DCS_CALL_GET_USER_LOCALTIME: + case SUPLA_DCS_CALL_GET_USER_LOCALTIME_RESULT: + case SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST_B: + return 11; + case SUPLA_CS_CALL_REGISTER_CLIENT_D: + case SUPLA_CSD_CALL_GET_CHANNEL_STATE: + case SUPLA_DSC_CALL_CHANNEL_STATE_RESULT: + case SUPLA_CS_CALL_GET_CHANNEL_BASIC_CFG: + case SUPLA_SC_CALL_CHANNEL_BASIC_CFG_RESULT: + case SUPLA_CS_CALL_SET_CHANNEL_FUNCTION: + case SUPLA_SC_CALL_SET_CHANNEL_FUNCTION_RESULT: + case SUPLA_CS_CALL_SET_CHANNEL_CAPTION: + case SUPLA_SC_CALL_SET_CHANNEL_CAPTION_RESULT: + case SUPLA_CS_CALL_CLIENTS_RECONNECT_REQUEST: + case SUPLA_SC_CALL_CLIENTS_RECONNECT_REQUEST_RESULT: + case SUPLA_CS_CALL_SET_REGISTRATION_ENABLED: + case SUPLA_SC_CALL_SET_REGISTRATION_ENABLED_RESULT: + case SUPLA_CS_CALL_DEVICE_RECONNECT_REQUEST: + case SUPLA_SC_CALL_DEVICE_RECONNECT_REQUEST_RESULT: + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_B: + case SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_C: + case SUPLA_DS_CALL_GET_CHANNEL_FUNCTIONS: + case SUPLA_SD_CALL_GET_CHANNEL_FUNCTIONS_RESULT: + case SUPLA_CS_CALL_GET_SUPERUSER_AUTHORIZATION_RESULT: + return 12; + case SUPLA_SD_CALL_CHANNELGROUP_SET_VALUE: + return 13; + } + + return 255; +} + +unsigned char SRPC_ICACHE_FLASH +srpc_call_allowed(void *_srpc, unsigned _supla_int_t call_type) { + unsigned char min_ver = srpc_call_min_version_required(_srpc, call_type); + + if (min_ver == 0 || srpc_get_proto_version(_srpc) >= min_ver) { + return 1; + } + + return 0; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_async__call(void *_srpc, + unsigned _supla_int_t call_type, + char *data, + unsigned _supla_int_t data_size, + unsigned char *version) { + Tsrpc *srpc = (Tsrpc *)_srpc; + + if (!srpc_call_allowed(_srpc, call_type)) { + if (srpc->params.on_min_version_required != NULL) { + srpc->params.on_min_version_required( + _srpc, call_type, srpc_call_min_version_required(_srpc, call_type), + srpc->params.user_params); + } + + return SUPLA_RESULT_FALSE; + } + + if (srpc->params.before_async_call != NULL) { + srpc->params.before_async_call(_srpc, call_type, srpc->params.user_params); + } + + lck_lock(srpc->lck); + + sproto_sdp_init(srpc->proto, &srpc->sdp); + + if (version != NULL) srpc->sdp.version = *version; + + if (SUPLA_RESULT_TRUE == + sproto_set_data(&srpc->sdp, data, data_size, call_type) && + srpc_out_queue_push(srpc, &srpc->sdp)) { +#ifndef __EH_DISABLED + if (srpc->params.eh != 0) { + eh_raise_event(srpc->params.eh); + } +#endif + + return lck_unlock_r(srpc->lck, srpc->sdp.rr_id); + } + + return lck_unlock_r(srpc->lck, SUPLA_RESULT_FALSE); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_async_call(void *_srpc, unsigned _supla_int_t call_type, char *data, + unsigned _supla_int_t data_size) { + return srpc_async__call(_srpc, call_type, data, data_size, NULL); +} + +unsigned char SRPC_ICACHE_FLASH srpc_get_proto_version(void *_srpc) { + unsigned char version; + + Tsrpc *srpc = (Tsrpc *)_srpc; + lck_lock(srpc->lck); + version = sproto_get_version(srpc->proto); + lck_unlock(srpc->lck); + + return version; +} + +void SRPC_ICACHE_FLASH srpc_set_proto_version(void *_srpc, + unsigned char version) { + Tsrpc *srpc = (Tsrpc *)_srpc; + + lck_lock(srpc->lck); + sproto_set_version(srpc->proto, version); + lck_unlock(srpc->lck); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_getversion(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_DCS_CALL_GETVERSION, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_getversion_result( + void *_srpc, char SoftVer[SUPLA_SOFTVER_MAXSIZE]) { + TSDC_SuplaGetVersionResult gvr; + + gvr.proto_version = SUPLA_PROTO_VERSION; + gvr.proto_version_min = SUPLA_PROTO_VERSION_MIN; + + memcpy(gvr.SoftVer, SoftVer, SUPLA_SOFTVER_MAXSIZE); + + return srpc_async_call(_srpc, SUPLA_SDC_CALL_GETVERSION_RESULT, (char *)&gvr, + sizeof(TSDC_SuplaGetVersionResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sdc_async_versionerror(void *_srpc, unsigned char remote_version) { + TSDC_SuplaVersionError ve; + ve.server_version = SUPLA_PROTO_VERSION; + ve.server_version_min = SUPLA_PROTO_VERSION_MIN; + + return srpc_async__call(_srpc, SUPLA_SDC_CALL_VERSIONERROR, (char *)&ve, + sizeof(TSDC_SuplaVersionError), &remote_version); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_ping_server(void *_srpc) { + TDCS_SuplaPingServer ps; + +#if defined(ESP8266) || defined(ESP32) + unsigned int time = system_get_time(); + ps.now.tv_sec = time / 1000000; + ps.now.tv_usec = time % 1000000; +#elif defined(__AVR__) + ps.now.tv_sec[0] = 0; + ps.now.tv_sec[1] = 0; + ps.now.tv_usec[0] = 0; + ps.now.tv_usec[1] = 0; +#else + struct timeval now; + gettimeofday(&now, NULL); + ps.now.tv_sec = now.tv_sec; + ps.now.tv_usec = now.tv_usec; +#endif + + return srpc_async_call(_srpc, SUPLA_DCS_CALL_PING_SERVER, (char *)&ps, + sizeof(TDCS_SuplaPingServer)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_ping_server_result(void *_srpc) { +#if !defined(ESP8266) && !defined(__AVR__) && !defined(ESP32) + TSDC_SuplaPingServerResult ps; + + struct timeval now; + gettimeofday(&now, NULL); + ps.now.tv_sec = now.tv_sec; + ps.now.tv_usec = now.tv_usec; + + return srpc_async_call(_srpc, SUPLA_SDC_CALL_PING_SERVER_RESULT, (char *)&ps, + sizeof(TSDC_SuplaPingServerResult)); +#else + return 0; +#endif +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_set_activity_timeout( + void *_srpc, TDCS_SuplaSetActivityTimeout *dcs_set_activity_timeout) { + return srpc_async_call(_srpc, SUPLA_DCS_CALL_SET_ACTIVITY_TIMEOUT, + (char *)dcs_set_activity_timeout, + sizeof(TDCS_SuplaSetActivityTimeout)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_set_activity_timeout_result( + void *_srpc, + TSDC_SuplaSetActivityTimeoutResult *sdc_set_activity_timeout_result) { + return srpc_async_call(_srpc, SUPLA_SDC_CALL_SET_ACTIVITY_TIMEOUT_RESULT, + (char *)sdc_set_activity_timeout_result, + sizeof(TSDC_SuplaSetActivityTimeoutResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_dcs_async_get_registration_enabled(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_DCS_CALL_GET_REGISTRATION_ENABLED, NULL, + 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_get_registration_enabled_result( + void *_srpc, TSDC_RegistrationEnabled *reg_enabled) { + return srpc_async_call(_srpc, SUPLA_SDC_CALL_GET_REGISTRATION_ENABLED_RESULT, + (char *)reg_enabled, sizeof(TSDC_RegistrationEnabled)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_get_user_localtime(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_DCS_CALL_GET_USER_LOCALTIME, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_get_user_localtime_result( + void *_srpc, TSDC_UserLocalTimeResult *localtime) { + if (localtime == NULL || localtime->timezoneSize > SUPLA_TIMEZONE_MAXSIZE) { + return 0; + } + + unsigned int size = sizeof(TSDC_UserLocalTimeResult) - + SUPLA_TIMEZONE_MAXSIZE + localtime->timezoneSize; + + return srpc_async_call(_srpc, SUPLA_DCS_CALL_GET_USER_LOCALTIME_RESULT, + (char *)localtime, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_csd_async_get_channel_state( + void *_srpc, TCSD_ChannelStateRequest *request) { + return srpc_async_call(_srpc, SUPLA_CSD_CALL_GET_CHANNEL_STATE, + (char *)request, sizeof(TCSD_ChannelStateRequest)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_csd_async_channel_state_result(void *_srpc, TDSC_ChannelState *state) { + return srpc_async_call(_srpc, SUPLA_DSC_CALL_CHANNEL_STATE_RESULT, + (char *)state, sizeof(TDSC_ChannelState)); +} + +#ifndef SRPC_EXCLUDE_DEVICE +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_firmware_update_url( + void *_srpc, TDS_FirmwareUpdateParams *params) { + return srpc_async_call(_srpc, SUPLA_DS_CALL_GET_FIRMWARE_UPDATE_URL, + (char *)params, sizeof(TDS_FirmwareUpdateParams)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice( + void *_srpc, TDS_SuplaRegisterDevice *registerdevice) { + _supla_int_t size = + sizeof(TDS_SuplaRegisterDevice) - + (sizeof(TDS_SuplaDeviceChannel) * SUPLA_CHANNELMAXCOUNT) + + (sizeof(TDS_SuplaDeviceChannel) * registerdevice->channel_count); + + if (size > sizeof(TDS_SuplaRegisterDevice)) return 0; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_REGISTER_DEVICE, + (char *)registerdevice, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_b( + void *_srpc, TDS_SuplaRegisterDevice_B *registerdevice) { + _supla_int_t size = + sizeof(TDS_SuplaRegisterDevice_B) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT) + + (sizeof(TDS_SuplaDeviceChannel_B) * registerdevice->channel_count); + + if (size > sizeof(TDS_SuplaRegisterDevice_B)) return 0; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_REGISTER_DEVICE_B, + (char *)registerdevice, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_c( + void *_srpc, TDS_SuplaRegisterDevice_C *registerdevice) { + _supla_int_t size = + sizeof(TDS_SuplaRegisterDevice_C) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT) + + (sizeof(TDS_SuplaDeviceChannel_B) * registerdevice->channel_count); + + if (size > sizeof(TDS_SuplaRegisterDevice_C)) return 0; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_REGISTER_DEVICE_C, + (char *)registerdevice, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_d( + void *_srpc, TDS_SuplaRegisterDevice_D *registerdevice) { + _supla_int_t size = + sizeof(TDS_SuplaRegisterDevice_D) - + (sizeof(TDS_SuplaDeviceChannel_B) * SUPLA_CHANNELMAXCOUNT) + + (sizeof(TDS_SuplaDeviceChannel_B) * registerdevice->channel_count); + + if (size > sizeof(TDS_SuplaRegisterDevice_D)) return 0; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_REGISTER_DEVICE_D, + (char *)registerdevice, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_e( + void *_srpc, TDS_SuplaRegisterDevice_E *registerdevice) { + _supla_int_t size = + sizeof(TDS_SuplaRegisterDevice_E) - + (sizeof(TDS_SuplaDeviceChannel_C) * SUPLA_CHANNELMAXCOUNT) + + (sizeof(TDS_SuplaDeviceChannel_C) * registerdevice->channel_count); + + if (size > sizeof(TDS_SuplaRegisterDevice_E)) return 0; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_REGISTER_DEVICE_E, + (char *)registerdevice, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_registerdevice_result( + void *_srpc, TSD_SuplaRegisterDeviceResult *registerdevice_result) { + return srpc_async_call(_srpc, SUPLA_SD_CALL_REGISTER_DEVICE_RESULT, + (char *)registerdevice_result, + sizeof(TSD_SuplaRegisterDeviceResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sd_async_set_channel_value(void *_srpc, TSD_SuplaChannelNewValue *value) { + return srpc_async_call(_srpc, SUPLA_SD_CALL_CHANNEL_SET_VALUE, (char *)value, + sizeof(TSD_SuplaChannelNewValue)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_set_channelgroup_value( + void *_srpc, TSD_SuplaChannelGroupNewValue *value) { + return srpc_async_call(_srpc, SUPLA_SD_CALL_CHANNELGROUP_SET_VALUE, + (char *)value, sizeof(TSD_SuplaChannelGroupNewValue)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_set_channel_result(void *_srpc, unsigned char ChannelNumber, + _supla_int_t SenderID, char Success) { + TDS_SuplaChannelNewValueResult result; + result.ChannelNumber = ChannelNumber; + result.SenderID = SenderID; + result.Success = Success; + + return srpc_async_call(_srpc, SUPLA_DS_CALL_CHANNEL_SET_VALUE_RESULT, + (char *)&result, + sizeof(TDS_SuplaChannelNewValueResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_firmware_update_url_result( + void *_srpc, TSD_FirmwareUpdate_UrlResult *result) { + return srpc_async_call( + _srpc, SUPLA_SD_CALL_GET_FIRMWARE_UPDATE_URL_RESULT, (char *)result, + result->exists == 1 ? sizeof(TSD_FirmwareUpdate_UrlResult) + : sizeof(char)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed( + void *_srpc, unsigned char channel_number, char *value) { + TDS_SuplaDeviceChannelValue ncsc; + ncsc.ChannelNumber = channel_number; + memcpy(ncsc.value, value, SUPLA_CHANNELVALUE_SIZE); + + return srpc_async_call(_srpc, SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED, + (char *)&ncsc, sizeof(TDS_SuplaDeviceChannelValue)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_channel_value_changed_b(void *_srpc, unsigned char channel_number, + char *value, unsigned char offline) { + TDS_SuplaDeviceChannelValue_B ncsc; + ncsc.ChannelNumber = channel_number; + ncsc.Offline = !!offline; + memcpy(ncsc.value, value, SUPLA_CHANNELVALUE_SIZE); + + return srpc_async_call(_srpc, SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_B, + (char *)&ncsc, sizeof(TDS_SuplaDeviceChannelValue_B)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed_c( + void *_srpc, unsigned char channel_number, char *value, + unsigned char offline, unsigned _supla_int_t validity_time_sec) { + TDS_SuplaDeviceChannelValue_C ncsc; + ncsc.ChannelNumber = channel_number; + ncsc.Offline = !!offline; + ncsc.ValidityTimeSec = validity_time_sec; + memcpy(ncsc.value, value, SUPLA_CHANNELVALUE_SIZE); + + return srpc_async_call(_srpc, SUPLA_DS_CALL_DEVICE_CHANNEL_VALUE_CHANGED_C, + (char *)&ncsc, sizeof(TDS_SuplaDeviceChannelValue_C)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_extendedvalue_changed( + void *_srpc, unsigned char channel_number, + TSuplaChannelExtendedValue *value) { + if (value == NULL || value->size > SUPLA_CHANNELEXTENDEDVALUE_SIZE || + value->size == 0) { + return 0; + } + + TDS_SuplaDeviceChannelExtendedValue ncsc; + ncsc.ChannelNumber = channel_number; + memcpy(&ncsc.value, value, sizeof(TSuplaChannelExtendedValue)); + + return srpc_async_call( + _srpc, SUPLA_DS_CALL_DEVICE_CHANNEL_EXTENDEDVALUE_CHANGED, (char *)&ncsc, + sizeof(TDS_SuplaDeviceChannelExtendedValue) - + (SUPLA_CHANNELEXTENDEDVALUE_SIZE - ncsc.value.size)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_device_calcfg_request( + void *_srpc, TSD_DeviceCalCfgRequest *request) { + if (request == NULL || request->DataSize > SUPLA_CALCFG_DATA_MAXSIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_SD_CALL_DEVICE_CALCFG_REQUEST, + (char *)request, + sizeof(TSD_DeviceCalCfgRequest) - + SUPLA_CALCFG_DATA_MAXSIZE + request->DataSize); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_device_calcfg_result( + void *_srpc, TDS_DeviceCalCfgResult *result) { + if (result == NULL || result->DataSize > SUPLA_CALCFG_DATA_MAXSIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_DS_CALL_DEVICE_CALCFG_RESULT, + (char *)result, + sizeof(TDS_DeviceCalCfgResult) - + SUPLA_CALCFG_DATA_MAXSIZE + result->DataSize); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_get_channel_functions(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_DS_CALL_GET_CHANNEL_FUNCTIONS, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_channel_functions_result( + void *_srpc, TSD_ChannelFunctions *result) { + if (result == NULL || result->ChannelCount > SUPLA_CHANNELMAXCOUNT) { + return 0; + } + + _supla_int_t size = sizeof(TSD_ChannelFunctions) - + sizeof(_supla_int_t) * SUPLA_CHANNELMAXCOUNT + + sizeof(_supla_int_t) * result->ChannelCount; + + if (size > sizeof(TSD_ChannelFunctions)) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_SD_CALL_GET_CHANNEL_FUNCTIONS_RESULT, + (char *)result, size); +} + +#endif /*SRPC_EXCLUDE_DEVICE*/ + +#ifndef SRPC_EXCLUDE_CLIENT +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient( + void *_srpc, TCS_SuplaRegisterClient *registerclient) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_REGISTER_CLIENT, + (char *)registerclient, + sizeof(TCS_SuplaRegisterClient)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_b( + void *_srpc, TCS_SuplaRegisterClient_B *registerclient) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_REGISTER_CLIENT_B, + (char *)registerclient, + sizeof(TCS_SuplaRegisterClient_B)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_c( + void *_srpc, TCS_SuplaRegisterClient_C *registerclient) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_REGISTER_CLIENT_C, + (char *)registerclient, + sizeof(TCS_SuplaRegisterClient_C)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_d( + void *_srpc, TCS_SuplaRegisterClient_D *registerclient) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_REGISTER_CLIENT_D, + (char *)registerclient, + sizeof(TCS_SuplaRegisterClient_D)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_registerclient_result( + void *_srpc, TSC_SuplaRegisterClientResult *registerclient_result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_REGISTER_CLIENT_RESULT, + (char *)registerclient_result, + sizeof(TSC_SuplaRegisterClientResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_registerclient_result_b( + void *_srpc, TSC_SuplaRegisterClientResult_B *registerclient_result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_REGISTER_CLIENT_RESULT_B, + (char *)registerclient_result, + sizeof(TSC_SuplaRegisterClientResult_B)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_location_update(void *_srpc, TSC_SuplaLocation *location) { + _supla_int_t size = sizeof(TSC_SuplaLocation) - + SUPLA_LOCATION_CAPTION_MAXSIZE + location->CaptionSize; + + if (size > sizeof(TSC_SuplaLocation)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_LOCATION_UPDATE, (char *)location, + size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_set_pack( + void *_srpc, void *pack, _supla_int_t count, + _func_srpc_pack_get_caption_size get_caption_size, + _func_srpc_pack_get_item_ptr get_item_ptr, + _func_srpc_pack_set_pack_count set_pack_count, + unsigned _supla_int_t pack_sizeof, unsigned _supla_int_t pack_max_count, + unsigned _supla_int_t caption_max_size, unsigned _supla_int_t item_sizeof, + unsigned _supla_int_t call_type) { + _supla_int_t result = 0; + _supla_int_t a; + _supla_int_t n = 0; + _supla_int_t size = 0; + _supla_int_t offset = 0; + + if (count < 1 || count > pack_max_count) return 0; + + size = pack_sizeof - (item_sizeof * pack_max_count); + offset = size; + + char *buffer = malloc(size); + + if (buffer == NULL) return 0; + + memcpy(buffer, pack, size); + + for (a = 0; a < count; a++) { + if (get_caption_size(pack, a) <= caption_max_size) { + size += item_sizeof - caption_max_size + get_caption_size(pack, a); + + char *new_buffer = (char *)realloc(buffer, size); + + if (new_buffer == NULL) { + free(buffer); + return 0; + } + + buffer = new_buffer; + memcpy(&buffer[offset], get_item_ptr(pack, a), size - offset); + offset += size - offset; + n++; + } + } + + set_pack_count(buffer, n, 0); + + result = srpc_async_call(_srpc, call_type, buffer, size); + + free(buffer); + return result; +} + +unsigned _supla_int_t srpc_locationpack_get_caption_size(void *pack, + _supla_int_t idx) { + return ((TSC_SuplaLocationPack *)pack)->items[idx].CaptionSize; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_locationpack_update( + void *_srpc, TSC_SuplaLocationPack *location_pack) { + return srpc_set_pack( + _srpc, location_pack, location_pack->count, + &srpc_locationpack_get_caption_size, &srpc_locationpack_get_item_ptr, + &srpc_locationpack_set_pack_count, sizeof(TSC_SuplaLocationPack), + SUPLA_LOCATIONPACK_MAXCOUNT, SUPLA_LOCATION_CAPTION_MAXSIZE, + sizeof(TSC_SuplaLocation), SUPLA_SC_CALL_LOCATIONPACK_UPDATE); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_channel_update(void *_srpc, TSC_SuplaChannel *channel) { + _supla_int_t size = sizeof(TSC_SuplaChannel) - SUPLA_CHANNEL_CAPTION_MAXSIZE + + channel->CaptionSize; + + if (size > sizeof(TSC_SuplaChannel)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNEL_UPDATE, (char *)channel, + size); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_channel_update_b(void *_srpc, TSC_SuplaChannel_B *channel_b) { + _supla_int_t size = sizeof(TSC_SuplaChannel_B) - + SUPLA_CHANNEL_CAPTION_MAXSIZE + channel_b->CaptionSize; + + if (size > sizeof(TSC_SuplaChannel_B)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNEL_UPDATE_B, + (char *)channel_b, size); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_channel_update_c(void *_srpc, TSC_SuplaChannel_C *channel_c) { + _supla_int_t size = sizeof(TSC_SuplaChannel_C) - + SUPLA_CHANNEL_CAPTION_MAXSIZE + channel_c->CaptionSize; + + if (size > sizeof(TSC_SuplaChannel_C)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNEL_UPDATE_C, + (char *)channel_c, size); +} + +unsigned _supla_int_t srpc_channelpack_get_caption_size(void *pack, + _supla_int_t idx) { + return ((TSC_SuplaChannelPack *)pack)->items[idx].CaptionSize; +} + +unsigned _supla_int_t srpc_channelpack_get_caption_size_b(void *pack, + _supla_int_t idx) { + return ((TSC_SuplaChannelPack_B *)pack)->items[idx].CaptionSize; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update( + void *_srpc, TSC_SuplaChannelPack *channel_pack) { + return srpc_set_pack( + _srpc, channel_pack, channel_pack->count, + &srpc_channelpack_get_caption_size, &srpc_channelpack_get_item_ptr, + &srpc_channelpack_set_pack_count, sizeof(TSC_SuplaChannelPack), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + sizeof(TSC_SuplaChannel), SUPLA_SC_CALL_CHANNELPACK_UPDATE); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update_b( + void *_srpc, TSC_SuplaChannelPack_B *channel_pack) { + return srpc_set_pack( + _srpc, channel_pack, channel_pack->count, + &srpc_channelpack_get_caption_size_b, &srpc_channelpack_get_item_ptr_b, + &srpc_channelpack_set_pack_count_b, sizeof(TSC_SuplaChannelPack_B), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + sizeof(TSC_SuplaChannel_B), SUPLA_SC_CALL_CHANNELPACK_UPDATE_B); +} + +unsigned _supla_int_t srpc_channelpack_get_caption_size_c(void *pack, + _supla_int_t idx) { + return ((TSC_SuplaChannelPack_C *)pack)->items[idx].CaptionSize; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update_c( + void *_srpc, TSC_SuplaChannelPack_C *channel_pack) { + return srpc_set_pack( + _srpc, channel_pack, channel_pack->count, + &srpc_channelpack_get_caption_size_c, &srpc_channelpack_get_item_ptr_c, + &srpc_channelpack_set_pack_count_c, sizeof(TSC_SuplaChannelPack_C), + SUPLA_CHANNELPACK_MAXCOUNT, SUPLA_CHANNEL_CAPTION_MAXSIZE, + sizeof(TSC_SuplaChannel_C), SUPLA_SC_CALL_CHANNELPACK_UPDATE_C); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_value_update( + void *_srpc, TSC_SuplaChannelValue *channel_value) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNEL_VALUE_UPDATE, + (char *)channel_value, sizeof(TSC_SuplaChannelValue)); +} + +unsigned _supla_int_t +srpc_channelgroup_pack_get_caption_size(void *pack, _supla_int_t idx) { + return ((TSC_SuplaChannelGroupPack *)pack)->items[idx].CaptionSize; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_pack_update( + void *_srpc, TSC_SuplaChannelGroupPack *channelgroup_pack) { + return srpc_set_pack( + _srpc, channelgroup_pack, channelgroup_pack->count, + &srpc_channelgroup_pack_get_caption_size, + &srpc_channelgroup_pack_get_item_ptr, + &srpc_channelgroup_pack_set_pack_count, sizeof(TSC_SuplaChannelGroupPack), + SUPLA_CHANNELGROUP_PACK_MAXCOUNT, SUPLA_CHANNELGROUP_CAPTION_MAXSIZE, + sizeof(TSC_SuplaChannelGroup), SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE); +} + +unsigned _supla_int_t +srpc_channelgroup_pack_b_get_caption_size(void *pack, _supla_int_t idx) { + return ((TSC_SuplaChannelGroupPack_B *)pack)->items[idx].CaptionSize; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_pack_update_b( + void *_srpc, TSC_SuplaChannelGroupPack_B *channelgroup_pack) { + return srpc_set_pack( + _srpc, channelgroup_pack, channelgroup_pack->count, + &srpc_channelgroup_pack_b_get_caption_size, + &srpc_channelgroup_pack_b_get_item_ptr, + &srpc_channelgroup_pack_b_set_pack_count, + sizeof(TSC_SuplaChannelGroupPack_B), SUPLA_CHANNELGROUP_PACK_MAXCOUNT, + SUPLA_CHANNELGROUP_CAPTION_MAXSIZE, sizeof(TSC_SuplaChannelGroup_B), + SUPLA_SC_CALL_CHANNELGROUP_PACK_UPDATE_B); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_relation_pack_update( + void *_srpc, + TSC_SuplaChannelGroupRelationPack *channelgroup_relation_pack) { + if (channelgroup_relation_pack->count < 1 || + channelgroup_relation_pack->count > + SUPLA_CHANNELGROUP_RELATION_PACK_MAXCOUNT) { + return 0; + } + + unsigned _supla_int_t size = sizeof(TSC_SuplaChannelGroupRelationPack) - + sizeof(channelgroup_relation_pack->items) + + (sizeof(TSC_SuplaChannelGroupRelation) * + channelgroup_relation_pack->count); + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNELGROUP_RELATION_PACK_UPDATE, + (char *)channelgroup_relation_pack, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelvalue_pack_update( + void *_srpc, TSC_SuplaChannelValuePack *channelvalue_pack) { + if (channelvalue_pack->count < 1 || + channelvalue_pack->count > SUPLA_CHANNELVALUE_PACK_MAXCOUNT) { + return 0; + } + + unsigned _supla_int_t size = + sizeof(TSC_SuplaChannelValuePack) - sizeof(channelvalue_pack->items) + + (sizeof(TSC_SuplaChannelValue) * channelvalue_pack->count); + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNELVALUE_PACK_UPDATE, + (char *)channelvalue_pack, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelextendedvalue_pack_update( + void *_srpc, TSC_SuplaChannelExtendedValuePack *extendedvalue_pack) { + if (extendedvalue_pack == NULL || extendedvalue_pack->count < 1 || + extendedvalue_pack->count > SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXCOUNT || + extendedvalue_pack->pack_size < 1 || + extendedvalue_pack->pack_size > + SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXDATASIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNELEXTENDEDVALUE_PACK_UPDATE, + (char *)extendedvalue_pack, + sizeof(TSC_SuplaChannelExtendedValuePack) - + SUPLA_CHANNELEXTENDEDVALUE_PACK_MAXDATASIZE + + extendedvalue_pack->pack_size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_get_next(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_GET_NEXT, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_event(void *_srpc, + TSC_SuplaEvent *event) { + _supla_int_t size = sizeof(TSC_SuplaEvent) - SUPLA_SENDER_NAME_MAXSIZE + + event->SenderNameSize; + + if (size > sizeof(TSC_SuplaEvent)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_EVENT, (char *)event, size); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_value(void *_srpc, TCS_SuplaChannelNewValue *value) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_CHANNEL_SET_VALUE, (char *)value, + sizeof(TCS_SuplaChannelNewValue)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_set_channel_value_b( + void *_srpc, TCS_SuplaChannelNewValue_B *value) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_CHANNEL_SET_VALUE_B, + (char *)value, sizeof(TCS_SuplaChannelNewValue_B)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_value(void *_srpc, TCS_SuplaNewValue *value) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_SET_VALUE, (char *)value, + sizeof(TCS_SuplaNewValue)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_oauth_token_request(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_OAUTH_TOKEN_REQUEST, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_oauth_token_request_result( + void *_srpc, TSC_OAuthTokenRequestResult *result) { + if (result == NULL || result->Token.TokenSize > SUPLA_OAUTH_TOKEN_MAXSIZE) { + return 0; + } + + return srpc_async_call( + _srpc, SUPLA_SC_CALL_OAUTH_TOKEN_REQUEST_RESULT, (char *)result, + sizeof(TSC_OAuthTokenRequestResult) - + (SUPLA_OAUTH_TOKEN_MAXSIZE - result->Token.TokenSize)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_superuser_authorization_request( + void *_srpc, TCS_SuperUserAuthorizationRequest *request) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_SUPERUSER_AUTHORIZATION_REQUEST, + (char *)request, + sizeof(TCS_SuperUserAuthorizationRequest)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_get_superuser_authorization_result(void *_srpc) { + return srpc_async_call( + _srpc, SUPLA_CS_CALL_GET_SUPERUSER_AUTHORIZATION_RESULT, NULL, 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_superuser_authorization_result( + void *_srpc, TSC_SuperUserAuthorizationResult *result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_SUPERUSER_AUTHORIZATION_RESULT, + (char *)result, + sizeof(TSC_SuperUserAuthorizationResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_calcfg_request( + void *_srpc, TCS_DeviceCalCfgRequest *request) { + if (request == NULL || request->DataSize > SUPLA_CALCFG_DATA_MAXSIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST, + (char *)request, + sizeof(TCS_DeviceCalCfgRequest) - + SUPLA_CALCFG_DATA_MAXSIZE + request->DataSize); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_calcfg_request_b( + void *_srpc, TCS_DeviceCalCfgRequest_B *request) { + if (request == NULL || request->DataSize > SUPLA_CALCFG_DATA_MAXSIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_CS_CALL_DEVICE_CALCFG_REQUEST_B, + (char *)request, + sizeof(TCS_DeviceCalCfgRequest_B) - + SUPLA_CALCFG_DATA_MAXSIZE + request->DataSize); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_device_calcfg_result( + void *_srpc, TSC_DeviceCalCfgResult *result) { + if (result == NULL || result->DataSize > SUPLA_CALCFG_DATA_MAXSIZE) { + return 0; + } + + return srpc_async_call(_srpc, SUPLA_SC_CALL_DEVICE_CALCFG_RESULT, + (char *)result, + sizeof(TSC_DeviceCalCfgResult) - + SUPLA_CALCFG_DATA_MAXSIZE + result->DataSize); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_get_channel_basic_cfg(void *_srpc, _supla_int_t ChannelID) { + TCS_ChannelBasicCfgRequest request; + memset(&request, 0, sizeof(TCS_ChannelBasicCfgRequest)); + request.ChannelID = ChannelID; + + return srpc_async_call(_srpc, SUPLA_CS_CALL_GET_CHANNEL_BASIC_CFG, + (char *)&request, sizeof(TCS_ChannelBasicCfgRequest)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_basic_cfg_result( + void *_srpc, TSC_ChannelBasicCfg *basic_cfg) { + _supla_int_t size = sizeof(TSC_ChannelBasicCfg) - + SUPLA_CHANNEL_CAPTION_MAXSIZE + basic_cfg->CaptionSize; + + if (size > sizeof(TSC_ChannelBasicCfg)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_CHANNEL_BASIC_CFG_RESULT, + (char *)basic_cfg, size); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_function(void *_srpc, TCS_SetChannelFunction *func) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_SET_CHANNEL_FUNCTION, + (char *)func, sizeof(TCS_SetChannelFunction)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_channel_function_result( + void *_srpc, TSC_SetChannelFunctionResult *result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_SET_CHANNEL_FUNCTION_RESULT, + (char *)result, sizeof(TSC_SetChannelFunctionResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_caption(void *_srpc, TCS_SetChannelCaption *caption) { + _supla_int_t size = sizeof(TCS_SetChannelCaption) - + SUPLA_CHANNEL_CAPTION_MAXSIZE + caption->CaptionSize; + + if (size > sizeof(TCS_SetChannelCaption)) return 0; + + return srpc_async_call(_srpc, SUPLA_CS_CALL_SET_CHANNEL_CAPTION, + (char *)caption, size); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_channel_caption_result( + void *_srpc, TSC_SetChannelCaptionResult *result) { + _supla_int_t size = sizeof(TSC_SetChannelCaptionResult) - + SUPLA_CHANNEL_CAPTION_MAXSIZE + result->CaptionSize; + + if (size > sizeof(TSC_SetChannelCaptionResult)) return 0; + + return srpc_async_call(_srpc, SUPLA_SC_CALL_SET_CHANNEL_CAPTION_RESULT, + (char *)result, size); +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_clients_reconnect_request(void *_srpc) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_CLIENTS_RECONNECT_REQUEST, NULL, + 0); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_clients_reconnect_request_result( + void *_srpc, TSC_ClientsReconnectRequestResult *result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_CLIENTS_RECONNECT_REQUEST_RESULT, + (char *)result, + sizeof(TSC_ClientsReconnectRequestResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_set_registration_enabled( + void *_srpc, TCS_SetRegistrationEnabled *reg_enabled) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_SET_REGISTRATION_ENABLED, + (char *)reg_enabled, + sizeof(TCS_SetRegistrationEnabled)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_registration_enabled_result( + void *_srpc, TSC_SetRegistrationEnabledResult *result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_SET_REGISTRATION_ENABLED_RESULT, + (char *)result, + sizeof(TSC_SetRegistrationEnabledResult)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_reconnect_request( + void *_srpc, TCS_DeviceReconnectRequest *request) { + return srpc_async_call(_srpc, SUPLA_CS_CALL_DEVICE_RECONNECT_REQUEST, + (char *)request, sizeof(TCS_DeviceReconnectRequest)); +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_device_reconnect_request_result( + void *_srpc, TSC_DeviceReconnectRequestResult *result) { + return srpc_async_call(_srpc, SUPLA_SC_CALL_DEVICE_RECONNECT_REQUEST_RESULT, + (char *)result, + sizeof(TSC_DeviceReconnectRequestResult)); +} + +#endif /*SRPC_EXCLUDE_CLIENT*/ + +#ifndef SRPC_EXCLUDE_EXTENDEDVALUE_TOOLS + +#ifdef USE_DEPRECATED_EMEV_V1 +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_emextended2extended( + TElectricityMeter_ExtendedValue *em_ev, TSuplaChannelExtendedValue *ev) { + if (em_ev == NULL || ev == NULL || em_ev->m_count > EM_MEASUREMENT_COUNT || + em_ev->m_count < 0) { + return 0; + } + + memset(ev, 0, sizeof(TSuplaChannelExtendedValue)); + ev->type = EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V1; + + ev->size = sizeof(TElectricityMeter_ExtendedValue) - + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT + + sizeof(TElectricityMeter_Measurement) * em_ev->m_count; + + if (ev->size > 0 && ev->size <= SUPLA_CHANNELEXTENDEDVALUE_SIZE) { + memcpy(ev->value, em_ev, ev->size); + return 1; + } + + ev->size = 0; + return 0; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2emextended( + TSuplaChannelExtendedValue *ev, TElectricityMeter_ExtendedValue *em_ev) { + if (em_ev == NULL || ev == NULL || + ev->type != EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V1 || ev->size == 0 || + ev->size > sizeof(TElectricityMeter_ExtendedValue)) { + return 0; + } + + memset(em_ev, 0, sizeof(TElectricityMeter_ExtendedValue)); + memcpy(em_ev, ev->value, ev->size); + + _supla_int_t expected_size = 0; + + if (em_ev->m_count <= EM_MEASUREMENT_COUNT) { + expected_size = + sizeof(TElectricityMeter_ExtendedValue) - + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT + + sizeof(TElectricityMeter_Measurement) * em_ev->m_count; + } + + if (ev->size != expected_size) { + memset(em_ev, 0, sizeof(TElectricityMeter_ExtendedValue)); + return 0; + } + + return 1; +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_evtool_emev_v1to2(TElectricityMeter_ExtendedValue *v1, + TElectricityMeter_ExtendedValue_V2 *v2) { + if (v1 == NULL || v2 == NULL) { + return 0; + } + memset(v2, 0, sizeof(TElectricityMeter_ExtendedValue_V2)); + + for (int a = 0; a < 3; a++) { + v2->total_forward_active_energy[a] = v1->total_forward_active_energy[a]; + v2->total_reverse_active_energy[a] = v1->total_reverse_active_energy[a]; + v2->total_forward_reactive_energy[a] = v1->total_forward_reactive_energy[a]; + v2->total_reverse_reactive_energy[a] = v1->total_reverse_reactive_energy[a]; + } + + v2->total_cost = v1->total_cost; + v2->price_per_unit = v1->price_per_unit; + memcpy(v2->currency, v1->currency, sizeof(v2->currency)); + v2->measured_values = v1->measured_values; + v2->period = v1->period; + v2->m_count = v1->m_count; + + memcpy(v2->m, v1->m, + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT); + + v2->measured_values ^= + v1->measured_values & EM_VAR_FORWARD_ACTIVE_ENERGY_BALANCED; + v2->measured_values ^= + v1->measured_values & EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED; + + return 1; +} + +_supla_int_t SRPC_ICACHE_FLASH +srpc_evtool_emev_v2to1(TElectricityMeter_ExtendedValue_V2 *v2, + TElectricityMeter_ExtendedValue *v1) { + if (v1 == NULL || v2 == NULL) { + return 0; + } + memset(v1, 0, sizeof(TElectricityMeter_ExtendedValue)); + + for (int a = 0; a < 3; a++) { + v1->total_forward_active_energy[a] = v2->total_forward_active_energy[a]; + v1->total_reverse_active_energy[a] = v2->total_reverse_active_energy[a]; + v1->total_forward_reactive_energy[a] = v2->total_forward_reactive_energy[a]; + v1->total_reverse_reactive_energy[a] = v2->total_reverse_reactive_energy[a]; + } + + v1->total_cost = v2->total_cost; + v1->price_per_unit = v2->price_per_unit; + memcpy(v1->currency, v2->currency, sizeof(v2->currency)); + v1->measured_values = v2->measured_values; + v1->period = v2->period; + v1->m_count = v2->m_count; + + memcpy(v1->m, v2->m, + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT); + + v1->measured_values ^= + v1->measured_values & EM_VAR_FORWARD_ACTIVE_ENERGY_BALANCED; + v1->measured_values ^= + v1->measured_values & EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED; + + return 1; +} + +#endif /*USE_DEPRECATED_EMEV_V1*/ + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v2_emextended2extended( + TElectricityMeter_ExtendedValue_V2 *em_ev, TSuplaChannelExtendedValue *ev) { + if (em_ev == NULL || ev == NULL || em_ev->m_count > EM_MEASUREMENT_COUNT || + em_ev->m_count < 0) { + return 0; + } + + memset(ev, 0, sizeof(TSuplaChannelExtendedValue)); + ev->type = EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V2; + + ev->size = sizeof(TElectricityMeter_ExtendedValue_V2) - + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT + + sizeof(TElectricityMeter_Measurement) * em_ev->m_count; + + if (ev->size > 0 && ev->size <= SUPLA_CHANNELEXTENDEDVALUE_SIZE) { + memcpy(ev->value, em_ev, ev->size); + return 1; + } + + ev->size = 0; + return 0; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v2_extended2emextended( + TSuplaChannelExtendedValue *ev, TElectricityMeter_ExtendedValue_V2 *em_ev) { + if (em_ev == NULL || ev == NULL || + ev->type != EV_TYPE_ELECTRICITY_METER_MEASUREMENT_V2 || ev->size == 0 || + ev->size > sizeof(TElectricityMeter_ExtendedValue_V2)) { + return 0; + } + + memset(em_ev, 0, sizeof(TElectricityMeter_ExtendedValue_V2)); + memcpy(em_ev, ev->value, ev->size); + + _supla_int_t expected_size = 0; + + if (em_ev->m_count <= EM_MEASUREMENT_COUNT) { + expected_size = + sizeof(TElectricityMeter_ExtendedValue_V2) - + sizeof(TElectricityMeter_Measurement) * EM_MEASUREMENT_COUNT + + sizeof(TElectricityMeter_Measurement) * em_ev->m_count; + } + + if (ev->size != expected_size) { + memset(em_ev, 0, sizeof(TElectricityMeter_ExtendedValue_V2)); + return 0; + } + + return 1; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2thermostatextended( + TSuplaChannelExtendedValue *ev, TThermostat_ExtendedValue *th_ev) { + if (ev == NULL || th_ev == NULL || + ev->type != EV_TYPE_THERMOSTAT_DETAILS_V1 || ev->size == 0 || + ev->size > sizeof(TThermostat_ExtendedValue)) { + return 0; + } + + memset(th_ev, 0, sizeof(TThermostat_ExtendedValue)); + memcpy(th_ev, ev->value, ev->size); + + return 1; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_thermostatextended2extended( + TThermostat_ExtendedValue *th_ev, TSuplaChannelExtendedValue *ev) { + if (th_ev == NULL || ev == NULL) { + return 0; + } + + memset(ev, 0, sizeof(TSuplaChannelExtendedValue)); + ev->type = EV_TYPE_THERMOSTAT_DETAILS_V1; + ev->size = 0; + + unsigned _supla_int_t size = sizeof(TThermostat_ExtendedValue); + + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_Schedule)) { + size -= sizeof(th_ev->Schedule); + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_Time)) { + size -= sizeof(th_ev->Time); + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_Values)) { + size -= sizeof(th_ev->Values); + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_Flags)) { + size -= sizeof(th_ev->Flags); + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_PresetTemperatures)) { + size -= sizeof(th_ev->PresetTemperature); + if (0 == (th_ev->Fields & THERMOSTAT_FIELD_MeasuredTemperatures)) { + size -= sizeof(th_ev->MeasuredTemperature); + } + } + } + } + } + } + + if (size > 0) { + ev->size = size; + memcpy(ev->value, th_ev, size); + return 1; + } + + return 0; +} + +#ifndef SRPC_EXCLUDE_CLIENT +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_icextended2extended( + TSC_ImpulseCounter_ExtendedValue *ic_ev, TSuplaChannelExtendedValue *ev) { + if (ic_ev == NULL || ev == NULL) { + return 0; + } + + memset(ev, 0, sizeof(TSuplaChannelExtendedValue)); + ev->type = EV_TYPE_IMPULSE_COUNTER_DETAILS_V1; + ev->size = sizeof(TSC_ImpulseCounter_ExtendedValue); + + memcpy(ev->value, ic_ev, ev->size); + return 1; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2icextended( + TSuplaChannelExtendedValue *ev, TSC_ImpulseCounter_ExtendedValue *ic_ev) { + if (ic_ev == NULL || ev == NULL || + ev->type != EV_TYPE_IMPULSE_COUNTER_DETAILS_V1 || ev->size == 0 || + ev->size != sizeof(TSC_ImpulseCounter_ExtendedValue)) { + return 0; + } + + memset(ic_ev, 0, sizeof(TSC_ImpulseCounter_ExtendedValue)); + memcpy(ic_ev, ev->value, ev->size); + + return 1; +} +#endif /*SRPC_EXCLUDE_CLIENT*/ + +#endif /*SRPC_EXCLUDE_EXTENDEDVALUE_TOOLS*/ diff --git a/lib/SuplaDevice/src/supla-common/srpc.h b/lib/SuplaDevice/src/supla-common/srpc.h new file mode 100644 index 00000000..7559298e --- /dev/null +++ b/lib/SuplaDevice/src/supla-common/srpc.h @@ -0,0 +1,398 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef supladex_H_ +#define supladex_H_ + +#include +#include +#include "eh.h" +#include "proto.h" +#if defined(ESP32) +#include +#endif + +#ifdef __ANDROID__ +#define SRPC_EXCLUDE_DEVICE +#endif /*__ANDROID__*/ + +#if defined(ESP8266) || defined(ESP32) + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#define SRPC_WITHOUT_OUT_QUEUE +#define SRPC_WITHOUT_IN_QUEUE +#endif /* defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) */ + +#define SRPC_EXCLUDE_CLIENT +#define SRPC_ICACHE_FLASH ICACHE_FLASH_ATTR + +#include +#if !defined(ESP32) +#include +#endif +#else +#define SRPC_ICACHE_FLASH +#endif + +#if defined(__AVR__) +#define SRPC_EXCLUDE_CLIENT +#define SRPC_WITHOUT_OUT_QUEUE +#define SRPC_WITHOUT_IN_QUEUE +#endif /*__AVR__*/ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef _supla_int_t (*_func_srpc_DataRW)(void *buf, _supla_int_t count, + void *user_params); +typedef void (*_func_srpc_event_OnRemoteCallReceived)( + void *_srpc, unsigned _supla_int_t rr_id, unsigned _supla_int_t call_type, + void *user_params, unsigned char proto_version); +typedef void (*_func_srpc_event_BeforeCall)(void *_srpc, + unsigned _supla_int_t call_type, + void *user_params); +typedef void (*_func_srpc_event_OnVersionError)(void *_srpc, + unsigned char remote_version, + void *user_params); +typedef void (*_func_srpc_event_OnMinVersionRequired)( + void *_srpc, unsigned _supla_int_t call_type, unsigned char min_version, + void *user_params); + +typedef struct { + _func_srpc_DataRW data_read; + _func_srpc_DataRW data_write; + _func_srpc_event_OnRemoteCallReceived on_remote_call_received; + _func_srpc_event_OnVersionError on_version_error; + _func_srpc_event_BeforeCall before_async_call; + _func_srpc_event_OnMinVersionRequired on_min_version_required; + + TEventHandler *eh; + + void *user_params; +} TsrpcParams; + +union TsrpcDataPacketData { + TDCS_SuplaPingServer *dcs_ping; + TSDC_SuplaPingServerResult *sdc_ping_result; + TSDC_SuplaGetVersionResult *sdc_getversion_result; + TSDC_SuplaVersionError *sdc_version_error; + TDCS_SuplaSetActivityTimeout *dcs_set_activity_timeout; + TSDC_SuplaSetActivityTimeoutResult *sdc_set_activity_timeout_result; + TDS_SuplaRegisterDevice *ds_register_device; + TDS_SuplaRegisterDevice_B *ds_register_device_b; + TDS_SuplaRegisterDevice_C *ds_register_device_c; + TDS_SuplaRegisterDevice_D *ds_register_device_d; + TDS_SuplaRegisterDevice_E *ds_register_device_e; + TSD_SuplaRegisterDeviceResult *sd_register_device_result; + TCS_SuplaRegisterClient *cs_register_client; + TCS_SuplaRegisterClient_B *cs_register_client_b; + TCS_SuplaRegisterClient_C *cs_register_client_c; + TCS_SuplaRegisterClient_D *cs_register_client_d; + TSC_SuplaRegisterClientResult *sc_register_client_result; + TSC_SuplaRegisterClientResult_B *sc_register_client_result_b; + TDS_SuplaDeviceChannelValue *ds_device_channel_value; + TDS_SuplaDeviceChannelValue_B *ds_device_channel_value_b; + TDS_SuplaDeviceChannelValue_C *ds_device_channel_value_c; + TDS_SuplaDeviceChannelExtendedValue *ds_device_channel_extendedvalue; + TSC_SuplaLocation *sc_location; + TSC_SuplaLocationPack *sc_location_pack; + TSC_SuplaChannel *sc_channel; + TSC_SuplaChannel_B *sc_channel_b; + TSC_SuplaChannel_C *sc_channel_c; + TSC_SuplaChannelPack *sc_channel_pack; + TSC_SuplaChannelPack_B *sc_channel_pack_b; + TSC_SuplaChannelPack_C *sc_channel_pack_c; + TSC_SuplaChannelValue *sc_channel_value; + TSC_SuplaEvent *sc_event; + TSD_SuplaChannelNewValue *sd_channel_new_value; + TSD_SuplaChannelGroupNewValue *sd_channelgroup_new_value; + TDS_SuplaChannelNewValueResult *ds_channel_new_value_result; + TCS_SuplaChannelNewValue *cs_channel_new_value; + TCS_SuplaChannelNewValue_B *cs_channel_new_value_b; + TDS_FirmwareUpdateParams *ds_firmware_update_params; + TSD_FirmwareUpdate_UrlResult *sc_firmware_update_url_result; + TSDC_RegistrationEnabled *sdc_reg_enabled; + TSC_SuplaChannelGroupPack *sc_channelgroup_pack; + TSC_SuplaChannelGroupPack_B *sc_channelgroup_pack_b; + TSC_SuplaChannelGroupRelationPack *sc_channelgroup_relation_pack; + TSC_SuplaChannelValuePack *sc_channelvalue_pack; + TSC_SuplaChannelExtendedValuePack *sc_channelextendedvalue_pack; + TCS_SuplaNewValue *cs_new_value; + TSC_OAuthTokenRequestResult *sc_oauth_tokenrequest_result; + TCS_SuperUserAuthorizationRequest *cs_superuser_authorization_request; + TSC_SuperUserAuthorizationResult *sc_superuser_authorization_result; + TCS_DeviceCalCfgRequest *cs_device_calcfg_request; + TCS_DeviceCalCfgRequest_B *cs_device_calcfg_request_b; + TSC_DeviceCalCfgResult *sc_device_calcfg_result; + TSD_DeviceCalCfgRequest *sd_device_calcfg_request; + TDS_DeviceCalCfgResult *ds_device_calcfg_result; + TSDC_UserLocalTimeResult *sdc_user_localtime_result; + TCSD_ChannelStateRequest *csd_channel_state_request; + TDSC_ChannelState *dsc_channel_state; + TCS_ChannelBasicCfgRequest *cs_channel_basic_cfg_request; + TSC_ChannelBasicCfg *sc_channel_basic_cfg; + TCS_SetChannelFunction *cs_set_channel_function; + TSC_SetChannelFunctionResult *sc_set_channel_function_result; + TCS_SetChannelCaption *cs_set_channel_caption; + TSC_SetChannelCaptionResult *sc_set_channel_caption_result; + TSC_ClientsReconnectRequestResult *sc_clients_reconnect_result; + TCS_SetRegistrationEnabled *cs_set_registration_enabled; + TSC_SetRegistrationEnabledResult *sc_set_registration_enabled_result; + TCS_DeviceReconnectRequest *cs_device_reconnect_request; + TSC_DeviceReconnectRequestResult *sc_device_reconnect_request_result; + TSD_ChannelFunctions *sd_channel_functions; +}; + +typedef struct { + unsigned _supla_int_t call_type; + unsigned _supla_int_t rr_id; + + union TsrpcDataPacketData data; +} TsrpcReceivedData; + +void SRPC_ICACHE_FLASH srpc_params_init(TsrpcParams *params); + +void *SRPC_ICACHE_FLASH srpc_init(TsrpcParams *params); +void SRPC_ICACHE_FLASH srpc_free(void *_srpc); + +char SRPC_ICACHE_FLASH srpc_input_dataexists(void *_srpc); +char SRPC_ICACHE_FLASH srpc_output_dataexists(void *_srpc); +unsigned char SRPC_ICACHE_FLASH srpc_out_queue_item_count(void *srpc); + +char SRPC_ICACHE_FLASH srpc_iterate(void *_srpc); + +char SRPC_ICACHE_FLASH srpc_getdata(void *_srpc, TsrpcReceivedData *rd, + unsigned _supla_int_t rr_id); + +void SRPC_ICACHE_FLASH srpc_rd_free(TsrpcReceivedData *rd); + +unsigned char SRPC_ICACHE_FLASH srpc_get_proto_version(void *_srpc); +void SRPC_ICACHE_FLASH srpc_set_proto_version(void *_srpc, + unsigned char version); + +unsigned char SRPC_ICACHE_FLASH +srpc_call_min_version_required(void *_srpc, unsigned _supla_int_t call_type); +unsigned char SRPC_ICACHE_FLASH +srpc_call_allowed(void *_srpc, unsigned _supla_int_t call_type); + +// device/client <-> server +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_getversion(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_getversion_result( + void *_srpc, char SoftVer[SUPLA_SOFTVER_MAXSIZE]); +_supla_int_t SRPC_ICACHE_FLASH +srpc_sdc_async_versionerror(void *_srpc, unsigned char remote_version); +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_ping_server(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_ping_server_result(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_set_activity_timeout( + void *_srpc, TDCS_SuplaSetActivityTimeout *dcs_set_activity_timeout); +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_set_activity_timeout_result( + void *_srpc, + TSDC_SuplaSetActivityTimeoutResult *sdc_set_activity_timeout_result); +_supla_int_t SRPC_ICACHE_FLASH +srpc_dcs_async_get_registration_enabled(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_get_registration_enabled_result( + void *_srpc, TSDC_RegistrationEnabled *reg_enabled); +_supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_get_user_localtime(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sdc_async_get_user_localtime_result( + void *_srpc, TSDC_UserLocalTimeResult *localtime); +_supla_int_t SRPC_ICACHE_FLASH srpc_csd_async_get_channel_state( + void *_srpc, TCSD_ChannelStateRequest *request); +_supla_int_t SRPC_ICACHE_FLASH +srpc_csd_async_channel_state_result(void *_srpc, TDSC_ChannelState *state); + +#ifndef SRPC_EXCLUDE_DEVICE +// device <-> server +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice( + void *_srpc, TDS_SuplaRegisterDevice *registerdevice); +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_b( + void *_srpc, TDS_SuplaRegisterDevice_B *registerdevice); // ver. >= 2 +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_c( + void *_srpc, TDS_SuplaRegisterDevice_C *registerdevice); // ver. >= 6 +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_d( + void *_srpc, TDS_SuplaRegisterDevice_D *registerdevice); // ver. >= 7 +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_registerdevice_e( + void *_srpc, TDS_SuplaRegisterDevice_E *registerdevice); // ver. >= 10 +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_registerdevice_result( + void *_srpc, TSD_SuplaRegisterDeviceResult *registerdevice_result); +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed( + void *_srpc, unsigned char channel_number, char *value); +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_channel_value_changed_b(void *_srpc, unsigned char channel_number, + char *value, unsigned char offline); +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed_c( + void *_srpc, unsigned char channel_number, char *value, + unsigned char offline, unsigned _supla_int_t validity_time_sec); +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_extendedvalue_changed( + void *_srpc, unsigned char channel_number, + TSuplaChannelExtendedValue *value); +_supla_int_t SRPC_ICACHE_FLASH +srpc_sd_async_set_channel_value(void *_srpc, TSD_SuplaChannelNewValue *value); +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_set_channelgroup_value( + void *_srpc, TSD_SuplaChannelGroupNewValue *value); // ver. >= 13 +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_set_channel_result(void *_srpc, unsigned char ChannelNumber, + _supla_int_t SenderID, char Success); +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_firmware_update_url( + void *_srpc, TDS_FirmwareUpdateParams *params); +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_firmware_update_url_result( + void *_srpc, TSD_FirmwareUpdate_UrlResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_device_calcfg_request( + void *_srpc, TSD_DeviceCalCfgRequest *request); +_supla_int_t SRPC_ICACHE_FLASH +srpc_ds_async_device_calcfg_result(void *_srpc, TDS_DeviceCalCfgResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_get_channel_functions(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sd_async_get_channel_functions_result( + void *_srpc, TSD_ChannelFunctions *result); + +#endif /*SRPC_EXCLUDE_DEVICE*/ + +#ifndef SRPC_EXCLUDE_CLIENT +// client <-> server +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient( + void *_srpc, TCS_SuplaRegisterClient *registerclient); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_b( + void *_srpc, TCS_SuplaRegisterClient_B *registerclient); // ver. >= 6 +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_c( + void *_srpc, TCS_SuplaRegisterClient_C *registerclient); // ver. >= 7 +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_registerclient_d( + void *_srpc, TCS_SuplaRegisterClient_D *registerclient); // ver. >= 11 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_registerclient_result( + void *_srpc, TSC_SuplaRegisterClientResult *registerclient_result); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_registerclient_result_b( + void *_srpc, + TSC_SuplaRegisterClientResult_B *registerclient_result); // ver. >= 9 +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_location_update(void *_srpc, TSC_SuplaLocation *location); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_locationpack_update( + void *_srpc, TSC_SuplaLocationPack *location_pack); +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_channel_update(void *_srpc, TSC_SuplaChannel *channel); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_update_b( + void *_srpc, TSC_SuplaChannel_B *channel); // ver. >= 8 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_update_c( + void *_srpc, TSC_SuplaChannel_C *channel); // ver. >= 10 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update( + void *_srpc, TSC_SuplaChannelPack *channel_pack); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update_b( + void *_srpc, TSC_SuplaChannelPack_B *channel_pack); // ver. >= 8 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelpack_update_c( + void *_srpc, TSC_SuplaChannelPack_C *channel_pack); // ver. >= 10 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_value_update( + void *_srpc, TSC_SuplaChannelValue *channel_item_value); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_pack_update( + void *_srpc, TSC_SuplaChannelGroupPack *channelgroup_pack); // ver. >= 9 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_pack_update_b( + void *_srpc, TSC_SuplaChannelGroupPack_B *channelgroup_pack); // ver. >= 10 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelgroup_relation_pack_update( + void *_srpc, TSC_SuplaChannelGroupRelationPack + *channelgroup_relation_pack); // ver. >= 9 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelvalue_pack_update( + void *_srpc, TSC_SuplaChannelValuePack *channelvalue_pack); // ver. >= 9 +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channelextendedvalue_pack_update( + void *_srpc, + TSC_SuplaChannelExtendedValuePack *extendedvalue_pack); // ver. >= 10 +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_get_next(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_event(void *_srpc, + TSC_SuplaEvent *event); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_value(void *_srpc, TCS_SuplaChannelNewValue *value); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_value(void *_srpc, TCS_SuplaNewValue *value); // ver. >= 9 +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_set_channel_value_b( + void *_srpc, TCS_SuplaChannelNewValue_B *value); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_oauth_token_request(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_oauth_token_request_result( + void *_srpc, TSC_OAuthTokenRequestResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_superuser_authorization_request( + void *_srpc, TCS_SuperUserAuthorizationRequest *request); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_get_superuser_authorization_result(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_superuser_authorization_result( + void *_srpc, TSC_SuperUserAuthorizationResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_calcfg_request( + void *_srpc, TCS_DeviceCalCfgRequest *request); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_calcfg_request_b( + void *_srpc, TCS_DeviceCalCfgRequest_B *request); +_supla_int_t SRPC_ICACHE_FLASH +srpc_sc_async_device_calcfg_result(void *_srpc, TSC_DeviceCalCfgResult *result); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_get_channel_basic_cfg(void *_srpc, _supla_int_t ChannelID); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_channel_basic_cfg_result( + void *_srpc, TSC_ChannelBasicCfg *basic_cfg); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_function(void *_srpc, TCS_SetChannelFunction *func); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_channel_function_result( + void *_srpc, TSC_SetChannelFunctionResult *result); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_set_channel_caption(void *_srpc, TCS_SetChannelCaption *caption); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_channel_caption_result( + void *_srpc, TSC_SetChannelCaptionResult *caption); +_supla_int_t SRPC_ICACHE_FLASH +srpc_cs_async_clients_reconnect_request(void *_srpc); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_clients_reconnect_request_result( + void *_srpc, TSC_ClientsReconnectRequestResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_set_registration_enabled( + void *_srpc, TCS_SetRegistrationEnabled *reg_enabled); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_set_registration_enabled_result( + void *_srpc, TSC_SetRegistrationEnabledResult *result); +_supla_int_t SRPC_ICACHE_FLASH srpc_cs_async_device_reconnect_request( + void *_srpc, TCS_DeviceReconnectRequest *request); +_supla_int_t SRPC_ICACHE_FLASH srpc_sc_async_device_reconnect_request_result( + void *_srpc, TSC_DeviceReconnectRequestResult *result); +#endif /*SRPC_EXCLUDE_CLIENT*/ + +#ifndef SRPC_EXCLUDE_EXTENDEDVALUE_TOOLS +#ifdef USE_DEPRECATED_EMEV_V1 +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_emextended2extended( + TElectricityMeter_ExtendedValue *em_ev, TSuplaChannelExtendedValue *ev); +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2emextended( + TSuplaChannelExtendedValue *ev, TElectricityMeter_ExtendedValue *em_ev); + +_supla_int_t SRPC_ICACHE_FLASH +srpc_evtool_emev_v1to2(TElectricityMeter_ExtendedValue *v1, + TElectricityMeter_ExtendedValue_V2 *v2); +_supla_int_t SRPC_ICACHE_FLASH +srpc_evtool_emev_v2to1(TElectricityMeter_ExtendedValue_V2 *v2, + TElectricityMeter_ExtendedValue *v1); + +#endif /*USE_DEPRECATED_EMEV_V1*/ + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v2_emextended2extended( + TElectricityMeter_ExtendedValue_V2 *em_ev, TSuplaChannelExtendedValue *ev); +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v2_extended2emextended( + TSuplaChannelExtendedValue *ev, TElectricityMeter_ExtendedValue_V2 *em_ev); + +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2thermostatextended( + TSuplaChannelExtendedValue *ev, TThermostat_ExtendedValue *th_ev); +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_thermostatextended2extended( + TThermostat_ExtendedValue *th_ev, TSuplaChannelExtendedValue *ev); + +#ifndef SRPC_EXCLUDE_CLIENT +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_icextended2extended( + TSC_ImpulseCounter_ExtendedValue *ic_ev, TSuplaChannelExtendedValue *ev); +_supla_int_t SRPC_ICACHE_FLASH srpc_evtool_v1_extended2icextended( + TSuplaChannelExtendedValue *ev, TSC_ImpulseCounter_ExtendedValue *ic_ev); +#endif /*SRPC_EXCLUDE_CLIENT*/ + +#endif /*SRPC_EXCLUDE_EXTENDEDVALUE_TOOLS*/ + +#ifdef __cplusplus +} +#endif + +#endif /* supladex_H_ */ diff --git a/lib/SuplaDevice/src/supla/actions.h b/lib/SuplaDevice/src/supla/actions.h new file mode 100644 index 00000000..e44a2647 --- /dev/null +++ b/lib/SuplaDevice/src/supla/actions.h @@ -0,0 +1,82 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _actions_h +#define _actions_h + +// Actions are used in triggerable elements. They are grouped by most common +// usage, but you should not rely on it. Please check exact supported actions +// in triggerable element documentation + +namespace Supla { +enum Action { + // Relays + TURN_ON, + TURN_OFF, + TOGGLE, + + // Settable binary sensors + SET, + CLEAR, + + // Roller shutters + OPEN, + CLOSE, + STOP, + OPEN_OR_STOP, + CLOSE_OR_STOP, + COMFORT_UP_POSITION, + COMFORT_DOWN_POSITION, + STEP_BY_STEP, + MOVE_UP, + MOVE_DOWN, + MOVE_UP_OR_STOP, + MOVE_DOWN_OR_STOP, + + // Dimmable light, RGB(W) LEDs + BRIGHTEN_ALL, + DIM_ALL, + BRIGHTEN_R, + BRIGHTEN_G, + BRIGHTEN_B, + BRIGHTEN_W, + BRIGHTEN_RGB, + DIM_R, + DIM_G, + DIM_B, + DIM_W, + DIM_RGB, + TURN_ON_RGB, + TURN_OFF_RGB, + TOGGLE_RGB, + TURN_ON_W, + TURN_OFF_W, + TOGGLE_W, + TURN_ON_RGB_DIMMED, + TURN_ON_W_DIMMED, + TURN_ON_ALL_DIMMED, + ITERATE_DIM_RGB, + ITERATE_DIM_W, + ITERATE_DIM_ALL, + + // Impulse counter + RESET + +}; + +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/channel.cpp b/lib/SuplaDevice/src/supla/channel.cpp new file mode 100644 index 00000000..b6c9a658 --- /dev/null +++ b/lib/SuplaDevice/src/supla/channel.cpp @@ -0,0 +1,286 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "supla/channel.h" +#include "supla-common/log.h" +#include "supla-common/srpc.h" +#include "tools.h" + +namespace Supla { +Channel *Channel::firstPtr = nullptr; + +unsigned long Channel::lastCommunicationTimeMs = 0; +TDS_SuplaRegisterDevice_E Channel::reg_dev; + +Channel::Channel() { + valueChanged = false; + channelNumber = -1; + if (reg_dev.channel_count < SUPLA_CHANNELMAXCOUNT) { + channelNumber = reg_dev.channel_count; + reg_dev.channels[channelNumber].Number = channelNumber; + reg_dev.channel_count++; + } else { +// TODO: add status CHANNEL_LIMIT_EXCEEDED + } + + if (firstPtr == nullptr) { + firstPtr = this; + } else { + last()->nextPtr = this; + } + nextPtr = nullptr; + setFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); +} + +Channel *Channel::begin() { + return firstPtr; +} + +Channel *Channel::last() { + Channel *ptr = firstPtr; + while (ptr && ptr->nextPtr) { + ptr = ptr->nextPtr; + } + return ptr; +} + +int Channel::size() { + int count = 0; + Channel *ptr = firstPtr; + if (ptr) { + count++; + } + while (ptr->nextPtr) { + count++; + ptr = ptr->nextPtr; + } + return count; +} + +void Channel::setNewValue(double dbl) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + if (sizeof(double) == 8) { + memcpy(newValue, &dbl, 8); + } else if (sizeof(double) == 4) { + float2DoublePacked(dbl, (uint8_t *)(newValue)); + } + if (setNewValue(newValue)) { + supla_log(LOG_DEBUG, "Channel(%d) value changed to %f", channelNumber, dbl); + } +} + +void Channel::setNewValue(double temp, double humi) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + long t = temp * 1000.00; + long h = humi * 1000.00; + + memcpy(newValue, &t, 4); + memcpy(&(newValue[4]), &h, 4); + + if (setNewValue(newValue)) { + supla_log(LOG_DEBUG, + "Channel(%d) value changed to temp(%f), humi(%f)", + channelNumber, + temp, + humi); + } +} + +void Channel::setNewValue(_supla_int64_t value) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + + memset(newValue, 0, SUPLA_CHANNELVALUE_SIZE); + + memcpy(newValue, &value, sizeof(_supla_int64_t)); + if (setNewValue(newValue)) { + supla_log( + LOG_DEBUG, "Channel(%d) value changed to %d", channelNumber, static_cast(value)); + } +} + +void Channel::setNewValue(int value) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + + memset(newValue, 0, SUPLA_CHANNELVALUE_SIZE); + + memcpy(newValue, &value, sizeof(int)); + if (setNewValue(newValue)) { + supla_log( + LOG_DEBUG, "Channel(%d) value changed to %d", channelNumber, value); + } +} + +void Channel::setNewValue(bool value) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + + memset(newValue, 0, SUPLA_CHANNELVALUE_SIZE); + + newValue[0] = value; + if (setNewValue(newValue)) { + supla_log( + LOG_DEBUG, "Channel(%d) value changed to %d", channelNumber, value); + } +} + +void Channel::setNewValue(TElectricityMeter_ExtendedValue_V2 &emValue) { + // Prepare standard channel value + if (sizeof(TElectricityMeter_Value) <= SUPLA_CHANNELVALUE_SIZE) { + TElectricityMeter_Measurement *m = nullptr; + TElectricityMeter_Value v; + memset(&v, 0, sizeof(TElectricityMeter_Value)); + + unsigned _supla_int64_t fae_sum = emValue.total_forward_active_energy[0] + + emValue.total_forward_active_energy[1] + + emValue.total_forward_active_energy[2]; + + v.total_forward_active_energy = fae_sum / 1000; + + if (emValue.m_count && emValue.measured_values & EM_VAR_VOLTAGE) { + m = &emValue.m[emValue.m_count - 1]; + + if (m->voltage[0] > 0) { + v.flags |= EM_VALUE_FLAG_PHASE1_ON; + } + + if (m->voltage[1] > 0) { + v.flags |= EM_VALUE_FLAG_PHASE2_ON; + } + + if (m->voltage[2] > 0) { + v.flags |= EM_VALUE_FLAG_PHASE3_ON; + } + } + + memcpy(reg_dev.channels[channelNumber].value, + &v, + sizeof(TElectricityMeter_Value)); + setUpdateReady(); + } +} + +bool Channel::setNewValue(char *newValue) { + if (memcmp(newValue, reg_dev.channels[channelNumber].value, 8) != 0) { + memcpy(reg_dev.channels[channelNumber].value, newValue, 8); + setUpdateReady(); + return true; + } + return false; +} + +void Channel::setType(_supla_int_t type) { + if (channelNumber >= 0) { + reg_dev.channels[channelNumber].Type = type; + } +} + +void Channel::setDefault(_supla_int_t value) { + if (channelNumber >= 0) { + reg_dev.channels[channelNumber].Default = value; + } +} + +void Channel::setFlag(_supla_int_t flag) { + if (channelNumber >= 0) { + reg_dev.channels[channelNumber].Flags |= flag; + } +} + +void Channel::unsetFlag(_supla_int_t flag) { + if (channelNumber >= 0) { + reg_dev.channels[channelNumber].Flags &= ~flag; + } +} + +void Channel::setFuncList(_supla_int_t functions) { + if (channelNumber >= 0) { + reg_dev.channels[channelNumber].FuncList = functions; + } +} + +int Channel::getChannelNumber() { + return channelNumber; +} + +void Channel::clearUpdateReady() { + valueChanged = false; +}; + +void Channel::sendUpdate(void *srpc) { + clearUpdateReady(); + srpc_ds_async_channel_value_changed( + srpc, channelNumber, reg_dev.channels[channelNumber].value); + + // returns null for non-extended channels + TSuplaChannelExtendedValue *extValue = getExtValue(); + if (extValue) { + srpc_ds_async_channel_extendedvalue_changed(srpc, channelNumber, extValue); + } + +} + +TSuplaChannelExtendedValue *Channel::getExtValue() { + return nullptr; +} + +void Channel::clearAllUpdateReady() { + for (auto channel = begin(); channel != nullptr; channel = channel->next()) { + if (!channel->isExtended()) { + channel->clearUpdateReady(); + } + } +} + +Channel *Channel::next() { + return nextPtr; +} + +void Channel::setUpdateReady() { + valueChanged = true; +}; + +bool Channel::isUpdateReady() { + return valueChanged; +}; + +bool Channel::isExtended() { + return false; +} + +void Channel::setNewValue(uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t colorBrightness, + uint8_t brightness) { + char newValue[SUPLA_CHANNELVALUE_SIZE]; + memset(newValue, 0, SUPLA_CHANNELVALUE_SIZE); + newValue[0] = brightness; + newValue[1] = colorBrightness; + newValue[2] = blue; + newValue[3] = green; + newValue[4] = red; + if (setNewValue(newValue)) { + supla_log(LOG_DEBUG, "Channel(%d) value changed to RGB(%d, %d, %d), colBr(%d), bright(%d)", channelNumber, red, green, blue, colorBrightness, brightness); + } +} + +_supla_int_t Channel::getChannelType() { + if (channelNumber >= 0) { + return reg_dev.channels[channelNumber].Type; + } + return -1; +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/channel.h b/lib/SuplaDevice/src/supla/channel.h new file mode 100644 index 00000000..c27b68a5 --- /dev/null +++ b/lib/SuplaDevice/src/supla/channel.h @@ -0,0 +1,78 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _channel_h +#define _channel_h + +#include + +#include "../supla-common/proto.h" + +namespace Supla { + +class Channel { + public: + Channel(); + + static Channel *begin(); + static Channel *last(); + static int size(); + + void setNewValue(double dbl); + void setNewValue(double temp, double humi); + void setNewValue(int value); + void setNewValue(bool value); + void setNewValue(TElectricityMeter_ExtendedValue_V2 &emValue); + void setNewValue(uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t colorBrightness, + uint8_t brightness); + void setNewValue(_supla_int64_t value); + bool setNewValue(char *newValue); + + virtual bool isExtended(); + bool isUpdateReady(); + int getChannelNumber(); + _supla_int_t getChannelType(); + + void setType(_supla_int_t type); + void setDefault(_supla_int_t value); + void setFlag(_supla_int_t flag); + void unsetFlag(_supla_int_t flag); + void setFuncList(_supla_int_t functions); + void clearUpdateReady(); + void sendUpdate(void *srpc); + virtual TSuplaChannelExtendedValue *getExtValue(); + static void clearAllUpdateReady(); + Channel *next(); + + static unsigned long lastCommunicationTimeMs; + static TDS_SuplaRegisterDevice_E reg_dev; + + protected: + void setUpdateReady(); + + bool valueChanged; + int channelNumber; + + Channel *nextPtr; + static Channel *firstPtr; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/channel_extended.cpp b/lib/SuplaDevice/src/supla/channel_extended.cpp new file mode 100644 index 00000000..623f3654 --- /dev/null +++ b/lib/SuplaDevice/src/supla/channel_extended.cpp @@ -0,0 +1,28 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "supla/channel_extended.h" + +namespace Supla { +bool ChannelExtended::isExtended() { + return true; +} + +TSuplaChannelExtendedValue *ChannelExtended::getExtValue() { + return &extValue; +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/channel_extended.h b/lib/SuplaDevice/src/supla/channel_extended.h new file mode 100644 index 00000000..d463e141 --- /dev/null +++ b/lib/SuplaDevice/src/supla/channel_extended.h @@ -0,0 +1,34 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _channel_extended_h +#define _channel_extended_h + +#include "channel.h" + +namespace Supla { +class ChannelExtended : public Channel { + public: + bool isExtended(); + TSuplaChannelExtendedValue *getExtValue(); + + protected: + TSuplaChannelExtendedValue extValue; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/clock/clock.cpp b/lib/SuplaDevice/src/supla/clock/clock.cpp new file mode 100644 index 00000000..39a78cc5 --- /dev/null +++ b/lib/SuplaDevice/src/supla/clock/clock.cpp @@ -0,0 +1,157 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "clock.h" +#include +#include "supla-common/srpc.h" + +using namespace Supla; + +Clock::Clock() : localtime(0), lastMillis(0), lastServerUpdate(0) {}; + +bool Clock::isReady() { + return true; +} + +int Clock::getYear() { + struct tm timeinfo; + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_year + 1900; +} + +int Clock::getMonth() { + struct tm timeinfo; +// timeinfo = gmtime(time(0)); + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_mon + 1; +} + +int Clock::getDay() { + struct tm timeinfo; +// timeinfo = gmtime(time(0)); + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_mday; +} + +int Clock::getDayOfWeek() { + struct tm timeinfo; +// timeinfo = gmtime(time(0)); + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_wday + 1; // 1 - Sunday, 2 - Monday ... +} + +int Clock::getHour() { + struct tm timeinfo; + //timeinfo = gmtime(time(0)); + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_hour; +} + +int Clock::getMin() { + struct tm timeinfo; + //timeinfo = gmtime(time(0)); + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_min; +} + +int Clock::getSec() { + struct tm timeinfo; + time_t currentTime = time(0); + gmtime_r(¤tTime, &timeinfo); + return timeinfo.tm_sec; +} + + +void Clock::parseLocaltimeFromServer(TSDC_UserLocalTimeResult *result) { + struct tm timeinfo; + memset(&timeinfo, 0, sizeof(timeinfo)); + + Serial.print(F("Current local time: ")); + Serial.print(getYear()); + Serial.print(F("-")); + Serial.print(getMonth()); + Serial.print(F("-")); + Serial.print(getDay()); + Serial.print(F(" ")); + Serial.print(getHour()); + Serial.print(F(":")); + Serial.print(getMin()); + Serial.print(F(":")); + Serial.println(getSec()); + + + + Serial.print(F("Received local time from server: ")); + Serial.print(result->year); + Serial.print(F("-")); + Serial.print(static_cast(result->month)); + Serial.print(F("-")); + Serial.print(static_cast(result->day)); + Serial.print(F(" ")); + Serial.print(static_cast(result->hour)); + Serial.print(F(":")); + Serial.print(static_cast(result->min)); + Serial.print(F(":")); + Serial.println(static_cast(result->sec)); + + timeinfo.tm_year = result->year - 1900; + timeinfo.tm_mon = result->month - 1; + timeinfo.tm_mday = result->day; + timeinfo.tm_hour = result->hour; + timeinfo.tm_min = result->min; + timeinfo.tm_sec = result->sec; + + localtime = mktime(&timeinfo); + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + timeval tv = { localtime, 0 }; + settimeofday(&tv, nullptr); +#elif defined(ARDUINO_ARCH_AVR) + set_system_time(mktime(&timeinfo)); +#endif + + +} + +void Clock::onTimer() { + unsigned long curMillis = millis(); + int seconds = (curMillis - lastMillis) / 1000; + if (seconds > 0) { + lastMillis = curMillis - ((curMillis - lastMillis) % 1000); + for (int i = 0; i < seconds; i++) { +#if defined(ARDUINO_ARCH_AVR) + system_tick(); +#endif + localtime++; + } + } + +} + +bool Clock::iterateConnected(void *srpc) { + if (lastServerUpdate == 0 || millis() - lastServerUpdate > 5*60000) { // update every 5 min + srpc_dcs_async_get_user_localtime(srpc); + lastServerUpdate = millis(); + return false; + } + return true; +} diff --git a/lib/SuplaDevice/src/supla/clock/clock.h b/lib/SuplaDevice/src/supla/clock/clock.h new file mode 100644 index 00000000..f282e8da --- /dev/null +++ b/lib/SuplaDevice/src/supla/clock/clock.h @@ -0,0 +1,52 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_clock_h +#define _supla_clock_h + +#include +#include +#include + +namespace Supla { + +class Clock : public Element { + public: + Clock(); + virtual bool isReady(); + virtual int getYear(); + virtual int getMonth(); + virtual int getDay(); + virtual int getDayOfWeek(); // 1 - Sunday, 2 - Monday + virtual int getHour(); + virtual int getMin(); + virtual int getSec(); + + void onTimer(); + bool iterateConnected(void *srpc); + + virtual void parseLocaltimeFromServer(TSDC_UserLocalTimeResult *result); + + protected: + time_t localtime; + unsigned long lastServerUpdate; + unsigned long lastMillis; + +}; + +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/control/bistable_relay.cpp b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp new file mode 100644 index 00000000..7efa96c9 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp @@ -0,0 +1,148 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "bistable_relay.h" + +using namespace Supla; +using namespace Control; + +#define STATE_ON_INIT_KEEP 2 + +BistableRelay::BistableRelay(int pin, + int statusPin, + bool statusPullUp, + bool statusHighIsOn, + bool highIsOn, + _supla_int_t functions) + : Relay(pin, highIsOn, functions), + statusPin(statusPin), + statusPullUp(statusPullUp), + statusHighIsOn(statusHighIsOn), + disarmTimeMs(0), + busy(false), + lastReadTime(0) { + stateOnInit = STATE_ON_INIT_KEEP; +} + +void BistableRelay::onInit() { + + if (statusPin >= 0) { + pinMode(statusPin, statusPullUp ? INPUT_PULLUP : INPUT); + channel.setNewValue(isOn()); + } else { + channel.setNewValue(false); + } + + Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOffValue()); + + if (stateOnInit == STATE_ON_INIT_ON || + stateOnInit == STATE_ON_INIT_RESTORED_ON) { + turnOn(); + } else if (stateOnInit == STATE_ON_INIT_OFF || + stateOnInit == STATE_ON_INIT_RESTORED_OFF) { + turnOff(); + } + + pinMode(pin, OUTPUT); +} + +void BistableRelay::iterateAlways() { + Relay::iterateAlways(); + + if (statusPin >= 0 && (millis() - lastReadTime > 100)) { + lastReadTime = millis(); + channel.setNewValue(isOn()); + } + + if (busy && millis() - disarmTimeMs > 200) { + busy = false; + Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOffValue()); + } +} + +int BistableRelay::handleNewValueFromServer( + TSD_SuplaChannelNewValue *newValue) { + // ignore new requests if we are in the middle of state change + if (busy) { + return 0; + } else { + return Relay::handleNewValueFromServer(newValue); + } +} + +void BistableRelay::turnOn(_supla_int_t duration) { + if (busy) { + return; + } + + durationMs = 0; + + if (keepTurnOnDurationMs) { + duration = storedTurnOnDurationMs; + } + // Change turn on requests duration to be at least 1 s + if (duration > 0 && duration < 1000) { + duration = 1000; + } + if (duration > 0) { + durationMs = duration; + durationTimestamp = millis(); + } + + if (isStatusUnknown() || !isOn()) { + internalToggle(); + } +} + +void BistableRelay::turnOff(_supla_int_t duration) { + if (busy) { + return; + } + + durationMs = 0; + + if (isStatusUnknown()) { + internalToggle(); + } else if (isOn()) { + internalToggle(); + } +} + +bool BistableRelay::isOn() { + if (isStatusUnknown()) { + return false; + } + return Supla::Io::digitalRead(channel.getChannelNumber(), statusPin) == + (statusHighIsOn ? HIGH : LOW); +} + +bool BistableRelay::isStatusUnknown() { + return (statusPin < 0); +} + +void BistableRelay::internalToggle() { + busy = true; + disarmTimeMs = millis(); + Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOnValue()); +} + +void BistableRelay::toggle(_supla_int_t duration) { + if (isOn() || isStatusUnknown()) { + turnOff(duration); + } else { + turnOn(duration); + } +} diff --git a/lib/SuplaDevice/src/supla/control/bistable_relay.h b/lib/SuplaDevice/src/supla/control/bistable_relay.h new file mode 100644 index 00000000..e75578fb --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/bistable_relay.h @@ -0,0 +1,67 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* BistableRelay + * This class can be used to controll bistable relay. + * Supla device will send short impulses (<0.5 s) on GPIO to toggle bistable + * relay state. + * Device does not have knowledge about the status of bistable relay, so it + * has to be read on a different GPIO (statusPin) + * This class can work without statusPin information, but Supla will lose + * information about status of bistable relay. + */ + +#ifndef _bistable_relay_h +#define _bistable_relay_h + +#include "relay.h" + +namespace Supla { +namespace Control { +class BistableRelay : public Relay { + public: + BistableRelay(int pin, + int statusPin = -1, + bool statusPullUp = true, + bool statusHighIsOn = true, + bool highIsOn = true, + _supla_int_t functions = + (0xFF ^ SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER)); + void onInit(); + void iterateAlways(); + int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); + void turnOn(_supla_int_t duration = 0); + void turnOff(_supla_int_t duration = 0); + void toggle(_supla_int_t duration = 0); + + virtual bool isOn(); + bool isStatusUnknown(); + + protected: + void internalToggle(); + + int statusPin; + bool statusPullUp; + bool statusHighIsOn; + unsigned long disarmTimeMs; + unsigned long lastReadTime; + bool busy; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp new file mode 100644 index 00000000..5fc8fe1f --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp @@ -0,0 +1,73 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "bistable_roller_shutter.h" + +namespace Supla { +namespace Control { + +BistableRollerShutter::BistableRollerShutter(int pinUp, int pinDown, bool highIsOn) + : RollerShutter(pinUp, pinDown, highIsOn), + activeBiRelay(false), + toggleTime(0) { +} + +void BistableRollerShutter::stopMovement() { + if (currentDirection == UP_DIR) { + relayUpOn(); + } else if (currentDirection == DOWN_DIR) { + relayDownOn(); + } + currentDirection = STOP_DIR; + doNothingTime = millis(); +} + +void BistableRollerShutter::relayDownOn() { + activeBiRelay = true; + toggleTime = millis(); + digitalWrite(pinDown, highIsOn ? HIGH : LOW); +} + +void BistableRollerShutter::relayUpOn() { + activeBiRelay = true; + toggleTime = millis(); + digitalWrite(pinUp, highIsOn ? HIGH : LOW); +} + +void BistableRollerShutter::relayDownOff() { + activeBiRelay = false; + digitalWrite(pinDown, highIsOn ? LOW : HIGH); +} + +void BistableRollerShutter::relayUpOff() { + activeBiRelay = false; + digitalWrite(pinUp, highIsOn ? LOW : HIGH); +} + +void BistableRollerShutter::onTimer() { + if (activeBiRelay && millis() - toggleTime > 200) { + switchOffRelays(); + doNothingTime = millis(); + } + if (activeBiRelay) { + return; + } + + RollerShutter::onTimer(); +} + +}; // namespace Control +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.h b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.h new file mode 100644 index 00000000..4d2fb735 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.h @@ -0,0 +1,46 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _bistable_roller_shutter_h +#define _bistable_roller_shutter_h + +#include "roller_shutter.h" + +namespace Supla { +namespace Control { +class BistableRollerShutter : public RollerShutter { + public: + BistableRollerShutter(int pinUp, int pinDown, bool highIsOn = true); + + void onTimer(); + + protected: + void stopMovement(); + void relayDownOn(); + void relayUpOn(); + void relayUpOff(); + void relayDownOff(); + + bool activeBiRelay; + unsigned long toggleTime; +}; + +}; // namespace Control +}; // namespace Supla + +#endif + + diff --git a/lib/SuplaDevice/src/supla/control/button.cpp b/lib/SuplaDevice/src/supla/control/button.cpp new file mode 100644 index 00000000..53d2b25d --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/button.cpp @@ -0,0 +1,186 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "button.h" + +enum StateResults {PRESSED, RELEASED, TO_PRESSED, TO_RELEASED}; + +Supla::Control::ButtonState::ButtonState(int pin, bool pullUp, bool invertLogic) + : pin(pin), + pullUp(pullUp), + invertLogic(invertLogic), + newStatusCandidate(LOW), + debounceTimeMs(0), + filterTimeMs(0), + debounceDelayMs(50), + swNoiseFilterDelayMs(20) { +} + +int Supla::Control::ButtonState::update() { + if (millis() - debounceTimeMs > debounceDelayMs) { + int currentState = digitalRead(pin); + if (currentState != prevState) { + // If status is changed, then make sure that it will be kept at + // least swNoiseFilterDelayMs ms to avoid noise + if (currentState != newStatusCandidate) { + newStatusCandidate = currentState; + filterTimeMs = millis(); + } else if (millis() - filterTimeMs > swNoiseFilterDelayMs) { + // If new status is kept at least swNoiseFilterDelayMs ms, then apply + // change of status + debounceTimeMs = millis(); + prevState = currentState; + if (currentState == valueOnPress()) { + return TO_PRESSED; + } else { + return TO_RELEASED; + } + } + } else { + // If current status is the same as prevState, then reset + // new status candidate + newStatusCandidate = prevState; + } + } + if (prevState == valueOnPress()) { + return PRESSED; + } else { + return RELEASED; + } +} + +Supla::Control::Button::Button(int pin, bool pullUp, bool invertLogic) + : state(pin, pullUp, invertLogic), + holdTimeMs(0), + multiclickTimeMs(0), + enableExtDetection(false), + lastStateChangeMs(0), + holdSend(false), + clickCounter(0), + bistable(false) { +} + +void Supla::Control::Button::onTimer() { + unsigned int timeDelta = millis() - lastStateChangeMs; + bool stateChanged = false; + int stateResult = state.update(); + if (stateResult == TO_PRESSED) { + stateChanged = true; + runAction(ON_PRESS); + runAction(ON_CHANGE); + } else if (stateResult == TO_RELEASED) { + stateChanged = true; + runAction(ON_RELEASE); + runAction(ON_CHANGE); + } + + if (!stateChanged) { + if (!bistable && stateResult == PRESSED) { + if (clickCounter <= 1 && holdTimeMs > 0 && timeDelta > holdTimeMs && !holdSend) { + runAction(ON_HOLD); + holdSend = true; + } + } else if (bistable || stateResult == RELEASED) { + if (multiclickTimeMs > 0 && timeDelta > multiclickTimeMs) { + if (!holdSend) { + switch (clickCounter) { + case 1: + runAction(ON_CLICK_1); + break; + case 2: + runAction(ON_CLICK_2); + break; + case 3: + runAction(ON_CLICK_3); + break; + case 4: + runAction(ON_CLICK_4); + break; + case 5: + runAction(ON_CLICK_5); + break; + case 6: + runAction(ON_CLICK_6); + break; + case 7: + runAction(ON_CLICK_7); + break; + case 8: + runAction(ON_CLICK_8); + break; + case 9: + runAction(ON_CLICK_9); + break; + } + } + holdSend = false; + clickCounter = 0; + } + } + } + + if (stateChanged) { + lastStateChangeMs = millis(); + if (stateResult == TO_PRESSED || bistable) { + clickCounter++; + } + + } +} + +void Supla::Control::Button::onInit() { + state.init(); +} + +void Supla::Control::ButtonState::init() { + pinMode(pin, pullUp ? INPUT_PULLUP : INPUT); + prevState = digitalRead(pin); + newStatusCandidate = prevState; +} + +int Supla::Control::ButtonState::valueOnPress() { + return invertLogic ? LOW : HIGH; +} + +void Supla::Control::Button::setSwNoiseFilterDelay(unsigned int newDelayMs) { + state.setSwNoiseFilterDelay(newDelayMs); +} +void Supla::Control::ButtonState::setSwNoiseFilterDelay(unsigned int newDelayMs) { + swNoiseFilterDelayMs = newDelayMs; +} + +void Supla::Control::Button::setDebounceDelay(unsigned int newDelayMs) { + state.setDebounceDelay(newDelayMs); +} + +void Supla::Control::ButtonState::setDebounceDelay(unsigned int newDelayMs) { + debounceDelayMs = newDelayMs; +} + +void Supla::Control::Button::setHoldTime(unsigned int timeMs) { + holdTimeMs = timeMs; + if (bistable) { + holdTimeMs = 0; + } +} + +void Supla::Control::Button::setMulticlickTime(unsigned int timeMs, bool bistableButton) { + multiclickTimeMs = timeMs; + bistable = bistableButton; + if (bistable) { + holdTimeMs = 0; + } +} diff --git a/lib/SuplaDevice/src/supla/control/button.h b/lib/SuplaDevice/src/supla/control/button.h new file mode 100644 index 00000000..7cf146a3 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/button.h @@ -0,0 +1,80 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _button_h +#define _button_h + +#include + +#include "../element.h" +#include "../events.h" +#include "../local_action.h" + +namespace Supla { +namespace Control { + +class ButtonState { + public: + ButtonState(int pin, bool pullUp, bool invertLogic); + int update(); + void init(); + + void setSwNoiseFilterDelay(unsigned int newDelayMs); + void setDebounceDelay(unsigned int newDelayMs); + void setHoldTime(unsigned int timeMs); + void setMulticlickTime(unsigned int timeMs); + + protected: + int valueOnPress(); + + unsigned long debounceTimeMs; + unsigned long filterTimeMs; + unsigned int debounceDelayMs; + unsigned int swNoiseFilterDelayMs; + int pin; + int8_t newStatusCandidate; + int8_t prevState; + bool pullUp; + bool invertLogic; +}; + +class Button : public Element, + public LocalAction { + public: + Button(int pin, bool pullUp = false, bool invertLogic = false); + + void onTimer(); + void onInit(); + void setSwNoiseFilterDelay(unsigned int newDelayMs); + void setDebounceDelay(unsigned int newDelayMs); + void setHoldTime(unsigned int timeMs); + void setMulticlickTime(unsigned int timeMs, bool bistableButton = false); + + protected: + ButtonState state; + unsigned int holdTimeMs; + unsigned int multiclickTimeMs; + uint8_t clickCounter; + unsigned long lastStateChangeMs; + bool enableExtDetection; + bool holdSend; + bool bistable; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/dimmer_base.h b/lib/SuplaDevice/src/supla/control/dimmer_base.h new file mode 100644 index 00000000..cd88b026 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/dimmer_base.h @@ -0,0 +1,37 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _dimmer_base_h +#define _dimmer_base_h + + +#include "rgbw_base.h" + +namespace Supla { +namespace Control { +class DimmerBase : public RGBWBase { + public: + DimmerBase() { + channel.setType(SUPLA_CHANNELTYPE_DIMMER); + channel.setDefault(SUPLA_CHANNELFNC_DIMMER); + } + +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/light_relay.cpp b/lib/SuplaDevice/src/supla/control/light_relay.cpp new file mode 100644 index 00000000..33b2b7d5 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/light_relay.cpp @@ -0,0 +1,109 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "light_relay.h" + +using namespace Supla; +using namespace Control; + +#pragma pack(push, 1) +struct LightRelayStateData { + unsigned short lifespan; + _supla_int_t turnOnSecondsCumulative; +}; +#pragma pop + +LightRelay::LightRelay(int pin, bool highIsOn) + : Relay(pin, + highIsOn, + SUPLA_BIT_FUNC_LIGHTSWITCH | SUPLA_BIT_FUNC_STAIRCASETIMER), + lifespan(10000), + turnOnSecondsCumulative(0) { + channel.setFlag(SUPLA_CHANNEL_FLAG_LIGHTSOURCELIFESPAN_SETTABLE); +} + +void LightRelay::handleGetChannelState(TDSC_ChannelState &channelState) { + channelState.Fields |= SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCELIFESPAN | + SUPLA_CHANNELSTATE_FIELD_LIGHTSOURCEOPERATINGTIME; + channelState.LightSourceLifespan = lifespan; + channelState.LightSourceOperatingTime = turnOnSecondsCumulative; +} + +int LightRelay::handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) { + if (request && + request->Command == SUPLA_CALCFG_CMD_SET_LIGHTSOURCE_LIFESPAN) { + if (request->DataSize == sizeof(TCalCfg_LightSourceLifespan)) { + TCalCfg_LightSourceLifespan *config = + reinterpret_cast(request->Data); + + if (config->SetTime) { + lifespan = config->LightSourceLifespan; + } + if (config->ResetCounter) { + turnOnSecondsCumulative = 0; + } + + return SUPLA_CALCFG_RESULT_DONE; + } + } + + return SUPLA_CALCFG_RESULT_NOT_SUPPORTED; +} + +void LightRelay::onLoadState() { + LightRelayStateData data; + if (Supla::Storage::ReadState((unsigned char *)&data, sizeof(data))) { + lifespan = data.lifespan; + turnOnSecondsCumulative = data.turnOnSecondsCumulative; + + Serial.print(F("LightRelay[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("] settings restored from storage. Total lifespan: ")); + Serial.print(lifespan); + Serial.print(F(" h; current operating time: ")); + Serial.print(turnOnSecondsCumulative); + Serial.println(F(" s")); + } + Relay::onLoadState(); +} + +void LightRelay::onSaveState() { + LightRelayStateData data; + + data.lifespan = lifespan; + data.turnOnSecondsCumulative = turnOnSecondsCumulative; + Supla::Storage::WriteState((unsigned char *)&data, sizeof(data)); + Relay::onSaveState(); +} + +void LightRelay::turnOn(_supla_int_t duration) { + turnOnTimestamp = millis(); + Relay::turnOn(duration); +} + +void LightRelay::iterateAlways() { + if (isOn()) { + unsigned long currentMillis = millis(); + int seconds = (currentMillis - turnOnTimestamp) / 1000; + if (seconds > 0) { + turnOnTimestamp = + currentMillis - ((currentMillis - turnOnTimestamp) % 1000); + turnOnSecondsCumulative += seconds; + } + } + + Relay::iterateAlways(); +} diff --git a/lib/SuplaDevice/src/supla/control/light_relay.h b/lib/SuplaDevice/src/supla/control/light_relay.h new file mode 100644 index 00000000..7c93598a --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/light_relay.h @@ -0,0 +1,43 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _light_relay_h +#define _light_relay_h + +#include "relay.h" + +namespace Supla { +namespace Control { +class LightRelay : public Relay { + public: + LightRelay(int pin, bool highIsOn = true); + void handleGetChannelState(TDSC_ChannelState &channelState); + int handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request); + void onLoadState(); + void onSaveState(); + void turnOn(_supla_int_t duration = 0); + void iterateAlways(); + + protected: + unsigned short lifespan; + _supla_int_t turnOnSecondsCumulative; + unsigned long turnOnTimestamp; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/relay.cpp b/lib/SuplaDevice/src/supla/control/relay.cpp new file mode 100644 index 00000000..ae70bd1d --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/relay.cpp @@ -0,0 +1,197 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Relay class + * This class is used to control any type of relay that can be controlled + * by setting LOW or HIGH output on selected GPIO. + */ + +#include + +#include "../actions.h" +#include "../io.h" +#include "../storage/storage.h" +#include "relay.h" + +using namespace Supla; +using namespace Control; + +Relay::Relay(int pin, bool highIsOn, _supla_int_t functions) + : pin(pin), + durationMs(0), + highIsOn(highIsOn), + durationTimestamp(0), + stateOnInit(STATE_ON_INIT_OFF), + keepTurnOnDurationMs(false), + storedTurnOnDurationMs(0) { + channel.setType(SUPLA_CHANNELTYPE_RELAY); + channel.setFuncList(functions); +} + +uint8_t Relay::pinOnValue() { + return highIsOn ? HIGH : LOW; +} + +uint8_t Relay::pinOffValue() { + return highIsOn ? LOW : HIGH; +} + +void Relay::onInit() { + if (stateOnInit == STATE_ON_INIT_ON || + stateOnInit == STATE_ON_INIT_RESTORED_ON) { + turnOn(); + } else { + turnOff(); + } + + pinMode(pin, OUTPUT); // pin mode is set after setting pin value in order to + // avoid problems with LOW trigger relays +} + +void Relay::iterateAlways() { + if (durationMs && millis() - durationTimestamp > durationMs) { + toggle(); + } +} + +int Relay::handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue) { + int result = -1; + if (newValue->value[0] == 1) { + if (keepTurnOnDurationMs) { + storedTurnOnDurationMs = newValue->DurationMS; + } + turnOn(newValue->DurationMS); + result = 1; + } else if (newValue->value[0] == 0) { + turnOff(0); // newValue->DurationMS may contain "turn on duration" which + // result in unexpected "turn on after duration ms received in + // turnOff message" + result = 1; + } + + return result; +} + +void Relay::turnOn(_supla_int_t duration) { + durationMs = duration; + durationTimestamp = millis(); + if (keepTurnOnDurationMs) { + durationMs = storedTurnOnDurationMs; + } + Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOnValue()); + + channel.setNewValue(true); +} + +void Relay::turnOff(_supla_int_t duration) { + durationMs = duration; + durationTimestamp = millis(); + Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOffValue()); + + channel.setNewValue(false); +} + +bool Relay::isOn() { + return Supla::Io::digitalRead(channel.getChannelNumber(), pin) == + pinOnValue(); +} + +void Relay::toggle(_supla_int_t duration) { + if (isOn()) { + turnOff(duration); + } else { + turnOn(duration); + } +} + +void Relay::runAction(int trigger, int action) { + switch (action) { + case TURN_ON: { + turnOn(); + break; + } + case TURN_OFF: { + turnOff(); + break; + } + case TOGGLE: { + toggle(); + break; + } + } +} + +Channel *Relay::getChannel() { + return &channel; +} + +void Relay::onSaveState() { + if (keepTurnOnDurationMs) { + Supla::Storage::WriteState((unsigned char *)&storedTurnOnDurationMs, + sizeof(storedTurnOnDurationMs)); + } + if (stateOnInit < 0) { + bool enabled = isOn(); + Supla::Storage::WriteState((unsigned char *)&enabled, sizeof(enabled)); + } +} + +void Relay::onLoadState() { + if (keepTurnOnDurationMs) { + Supla::Storage::ReadState((unsigned char *)&storedTurnOnDurationMs, + sizeof(storedTurnOnDurationMs)); + Serial.print(F("Relay[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("]: restored durationMs: ")); + Serial.println(storedTurnOnDurationMs); + } + + if (stateOnInit < 0) { + bool enabled = false; + if (Supla::Storage::ReadState((unsigned char *)&enabled, sizeof(enabled))) { + Serial.print(F("Relay[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("]: restored relay state: ")); + if (enabled) { + Serial.println(F("ON")); + stateOnInit = STATE_ON_INIT_RESTORED_ON; + } else { + Serial.println(F("OFF")); + stateOnInit = STATE_ON_INIT_RESTORED_OFF; + } + } + } +} + +Relay &Relay::setDefaultStateOn() { + stateOnInit = STATE_ON_INIT_ON; + return *this; +} + +Relay &Relay::setDefaultStateOff() { + stateOnInit = STATE_ON_INIT_OFF; + return *this; +} + +Relay &Relay::setDefaultStateRestore() { + stateOnInit = STATE_ON_INIT_RESTORE; + return *this; +} + +Relay &Relay::keepTurnOnDuration(bool keep) { + keepTurnOnDurationMs = keep; + return *this; +} diff --git a/lib/SuplaDevice/src/supla/control/relay.h b/lib/SuplaDevice/src/supla/control/relay.h new file mode 100644 index 00000000..98a79735 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/relay.h @@ -0,0 +1,87 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Relay class + * This class is used to control any type of relay that can be controlled + * by setting LOW or HIGH output on selected GPIO. + */ + +#ifndef _relay_h +#define _relay_h + +#include + +#include "../actions.h" +#include "../channel.h" +#include "../element.h" +#include "../io.h" +#include "../storage/storage.h" +#include "../triggerable.h" + +#define STATE_ON_INIT_RESTORED_OFF -3 +#define STATE_ON_INIT_RESTORED_ON -2 +#define STATE_ON_INIT_RESTORE -1 +#define STATE_ON_INIT_OFF 0 +#define STATE_ON_INIT_ON 1 + +namespace Supla { +namespace Control { +class Relay : public Element, public Triggerable { + public: + Relay(int pin, + bool highIsOn = true, + _supla_int_t functions = (0xFF ^ + SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER)); + + virtual Relay &setDefaultStateOn(); + virtual Relay &setDefaultStateOff(); + virtual Relay &setDefaultStateRestore(); + virtual Relay &keepTurnOnDuration(bool keep = true); + + virtual uint8_t pinOnValue(); + virtual uint8_t pinOffValue(); + virtual void turnOn(_supla_int_t duration = 0); + virtual void turnOff(_supla_int_t duration = 0); + virtual bool isOn(); + virtual void toggle(_supla_int_t duration = 0); + + void runAction(int trigger, int action); + + void onInit(); + void onLoadState(); + void onSaveState(); + void iterateAlways(); + int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); + + protected: + Channel *getChannel(); + Channel channel; + int pin; + bool highIsOn; + + int8_t stateOnInit; + + _supla_int_t durationMs; + _supla_int_t storedTurnOnDurationMs; + unsigned long durationTimestamp; + bool keepTurnOnDurationMs; + +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/rgb_base.h b/lib/SuplaDevice/src/supla/control/rgb_base.h new file mode 100644 index 00000000..7bcc3936 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/rgb_base.h @@ -0,0 +1,36 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _rgb_base_h +#define _rgb_base_h + +#include "rgbw_base.h" + +namespace Supla { +namespace Control { +class RGBBase : public RGBWBase { + public: + RGBBase() { + channel.setType(SUPLA_CHANNELTYPE_RGBLEDCONTROLLER); + channel.setDefault(SUPLA_CHANNELFNC_RGBLIGHTING); + } + +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/rgbw_base.cpp b/lib/SuplaDevice/src/supla/control/rgbw_base.cpp new file mode 100644 index 00000000..d7ec2c90 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/rgbw_base.cpp @@ -0,0 +1,427 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +#include "rgbw_base.h" + +namespace Supla { +namespace Control { + +RGBWBase::RGBWBase() + : buttonStep(10), + lastColorBrightness(100), + lastBrightness(100), + curRed(0), + curGreen(255), + curBlue(0), + curColorBrightness(0), + curBrightness(0), + defaultDimmedBrightness(20), + dimIterationDirection(false), + iterationDelayCounter(0), + fadeEffect(1000), + hwRed(0), + hwGreen(255), + hwBlue(0), + hwColorBrightness(0), + hwBrightness(0), + lastTick(0) { + channel.setType(SUPLA_CHANNELTYPE_DIMMERANDRGBLED); + channel.setDefault(SUPLA_CHANNELFNC_DIMMERANDRGBLIGHTING); +} + + +void RGBWBase::setRGBW( + int red, int green, int blue, int colorBrightness, int brightness) { + // Store last non 0 brightness for turn on/toggle operations + if (colorBrightness > 0) { + lastColorBrightness = colorBrightness; + } + if (brightness > 0) { + lastBrightness = brightness; + } + + // Store current values + if (red >= 0) { + curRed = red; + } + if (green >= 0) { + curGreen = green; + } + if (blue >= 0) { + curBlue = blue; + } + if (colorBrightness >= 0) { + curColorBrightness = colorBrightness; + } + if (brightness >= 0) { + curBrightness = brightness; + } + + // If fade effect is disabled, then set new values to device directly + if (fadeEffect <= 0) { + setRGBWValueOnDevice( + curRed, curGreen, curBlue, curColorBrightness, curBrightness); + } + + // Send to Supla server new values + channel.setNewValue( + curRed, curGreen, curBlue, curColorBrightness, curBrightness); +} + +int RGBWBase::handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue) { + uint8_t red = static_cast(newValue->value[4]); + uint8_t green = static_cast(newValue->value[3]); + uint8_t blue = static_cast(newValue->value[2]); + uint8_t colorBrightness = static_cast(newValue->value[1]); + uint8_t brightness = static_cast(newValue->value[0]); + + setRGBW(red, green, blue, colorBrightness, brightness); + + return -1; +} + +void RGBWBase::turnOn() { + setRGBW(-1, -1, -1, lastColorBrightness, lastBrightness); +} +void RGBWBase::turnOff() { + setRGBW(-1, -1, -1, 0, 0); +} + +void RGBWBase::toggle() { + setRGBW(-1, + -1, + -1, + curColorBrightness > 0 ? 0 : lastColorBrightness, + curBrightness > 0 ? 0 : lastBrightness); +} + +uint8_t RGBWBase::addWithLimit(int value, int addition, int limit) { + if (addition > 0 && value + addition > limit) { + return limit; + } + if (addition < 0 && value + addition < 0) { + return 0; + } + return value + addition; +} + +void RGBWBase::runAction(int trigger, int action) { + switch (action) { + case TURN_ON: { + turnOn(); + break; + } + case TURN_OFF: { + turnOff(); + break; + } + case TOGGLE: { + toggle(); + break; + } + case BRIGHTEN_ALL: { + setRGBW(-1, + -1, + -1, + addWithLimit(curColorBrightness, buttonStep, 100), + addWithLimit(curBrightness, buttonStep, 100)); + break; + } + case DIM_ALL: { + setRGBW(-1, + -1, + -1, + addWithLimit(curColorBrightness, -buttonStep, 100), + addWithLimit(curBrightness, -buttonStep, 100)); + break; + } + case BRIGHTEN_R: { + setRGBW(addWithLimit(curRed, buttonStep), -1, -1, -1, -1); + break; + } + case DIM_R: { + setRGBW(addWithLimit(curRed, -buttonStep), -1, -1, -1, -1); + break; + } + case BRIGHTEN_G: { + setRGBW(-1, addWithLimit(curGreen, buttonStep), -1, -1, -1); + break; + } + case DIM_G: { + setRGBW(-1, addWithLimit(curGreen, -buttonStep), -1, -1, -1); + break; + } + case BRIGHTEN_B: { + setRGBW(-1, -1, addWithLimit(curBlue, buttonStep), -1, -1); + break; + } + case DIM_B: { + setRGBW(-1, -1, addWithLimit(curBlue, -buttonStep), -1, -1); + break; + } + case BRIGHTEN_W: { + setRGBW(-1, -1, -1, -1, addWithLimit(curBrightness, buttonStep, 100)); + break; + } + case DIM_W: { + setRGBW(-1, -1, -1, -1, addWithLimit(curBrightness, -buttonStep, 100)); + break; + } + case BRIGHTEN_RGB: { + setRGBW( + -1, -1, -1, addWithLimit(curColorBrightness, buttonStep, 100), -1); + break; + } + case DIM_RGB: { + setRGBW( + -1, -1, -1, addWithLimit(curColorBrightness, -buttonStep, 100), -1); + break; + } + case TURN_ON_RGB: { + setRGBW(-1, -1, -1, lastColorBrightness, -1); + break; + } + case TURN_OFF_RGB: { + setRGBW(-1, -1, -1, 0, -1); + break; + } + case TOGGLE_RGB: { + setRGBW(-1, -1, -1, curColorBrightness > 0 ? 0 : lastColorBrightness, -1); + break; + } + case TURN_ON_W: { + setRGBW(-1, -1, -1, -1, lastBrightness); + break; + } + case TURN_OFF_W: { + setRGBW(-1, -1, -1, -1, 0); + break; + } + case TOGGLE_W: { + setRGBW(-1, -1, -1, -1, curBrightness > 0 ? 0 : lastBrightness); + break; + } + case TURN_ON_RGB_DIMMED: { + if (curColorBrightness == 0) { + setRGBW(-1, -1, -1, defaultDimmedBrightness, -1); + } + break; + } + case TURN_ON_W_DIMMED: { + if (curBrightness == 0) { + setRGBW(-1, -1, -1, -1, defaultDimmedBrightness); + } + break; + } + case TURN_ON_ALL_DIMMED: { + if (curBrightness == 0 && curColorBrightness == 0) { + setRGBW(-1, -1, -1, defaultDimmedBrightness, defaultDimmedBrightness); + } + break; + } + case ITERATE_DIM_RGB: { + iterateDimmerRGBW(buttonStep, 0); + break; + } + case ITERATE_DIM_W: { + iterateDimmerRGBW(0, buttonStep); + break; + } + case ITERATE_DIM_ALL: { + iterateDimmerRGBW(buttonStep, buttonStep); + break; + } + } +} + +void RGBWBase::iterateDimmerRGBW(int rgbStep, int wStep) { + // if we iterate both RGB and W, then we should sync brightness + if (rgbStep > 0 && wStep > 0) { + curBrightness = curColorBrightness; + } + if (rgbStep > 0) { + if (curColorBrightness <= 10 && dimIterationDirection == true) { + iterationDelayCounter++; + if (iterationDelayCounter == 5) { + dimIterationDirection = false; + iterationDelayCounter = 0; + } else { + return; + } + } else if (curColorBrightness == 100 && dimIterationDirection == false) { + iterationDelayCounter++; + if (iterationDelayCounter == 5) { + dimIterationDirection = true; + iterationDelayCounter = 0; + } else { + return; + } + } + } else if (wStep > 0) { + if (curBrightness <= 10 && dimIterationDirection == true) { + iterationDelayCounter++; + if (iterationDelayCounter == 5) { + dimIterationDirection = false; + iterationDelayCounter = 0; + } else { + return; + } + } else if (curBrightness == 100 && dimIterationDirection == false) { + iterationDelayCounter++; + if (iterationDelayCounter == 5) { + dimIterationDirection = true; + iterationDelayCounter = 0; + } else { + return; + } + } + } + iterationDelayCounter = 0; + + // If direction is dim, then brightness step is set to negative + if (dimIterationDirection) { + rgbStep = -rgbStep; + wStep = -wStep; + } + + setRGBW(-1, + -1, + -1, + addWithLimit(curColorBrightness, rgbStep, 100), + addWithLimit(curBrightness, wStep, 100)); +} + +Channel *RGBWBase::getChannel() { + return &channel; +} + +void RGBWBase::setStep(int step) { + buttonStep = step; +} + +void RGBWBase::setDefaultDimmedBrightness(int dimmedBrightness) { + defaultDimmedBrightness = dimmedBrightness; +} + +void RGBWBase::setFadeEffectTime(int timeMs) { + fadeEffect = timeMs; +} + +void RGBWBase::onTimer() { + // exit it fade effect is disabled + if (fadeEffect <= 0) { + return; + } + unsigned long timeDiff = millis() - lastTick; + lastTick = millis(); + + if (timeDiff > 0) { + int divider = fadeEffect / timeDiff; + if (divider <= 0) { + divider = 1; + } + + uint8_t rgbStep = 255 / divider; + uint8_t brightnessStep = 100 / divider; + bool valueChanged = false; + if (rgbStep < 1) { + rgbStep = 1; + } + if (brightnessStep < 1) { + brightnessStep = 1; + } + + if (curRed > hwRed) { + valueChanged = true; + hwRed += rgbStep; + if (hwRed > curRed) { + hwRed = curRed; + } + } else if (curRed < hwRed) { + valueChanged = true; + hwRed -= rgbStep; + if (hwRed < curRed) { + hwRed = curRed; + } + } + + if (curGreen > hwGreen) { + valueChanged = true; + hwGreen += rgbStep; + if (hwGreen > curGreen) { + hwGreen = curGreen; + } + } else if (curGreen < hwGreen) { + valueChanged = true; + hwGreen -= rgbStep; + if (hwGreen < curGreen) { + hwGreen = curGreen; + } + } + + if (curBlue > hwBlue) { + valueChanged = true; + hwBlue += rgbStep; + if (hwBlue > curBlue) { + hwBlue = curBlue; + } + } else if (curBlue < hwBlue) { + valueChanged = true; + hwBlue -= rgbStep; + if (hwBlue < curBlue) { + hwBlue = curBlue; + } + } + + if (curColorBrightness > hwColorBrightness) { + valueChanged = true; + hwColorBrightness += brightnessStep; + if (hwColorBrightness > curColorBrightness) { + hwColorBrightness = curColorBrightness; + } + } else if (curColorBrightness < hwColorBrightness) { + valueChanged = true; + hwColorBrightness -= brightnessStep; + if (hwColorBrightness < curColorBrightness) { + hwColorBrightness = curColorBrightness; + } + } + + if (curBrightness > hwBrightness) { + valueChanged = true; + hwBrightness += brightnessStep; + if (hwBrightness > curBrightness) { + hwBrightness = curBrightness; + } + } else if (curBrightness < hwBrightness) { + valueChanged = true; + hwBrightness -= brightnessStep; + if (hwBrightness < curBrightness) { + hwBrightness = curBrightness; + } + } + + if (valueChanged) { + setRGBWValueOnDevice(hwRed, hwGreen, hwBlue, hwColorBrightness, hwBrightness); + } + } +} + +}; // namespace Control +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/control/rgbw_base.h b/lib/SuplaDevice/src/supla/control/rgbw_base.h new file mode 100644 index 00000000..6000b063 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/rgbw_base.h @@ -0,0 +1,89 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _rgbw_base_h +#define _rgbw_base_h + +#include +#include + +#include "../channel.h" +#include "../element.h" +#include "../triggerable.h" +#include "../actions.h" + +namespace Supla { +namespace Control { +class RGBWBase : public Element, public Triggerable { + public: + RGBWBase(); + + virtual void setRGBWValueOnDevice(uint8_t red, + uint8_t green, + uint8_t blue, + uint8_t colorBrightness, + uint8_t brightness) = 0; + + virtual void setRGBW( + int red, int green, int blue, int colorBrightness, int brightness); + + int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); + virtual void turnOn(); + virtual void turnOff(); + virtual void toggle(); + void runAction(int trigger, int action); + void setStep(int step); + void setDefaultDimmedBrightness(int dimmedBrightness); + void setFadeEffectTime(int timeMs); + void onTimer(); + +void onInit() { + + // Send to Supla server new values + channel.setNewValue( + curRed, curGreen, curBlue, curColorBrightness, curBrightness); +} + protected: + uint8_t addWithLimit(int value, int addition, int limit = 255); + Channel *getChannel(); + void iterateDimmerRGBW(int rgbStep, int wStep); + + Channel channel; + uint8_t buttonStep; // 10 + uint8_t curRed; // 0 - 255 + uint8_t curGreen; // 0 - 255 + uint8_t curBlue; // 0 - 255 + uint8_t curColorBrightness; // 0 - 100 + uint8_t curBrightness; // 0 - 100 + uint8_t lastColorBrightness; // 0 - 100 + uint8_t lastBrightness; // 0 - 100 + uint8_t defaultDimmedBrightness; // 20 + bool dimIterationDirection; + int iterationDelayCounter; + int fadeEffect; + int hwRed; // 0 - 255 + int hwGreen; // 0 - 255 + int hwBlue; // 0 - 255 + int hwColorBrightness; // 0 - 100 + int hwBrightness; // 0 - 100 + unsigned long lastTick; + +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp new file mode 100644 index 00000000..0d9a2102 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp @@ -0,0 +1,509 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "roller_shutter.h" +#include "supla/storage/storage.h" + +namespace Supla { +namespace Control { + +#pragma pack(push, 1) +struct RollerShutterStateData { + uint32_t closingTimeMs; + uint32_t openingTimeMs; + int8_t currentPosition; // 0 - closed; 100 - opened +}; +#pragma pop + +RollerShutter::RollerShutter(int pinUp, int pinDown, bool highIsOn) + : highIsOn(highIsOn), + pinUp(pinUp), + pinDown(pinDown), + openingTimeMs(0), + closingTimeMs(0), + calibrate(true), + comfortDownValue(20), + comfortUpValue(80), + newTargetPositionAvailable(false), + currentDirection(STOP_DIR), + lastDirection(STOP_DIR), + currentPosition(UNKNOWN_POSITION), + lastMovementStartTime(0), + doNothingTime(0), + calibrationTime(0), + operationTimeout(0) { + targetPosition = STOP_POSITION; + lastPositionBeforeMovement = currentPosition; + channel.setType(SUPLA_CHANNELTYPE_RELAY); + channel.setDefault(SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER); + channel.setFuncList(SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER); +} + +void RollerShutter::onInit() { + pinMode(pinUp, OUTPUT); + pinMode(pinDown, OUTPUT); + digitalWrite(pinUp, highIsOn ? LOW : HIGH); + digitalWrite(pinDown, highIsOn ? LOW : HIGH); +} + +/* + * Protocol: + * value[0]: + * 0 - stop + * 1 - down + * 2 - up + * 10 - 110 - 0% - 100% + * + * time is send in 0.1 s. i.e. 105 -> 10.5 s + * time * 100 = gives time in ms + * + */ + +int RollerShutter::handleNewValueFromServer( + TSD_SuplaChannelNewValue *newValue) { + uint32_t newClosingTime = (newValue->DurationMS & 0xFFFF) * 100; + uint32_t newOpeningTime = ((newValue->DurationMS >> 16) & 0xFFFF) * 100; + + setOpenCloseTime(newClosingTime, newOpeningTime); + + char task = newValue->value[0]; + Serial.print(F("RollerShutter[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("] new value from server: ")); + if (task == 0) { + Serial.println(F("STOP")); + stop(); + } else if (task == 1) { + Serial.println(F("MOVE_DOWN")); + moveDown(); + } else if (task == 2) { + Serial.println(F("MOVE_UP")); + moveUp(); + } else if (task >= 10 && task <= 110) { + setTargetPosition(task - 10); + Serial.println(static_cast(task - 10)); + } + + return -1; +} + +void RollerShutter::setOpenCloseTime(uint32_t newClosingTimeMs, + uint32_t newOpeningTimeMs) { + if (newClosingTimeMs == 0) { + newClosingTimeMs = closingTimeMs; + } + if (newOpeningTimeMs == 0) { + newOpeningTimeMs = openingTimeMs; + } + + if (newClosingTimeMs != closingTimeMs || newOpeningTimeMs != openingTimeMs) { + closingTimeMs = newClosingTimeMs; + openingTimeMs = newOpeningTimeMs; + calibrate = true; + currentPosition = UNKNOWN_POSITION; + Serial.print(F("RollerShutter[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("] new time settings received. Opening time: ")); + Serial.print(openingTimeMs); + Serial.print(F(" ms; closing time: ")); + Serial.print(closingTimeMs); + Serial.println(F(" ms. Starting calibration...")); + } +} + +void RollerShutter::runAction(int trigger, int action) { + switch (action) { + case CLOSE_OR_STOP: { + if (inMove()) { + stop(); + } else { + close(); + } + break; + } + + case CLOSE: { + close(); + break; + } + case OPEN_OR_STOP: { + if (inMove()) { + stop(); + } else { + open(); + } + break; + } + + case OPEN: { + open(); + break; + } + + case COMFORT_DOWN_POSITION: { + setTargetPosition(comfortDownValue); + break; + } + + case COMFORT_UP_POSITION: { + setTargetPosition(comfortUpValue); + break; + } + + case STOP: { + stop(); + break; + } + + case STEP_BY_STEP: { + if (inMove()) { + stop(); + } else if (lastDirectionWasOpen()) { + close(); + } else if (lastDirectionWasClose()) { + open(); + } else if (currentPosition < 50) { + close(); + } else { + open(); + } + break; + } + + case MOVE_UP: { + moveUp(); + break; + } + + case MOVE_DOWN: { + moveDown(); + break; + } + + case MOVE_UP_OR_STOP: { + if (inMove()) { + stop(); + } else { + moveUp(); + } + break; + } + case MOVE_DOWN_OR_STOP: { + if (inMove()) { + stop(); + } else { + moveDown(); + } + break; + } + } +} + +void RollerShutter::close() { + setTargetPosition(100); +} + +void RollerShutter::open() { + setTargetPosition(0); +} + +void RollerShutter::moveDown() { + setTargetPosition(MOVE_DOWN_POSITION); +} + +void RollerShutter::moveUp() { + setTargetPosition(MOVE_UP_POSITION); +} + +void RollerShutter::stop() { + setTargetPosition(STOP_POSITION); +} + +void RollerShutter::setTargetPosition(int newPosition) { + targetPosition = newPosition; + newTargetPositionAvailable = true; + + // Negative targetPosition is either unknown or stop command, so we + // ignore it + if (targetPosition == MOVE_UP_POSITION) { + lastDirection = UP_DIR; + } else if (targetPosition == MOVE_DOWN_POSITION) { + lastDirection = DOWN_DIR; + } else if (targetPosition >= 0) { + if (targetPosition < currentPosition) { + lastDirection = UP_DIR; + } else if (targetPosition > currentPosition) { + lastDirection = DOWN_DIR; + } + } +} + +bool RollerShutter::lastDirectionWasOpen() { + return lastDirection == UP_DIR; +} + +bool RollerShutter::lastDirectionWasClose() { + return lastDirection == DOWN_DIR; +} + +bool RollerShutter::inMove() { + return currentDirection != STOP_DIR; +} + +void RollerShutter::stopMovement() { + switchOffRelays(); + currentDirection = STOP_DIR; + doNothingTime = millis(); +} + +void RollerShutter::relayDownOn() { + digitalWrite(pinDown, highIsOn ? HIGH : LOW); +} + +void RollerShutter::relayUpOn() { + digitalWrite(pinUp, highIsOn ? HIGH : LOW); +} + +void RollerShutter::relayDownOff() { + digitalWrite(pinDown, highIsOn ? LOW : HIGH); +} + +void RollerShutter::relayUpOff() { + digitalWrite(pinUp, highIsOn ? LOW : HIGH); +} + +void RollerShutter::startClosing() { + currentDirection = DOWN_DIR; + relayUpOff(); // just to make sure + relayDownOn(); +} + +void RollerShutter::startOpening() { + currentDirection = UP_DIR; + relayDownOff(); // just to make sure + relayUpOn(); +} + +void RollerShutter::switchOffRelays() { + relayUpOff(); + relayDownOff(); +} + +void RollerShutter::onTimer() { + if (millis() - doNothingTime < + 300) { // doNothingTime time is used when we change + // direction of roller - to stop for a moment + // before enabling opposite direction + return; + } + + if (operationTimeout != 0 && + millis() - lastMovementStartTime > operationTimeout) { + setTargetPosition(STOP_POSITION); + operationTimeout = 0; + } + + if (targetPosition == STOP_POSITION && inMove()) { + stopMovement(); + calibrationTime = 0; + } + + if (calibrate && targetPosition == STOP_POSITION) { + return; + } else if (calibrate) { + // If calibrationTime is not set, then it means we should start calibration + if (calibrationTime == 0) { + // If roller shutter wasn't in move when calibration is requested, we + // select direction based on requested targetPosition + operationTimeout = 0; + if (targetPosition > 50 || targetPosition == MOVE_DOWN_POSITION) { + if (currentDirection == UP_DIR) { + stopMovement(); + } else if (currentDirection == STOP_DIR) { + Serial.println(F("Calibration: closing")); + calibrationTime = closingTimeMs; + lastMovementStartTime = millis(); + if (calibrationTime == 0) { + operationTimeout = 60000; + } + startClosing(); + } + } else { + if (currentDirection == DOWN_DIR) { + stopMovement(); + } else if (currentDirection == STOP_DIR) { + Serial.println(F("Calibration: opening")); + calibrationTime = openingTimeMs; + lastMovementStartTime = millis(); + if (calibrationTime == 0) { + operationTimeout = 60000; + } + startOpening(); + } + } + // + // Time used for calibaration is 10% higher then requested by user + calibrationTime *= 1.1; + if (calibrationTime > 0) { + Serial.print(F("Calibration time: ")); + Serial.println(calibrationTime); + } + } + + if (calibrationTime != 0 && + millis() - lastMovementStartTime > calibrationTime) { + Serial.println(F("Calibration done")); + calibrationTime = 0; + calibrate = false; + if (currentDirection == UP_DIR) { + currentPosition = 0; + } else { + currentPosition = 100; + } + stopMovement(); + } + + } else if (!newTargetPositionAvailable && + currentDirection != + STOP_DIR) { // no new command available and it is moving, + // just handle roller movement/status + if (currentDirection == UP_DIR && currentPosition > 0) { + int movementDistance = lastPositionBeforeMovement; + int timeRequired = (1.0 * openingTimeMs * movementDistance / 100.0); + float fractionOfMovemendDone = + (1.0 * (millis() - lastMovementStartTime) / timeRequired); + if (fractionOfMovemendDone > 1) { + fractionOfMovemendDone = 1; + } + currentPosition = lastPositionBeforeMovement - + movementDistance * fractionOfMovemendDone; + if (targetPosition >= 0 && currentPosition <= targetPosition) { + stopMovement(); + } + } else if (currentDirection == DOWN_DIR && currentPosition < 100) { + int movementDistance = 100 - lastPositionBeforeMovement; + int timeRequired = (1.0 * closingTimeMs * movementDistance / 100.0); + float fractionOfMovemendDone = + (1.0 * (millis() - lastMovementStartTime) / timeRequired); + if (fractionOfMovemendDone > 1) { + fractionOfMovemendDone = 1; + } + currentPosition = lastPositionBeforeMovement + + movementDistance * fractionOfMovemendDone; + if (targetPosition >= 0 && currentPosition >= targetPosition) { + stopMovement(); + } + } + + if (currentPosition > 100) { + currentPosition = 100; + } else if (currentPosition < 0) { + currentPosition = 0; + } + + } else if (newTargetPositionAvailable && targetPosition != STOP_POSITION) { + // new target state was set, let's handle it + int newDirection = STOP_DIR; + if (targetPosition == MOVE_UP_POSITION) { + newDirection = UP_DIR; + operationTimeout = 60000; + } else if (targetPosition == MOVE_DOWN_POSITION) { + newDirection = DOWN_DIR; + operationTimeout = 60000; + } else { + operationTimeout = 0; + int newMovementValue = targetPosition - currentPosition; + // 0 - 100 = -100 (move down); 50 - + // 20 = 30 (move up 30%), etc + if (newMovementValue > 0) { + newDirection = DOWN_DIR; // move down + } else if (newMovementValue < 0) { + newDirection = UP_DIR; // move up + } + } + // If new direction is the same as current move, then keep movin` + if (newDirection == currentDirection) { + newTargetPositionAvailable = false; + } else if (currentDirection == STOP_DIR) { // else start moving + newTargetPositionAvailable = false; + lastPositionBeforeMovement = currentPosition; + lastMovementStartTime = millis(); + if (newDirection == DOWN_DIR) { + startClosing(); + } else { + startOpening(); + } + } else { // else stop before changing direction + stopMovement(); + } + } + // if (newCurrentPosition != currentPosition) { + // currentPosition = newCurrentPosition; + channel.setNewValue( + currentPosition); // value set on channel will be send to server + // during iterateConnected() execution + // } +} + +Channel *RollerShutter::getChannel() { + return &channel; +} + +void RollerShutter::configComfortUpValue(uint8_t position) { + comfortUpValue = position; + if (comfortUpValue > 100) { + comfortUpValue = 100; + } +} + +void RollerShutter::configComfortDownValue(uint8_t position) { + comfortDownValue = position; + if (comfortDownValue > 100) { + comfortDownValue = 100; + } +} + +void RollerShutter::onLoadState() { + RollerShutterStateData data; + if (Supla::Storage::ReadState((unsigned char *)&data, sizeof(data))) { + closingTimeMs = data.closingTimeMs; + openingTimeMs = data.openingTimeMs; + currentPosition = data.currentPosition; + if (currentPosition >= 0) { + calibrate = false; + } + Serial.print(F("RollerShutter[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("] settings restored from storage. Opening time: ")); + Serial.print(openingTimeMs); + Serial.print(F(" ms; closing time: ")); + Serial.print(closingTimeMs); + Serial.print(F(" ms. Position: ")); + Serial.println(currentPosition); + } +} + +void RollerShutter::onSaveState() { + RollerShutterStateData data; + data.closingTimeMs = closingTimeMs; + data.openingTimeMs = openingTimeMs; + data.currentPosition = currentPosition; + + Supla::Storage::WriteState((unsigned char *)&data, sizeof(data)); +} + +}; // namespace Control +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.h b/lib/SuplaDevice/src/supla/control/roller_shutter.h new file mode 100644 index 00000000..0b479cd7 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.h @@ -0,0 +1,112 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _roller_shutter_h +#define _roller_shutter_h + +#include + +#include "../io.h" +#include "../channel.h" +#include "../element.h" +#include "../triggerable.h" +#include "../actions.h" + +#define UNKNOWN_POSITION -1 +#define STOP_POSITION -2 +#define MOVE_UP_POSITION -3 +#define MOVE_DOWN_POSITION -4 + +namespace Supla { +namespace Control { + +enum Directions { STOP_DIR, DOWN_DIR, UP_DIR }; + +class RollerShutter : public Element, public Triggerable { + public: + RollerShutter(int pinUp, int pinDown, bool highIsOn = true); + + int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); + void runAction(int trigger, int action); + + void close(); // Sets target position to 100% + void open(); // Sets target position to 0% + void stop(); // Stop motor + void moveUp(); // start opening roller shutter regardless of its position (keep motor going up) + void moveDown(); // starts closing roller shutter regardless of its position (keep motor going down) + void setTargetPosition(int newPosition); + + void configComfortUpValue(uint8_t position); + void configComfortDownValue(uint8_t position); + + + void onInit(); + void onTimer(); + void onLoadState(); + void onSaveState(); + + protected: + virtual void stopMovement(); + virtual void relayDownOn(); + virtual void relayUpOn(); + virtual void relayDownOff(); + virtual void relayUpOff(); + virtual void startClosing(); + virtual void startOpening(); + virtual void switchOffRelays(); + void setOpenCloseTime(uint32_t newClosingTimeMs, uint32_t newOpeningTimeMs); + + bool lastDirectionWasOpen(); + bool lastDirectionWasClose(); + bool inMove(); + + Channel *getChannel(); + + Channel channel; + + uint32_t closingTimeMs; + uint32_t openingTimeMs; + bool calibrate; // set to true when new closing/opening time is given - calibration is done to sync roller shutter position + + uint8_t comfortDownValue; + uint8_t comfortUpValue; + + bool newTargetPositionAvailable; + + bool highIsOn; + + uint8_t currentDirection; // stop, up, down + uint8_t lastDirection; + + int8_t currentPosition; // 0 - closed; 100 - opened + int8_t targetPosition; // 0-100 + int8_t lastPositionBeforeMovement; // 0-100 + + int pinUp; + int pinDown; + + unsigned long lastMovementStartTime; + unsigned long doNothingTime; + unsigned long calibrationTime; + unsigned long operationTimeout; +}; + +}; // namespace Control +}; // namespace Supla + +#endif + + diff --git a/lib/SuplaDevice/src/supla/control/virtual_relay.h b/lib/SuplaDevice/src/supla/control/virtual_relay.h new file mode 100644 index 00000000..1a54c51f --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/virtual_relay.h @@ -0,0 +1,70 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _virtual_relay_h +#define _virtual_relay_h + +#include "relay.h" + +namespace Supla { +namespace Control { +class VirtualRelay : public Relay { + public: + VirtualRelay(_supla_int_t functions = + (0xFF ^ SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER)) + : Relay(-1, true, functions), state(false) { + } + + void onInit() { + if (stateOnInit == STATE_ON_INIT_ON || + stateOnInit == STATE_ON_INIT_RESTORED_ON) { + turnOn(); + } else { + turnOff(); + } + } + + void turnOn(_supla_int_t duration = 0) { + durationMs = duration; + durationTimestamp = millis(); + if (keepTurnOnDurationMs) { + durationMs = storedTurnOnDurationMs; + } + state = true; + + channel.setNewValue(state); + } + + virtual void turnOff(_supla_int_t duration = 0) { + durationMs = duration; + durationTimestamp = millis(); + state = false; + + channel.setNewValue(state); + } + + virtual bool isOn() { + return state; + } + + protected: + bool state; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/element.cpp b/lib/SuplaDevice/src/supla/element.cpp new file mode 100644 index 00000000..0839a8f8 --- /dev/null +++ b/lib/SuplaDevice/src/supla/element.cpp @@ -0,0 +1,112 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include "supla/element.h" + +namespace Supla { +Element *Element::firstPtr = nullptr; + +Element::Element() : nextPtr(nullptr) { + if (firstPtr == nullptr) { + firstPtr = this; + } else { + last()->nextPtr = this; + } +} + +Element *Element::begin() { + return firstPtr; +} + +Element *Element::last() { + Element *ptr = firstPtr; + while (ptr && ptr->nextPtr) { + ptr = ptr->nextPtr; + } + return ptr; +} + +Element *Element::getElementByChannelNumber(int channelNumber) { + Element *element = begin(); + while (element != nullptr && element->getChannelNumber() != channelNumber) { + element = element->next(); + } + + return element; +} + +Element *Element::next() { + return nextPtr; +} + +void Element::onInit(){}; + +void Element::onLoadState(){}; + +void Element::onSaveState(){}; + +void Element::iterateAlways(){}; + +bool Element::iterateConnected(void *srpc) { + Channel *channel = getChannel(); + if (channel && channel->isUpdateReady() && + millis() - channel->lastCommunicationTimeMs > 100) { + channel->lastCommunicationTimeMs = millis(); + channel->sendUpdate(srpc); + return false; + } + return true; +} + +void Element::onTimer(){}; + +void Element::onFastTimer(){}; + +int Element::handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue) { + return -1; +} + +int Element::getChannelNumber() { + int result = -1; + Channel *channel = getChannel(); + if (channel) { + result = channel->getChannelNumber(); + } + return result; +} + +Channel *Element::getChannel() { + return nullptr; +} + +void Element::handleGetChannelState(TDSC_ChannelState &channelState) { + return; +} + +int Element::handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) { + return SUPLA_CALCFG_RESULT_NOT_SUPPORTED; +} + +Element & Element::disableChannelState() { + if (getChannel()) { + getChannel()->unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); + } + return *this; +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/element.h b/lib/SuplaDevice/src/supla/element.h new file mode 100644 index 00000000..f2ee9fdc --- /dev/null +++ b/lib/SuplaDevice/src/supla/element.h @@ -0,0 +1,90 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _element_h +#define _element_h + +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +#include +#endif +#include "channel.h" + +namespace Supla { + +class Element { + public: + Element(); + static Element *begin(); + static Element *last(); + static Element *getElementByChannelNumber(int channelNumber); + Element *next(); + + // method called during SuplaDevice initialization. I.e. load initial state, + // initialize pins etc. + virtual void onInit(); + + // method called during Config initialization (i.e. read from EEPROM, FRAM). + // Called only if Storage class is configured + virtual void onLoadState(); + + // method called during periodically during SuplaDevice iteration + // Called only if Storage class is configured + virtual void onSaveState(); + + // method called on each SuplaDevice iteration (before Network layer + // iteration). When Device is connected, both iterateAlways() and + // iterateConnected() are called. + virtual void iterateAlways(); + + // method called on each Supla::Device iteration when Device is connected and + // registered to Supla server + virtual bool iterateConnected(void *srpc); + + // method called on timer interupt + // Include all actions that have to be executed periodically regardless of + // other SuplaDevice activities + virtual void onTimer(); + + // method called on fast timer interupt + // Include all actions that have to be executed periodically regardless of + // other SuplaDevice activities + virtual void onFastTimer(); + + // return value: + // -1 - don't send reply to server + // 0 - success==false + // 1 - success==true + virtual int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); + + // Handles "get channel state" request from server + // channelState is prefilled with network and device status informations + virtual void handleGetChannelState(TDSC_ChannelState &channelState); + + virtual int handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request); + + int getChannelNumber(); + + Element &disableChannelState(); + + protected: + virtual Channel *getChannel(); + static Element *firstPtr; + Element *nextPtr; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/events.h b/lib/SuplaDevice/src/supla/events.h new file mode 100644 index 00000000..88014963 --- /dev/null +++ b/lib/SuplaDevice/src/supla/events.h @@ -0,0 +1,41 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _events_h +#define _events_h + +namespace Supla { + +enum Event { +// Supla::Control::Button events + ON_PRESS, // Triggered on transition to valueOnPress() + ON_RELEASE, // Triggered on transition from valueOnPress() + ON_CHANGE, // Triggered on all transitions + ON_HOLD, // Triggered when button is hold + ON_CLICK_1, // ON_MULTI_x is triggered when multiclick is detected + ON_CLICK_2, + ON_CLICK_3, + ON_CLICK_4, + ON_CLICK_5, + ON_CLICK_6, + ON_CLICK_7, + ON_CLICK_8, + ON_CLICK_9 +}; + +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/io.cpp b/lib/SuplaDevice/src/supla/io.cpp new file mode 100644 index 00000000..fa7a24e0 --- /dev/null +++ b/lib/SuplaDevice/src/supla/io.cpp @@ -0,0 +1,57 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "io.h" + +#include + +namespace Supla { +int Io::digitalRead(int channelNumber, uint8_t pin) { + if (ioInstance) { + return ioInstance->customDigitalRead(channelNumber, pin); + } + return ::digitalRead(pin); +} + +void Io::digitalWrite(int channelNumber, uint8_t pin, uint8_t val) { + Serial.print(" **** Digital write["); + Serial.print(channelNumber); + Serial.print("], pin: "); + Serial.print(pin); + Serial.print("; value: "); + Serial.println(val); + if (ioInstance) { + ioInstance->customDigitalWrite(channelNumber, pin, val); + return; + } + ::digitalWrite(pin, val); +} + +Io *Io::ioInstance = 0; + +Io::Io() { + ioInstance = this; +} + +int Io::customDigitalRead(int channelNumber, uint8_t pin) { + return ::digitalRead(pin); +} + +void Io::customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val) { + ::digitalWrite(pin, val); +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/io.h b/lib/SuplaDevice/src/supla/io.h new file mode 100644 index 00000000..461f8b6a --- /dev/null +++ b/lib/SuplaDevice/src/supla/io.h @@ -0,0 +1,44 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_io_h +#define _supla_io_h + +#include + +namespace Supla { +// This class can be used to override digitalRead and digitalWrite methods. +// If you want to add custom behavior i.e. during read/write from some +// digital pin, you can inherit from Supla::Io class, implement your +// own customDigitalRead and customDigitalWrite methods and create instance +// of this class. It will automatically register and SuplaDevice will use it. +// +// Example use: implement some additional logic, when relay state is +// changed. +class Io { + public: + static int digitalRead(int channelNumber, uint8_t pin); + static void digitalWrite(int channelNumber, uint8_t pin, uint8_t val); + + static Io *ioInstance; + + Io(); + virtual int customDigitalRead(int channelNumber, uint8_t pin); + virtual void customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val); +}; +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/local_action.cpp b/lib/SuplaDevice/src/supla/local_action.cpp new file mode 100644 index 00000000..cc4c08eb --- /dev/null +++ b/lib/SuplaDevice/src/supla/local_action.cpp @@ -0,0 +1,46 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "supla/local_action.h" + +namespace Supla { + +LocalAction::LocalAction() : registeredClientsCount(0) { +} + +void LocalAction::addAction(int action, Triggerable &client, int event) { + if (registeredClientsCount < MAX_TRIGGERABLE_CLIENTS) { + clients[registeredClientsCount].client = &client; + clients[registeredClientsCount].onEvent = event; + clients[registeredClientsCount].action = action; + registeredClientsCount++; + } +} + +void LocalAction::addAction(int action, Triggerable *client, int event) { + addAction(action, *client, event); +} + +void LocalAction::runAction(int event) { + for (int i = 0; i < registeredClientsCount; i++) { + if (clients[i].onEvent == event) { + clients[i].client->runAction(event, clients[i].action); + } + } +} + +}; // namespace Supla + diff --git a/lib/SuplaDevice/src/supla/local_action.h b/lib/SuplaDevice/src/supla/local_action.h new file mode 100644 index 00000000..71e52dbd --- /dev/null +++ b/lib/SuplaDevice/src/supla/local_action.h @@ -0,0 +1,49 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _local_action_h +#define _local_action_h + +#include "triggerable.h" + +#define MAX_TRIGGERABLE_CLIENTS 10 + +namespace Supla { + +class TriggerableClient { + public: + Triggerable *client; + int onEvent; + int action; +}; + +class LocalAction { + public: + LocalAction(); + + virtual void addAction(int action, Triggerable &client, int event); + virtual void addAction(int action, Triggerable *client, int event); + + virtual void runAction(int event); + + protected: + TriggerableClient clients[MAX_TRIGGERABLE_CLIENTS]; + int registeredClientsCount; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/network/ENC28J60.h b/lib/SuplaDevice/src/supla/network/ENC28J60.h new file mode 100644 index 00000000..e1241375 --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/ENC28J60.h @@ -0,0 +1,113 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef ENC28J60_h_ +#define ENC28J60_h_ + +#include +#include + +#include "../supla_lib_config.h" +#include "network.h" + +// TODO: change logs to supla_log + +namespace Supla { +class ENC28J60 : public Supla::Network { + public: + ENC28J60(uint8_t mac[6], IPAddress *ip = NULL) : Network(ip) { + memcpy(this->mac, mac, 6); + } + + int read(void *buf, int count) { + _supla_int_t size = client.available(); + + if (size > 0) { + if (size > count) size = count; + long readSize = client.read((uint8_t *)buf, size); +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Received: [")); + for (int i = 0; i < readSize; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + return readSize; + }; + + return -1; + } + + int write(void *buf, int count) { +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Sending: [")); + for (int i = 0; i < count; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + long sendSize = client.write((const uint8_t *)buf, count); + return sendSize; + } + + int connect(const char *server, int port = -1) { + int connectionPort = (port == -1 ? 2015 : port); + supla_log( + LOG_DEBUG, "Establishing connection with: %s (port: %d)", server, connectionPort); + + return client.connect(server, connectionPort); + } + + bool connected() { + return client.connected(); + } + + void disconnect() { + client.stop(); + } + + bool isReady() { + return true; + } + + void setup(uint8_t mac[6]) { + Serial.println(F("Connecting to network...")); + if (useLocalIp) { + Ethernet.begin(mac, localIp); + } else { + Ethernet.begin(mac); + } + + Serial.print(F("localIP: ")); + Serial.println(Ethernet.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(Ethernet.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(Ethernet.gatewayIP()); + Serial.print(F("dnsServerIP: ")); + Serial.println(Ethernet.dnsServerIP()); + } + + protected: + EthernetClient client; + uint8_t mac[6]; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/network/esp32_wifi.h b/lib/SuplaDevice/src/supla/network/esp32_wifi.h new file mode 100644 index 00000000..fa3915d1 --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/esp32_wifi.h @@ -0,0 +1,133 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef esp_wifi_h__ +#define esp_wifi_h__ + +#include +#include + +#include "../supla_lib_config.h" +#include "network.h" + +#define MAX_SSID_SIZE 32 +#define MAX_WIFI_PASSWORD_SIZE 64 + +// TODO: change logs to supla_log + +namespace Supla { +class ESP32Wifi : public Supla::Network { + public: + ESP32Wifi(const char *wifiSsid, + const char *wifiPassword, + IPAddress *ip = NULL) + : Network(ip) { + strcpy(ssid, wifiSsid); + strcpy(password, wifiPassword); + } + + int read(void *buf, int count) { + _supla_int_t size = client.available(); + + if (size > 0) { + if (size > count) size = count; + long readSize = client.read((uint8_t *)buf, size); +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Received: [")); + for (int i = 0; i < readSize; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + + return readSize; + } + return -1; + } + + int write(void *buf, int count) { +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Sending: [")); + for (int i = 0; i < count; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + long sendSize = client.write((const uint8_t *)buf, count); + return sendSize; + } + + int connect(const char *server, int port = -1) { + int connectionPort = (port == -1 ? 2015 : port); + supla_log( + LOG_DEBUG, "Establishing connection with: %s (port: %d)", server, connectionPort); + return client.connect(server, connectionPort); + } + + bool connected() { + return client.connected(); + } + + bool isReady() { + return WiFi.status() == WL_CONNECTED; + } + + void disconnect() { + client.stop(); + } + + // TODO: add handling of custom local ip + void setup() { + WiFiEventId_t event_gotIP = WiFi.onEvent( + [](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.print(F("local IP: ")); + Serial.println(WiFi.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(WiFi.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(WiFi.gatewayIP()); + long rssi = WiFi.RSSI(); + Serial.print(F("Signal Strength (RSSI): ")); + Serial.print(rssi); + Serial.println(F(" dBm")); + }, + WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); + + WiFiEventId_t event_disconnected = WiFi.onEvent( + [](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.println(F("wifi Station disconnected")); + }, + WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); + + Serial.print(F("WIFI: establishing connection with SSID: \"")); + Serial.print(ssid); + Serial.println(F("\"")); + WiFi.begin(ssid, password); + yield(); + } + + protected: + WiFiClient client; + + char ssid[MAX_SSID_SIZE]; + char password[MAX_WIFI_PASSWORD_SIZE]; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/network/esp_wifi.h b/lib/SuplaDevice/src/supla/network/esp_wifi.h new file mode 100644 index 00000000..78175be6 --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/esp_wifi.h @@ -0,0 +1,233 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef esp_wifi_h__ +#define esp_wifi_h__ + +#include +#include +#include + +#include "../supla_lib_config.h" +#include "network.h" + +#define MAX_SSID_SIZE 32 +#define MAX_WIFI_PASSWORD_SIZE 64 + +WiFiEventHandler gotIpEventHandler, disconnectedEventHandler; + +// TODO: change logs to supla_log + +namespace Supla { +class ESPWifi : public Supla::Network { + public: + ESPWifi(const char *wifiSsid = nullptr, + const char *wifiPassword = nullptr, + IPAddress *ip = nullptr) + : Network(ip), client(nullptr), isSecured(true), wifiConfigured(false) { + ssid[0] = '\0'; + password[0] = '\0'; + setSsid(wifiSsid); + setPassword(wifiPassword); + } + + int read(void *buf, int count) { + _supla_int_t size = client->available(); + + if (size > 0) { + if (size > count) size = count; + long readSize = client->read((uint8_t *)buf, size); +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Received: [")); + for (int i = 0; i < readSize; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + + return readSize; + } + return -1; + } + + int write(void *buf, int count) { +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Sending: [")); + for (int i = 0; i < count; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + long sendSize = client->write((const uint8_t *)buf, count); + return sendSize; + } + + int connect(const char *server, int port = -1) { + String message; + if (client == NULL) { + if (isSecured) { + message = "Secured connection"; + client = new WiFiClientSecure(); + if (fingerprint.length() > 0) { + message += " with certificate matching"; + ((WiFiClientSecure *)client)->setFingerprint(fingerprint.c_str()); + } else { + message += " without certificate matching"; + ((WiFiClientSecure *)client)->setInsecure(); + } + } else { + message = "unsecured connection"; + client = new WiFiClient(); + } + } + + int connectionPort = (isSecured ? 2016 : 2015); + if (port != -1) { + connectionPort = port; + } + + supla_log(LOG_DEBUG, + "Establishing %s with: %s (port: %d)", + message.c_str(), + server, + connectionPort); + + // static_cast(client)->setBufferSizes(512, 512); // + // EXPERIMENTAL + + bool result = client->connect(server, connectionPort); + + if (result && isSecured) { + if (!((WiFiClientSecure *)client)->verify(fingerprint.c_str(), server)) { + supla_log(LOG_DEBUG, "Provided certificates doesn't match!"); + client->stop(); + return false; + } + }; + + return result; + } + + bool connected() { + return (client != NULL) && client->connected(); + } + + bool isReady() { + return WiFi.status() == WL_CONNECTED; + } + + void disconnect() { + if (client != nullptr) { + client->stop(); + } + } + + // TODO: add handling of custom local ip + void setup() { + if (!wifiConfigured) { + wifiConfigured = true; + gotIpEventHandler = + WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &event) { + Serial.print(F("local IP: ")); + Serial.println(WiFi.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(WiFi.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(WiFi.gatewayIP()); + long rssi = WiFi.RSSI(); + Serial.print(F("Signal strength (RSSI): ")); + Serial.print(rssi); + Serial.println(F(" dBm")); + }); + disconnectedEventHandler = WiFi.onStationModeDisconnected( + [](const WiFiEventStationModeDisconnected &event) { + Serial.println(F("WiFi station disconnected")); + }); + + Serial.print(F("WiFi: establishing connection with SSID: \"")); + Serial.print(ssid); + Serial.println(F("\"")); + WiFi.begin(ssid, password); + } else { + Serial.println(F("WiFi: resetting WiFi connection")); + if (client) { + delete client; + client = nullptr; + } + WiFi.reconnect(); + } + + yield(); + } + + void enableSSL(bool value) { + isSecured = value; + } + + void setServersCertFingerprint(String value) { + fingerprint = value; + } + + void setSsid(const char *wifiSsid) { + if (wifiSsid) { + strncpy(ssid, wifiSsid, MAX_SSID_SIZE); + } + } + + void setPassword(const char *wifiPassword) { + if (wifiPassword) { + strncpy(password, wifiPassword, MAX_WIFI_PASSWORD_SIZE); + } + } + + void setTimeout(int timeoutMs) { + if (client) { + client->setTimeout(timeoutMs); + } + } + + void fillStateData(TDSC_ChannelState &channelState) { + channelState.Fields |= SUPLA_CHANNELSTATE_FIELD_IPV4 | + SUPLA_CHANNELSTATE_FIELD_MAC | + SUPLA_CHANNELSTATE_FIELD_WIFIRSSI | + SUPLA_CHANNELSTATE_FIELD_WIFISIGNALSTRENGTH; + channelState.IPv4 = WiFi.localIP(); + WiFi.macAddress(channelState.MAC); + int rssi = WiFi.RSSI(); + channelState.WiFiRSSI = rssi; + if (rssi > -50) { + channelState.WiFiSignalStrength = 100; + } else if (rssi <= -100) { + channelState.WiFiSignalStrength = 0; + } else { + channelState.WiFiSignalStrength = 2 * (rssi + 100); + } + } + + protected: + WiFiClient *client = NULL; + bool isSecured; + bool wifiConfigured; + String fingerprint; + char ssid[MAX_SSID_SIZE]; + char password[MAX_WIFI_PASSWORD_SIZE]; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/network/ethernet_shield.h b/lib/SuplaDevice/src/supla/network/ethernet_shield.h new file mode 100644 index 00000000..7d5e7e7a --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/ethernet_shield.h @@ -0,0 +1,130 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef ethernet_shield_h__ +#define ethernet_shield_h__ + +#include +#include + +#include "../supla_lib_config.h" +#include "network.h" + +// TODO: change logs to supla_log + +namespace Supla { +class EthernetShield : public Supla::Network { + public: + EthernetShield(uint8_t mac[6], IPAddress *ip = NULL) : Network(ip), isDeviceReady(false) { + memcpy(this->mac, mac, 6); + } + + int read(void *buf, int count) { + _supla_int_t size = client.available(); + if (size > 0) { + if (size > count) size = count; + long readSize = client.read((uint8_t *)buf, size); +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Received: [")); + for (int i = 0; i < readSize; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + return readSize; + }; + + return -1; + } + + int write(void *buf, int count) { +#ifdef SUPLA_COMM_DEBUG + Serial.print(F("Sending: [")); + for (int i = 0; i < count; i++) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + Serial.println(F("]")); +#endif + long sendSize = client.write((const uint8_t *)buf, count); + return sendSize; + } + + int connect(const char *server, int port = -1) { + int connectionPort = (port == -1 ? 2015 : port); + supla_log( + LOG_DEBUG, "Establishing connection with: %s (port: %d)", server, connectionPort); + + return client.connect(server, connectionPort); + } + + bool connected() { + return client.connected(); + } + + void disconnect() { + client.stop(); + } + + bool isReady() { + return isDeviceReady; + } + + void setup() { + Serial.println(F("Connecting to network...")); + if (useLocalIp) { + Ethernet.begin(mac, localIp); + isDeviceReady = true; + } else { + int result = false; + result = Ethernet.begin(mac, 10000, 4000); + Serial.print(F("DHCP connection result: ")); + Serial.println(result); + isDeviceReady = result == 1 ? true : false; + } + + Serial.print(F("localIP: ")); + Serial.println(Ethernet.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(Ethernet.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(Ethernet.gatewayIP()); + Serial.print(F("dnsServerIP: ")); + Serial.println(Ethernet.dnsServerIP()); + } + + bool iterate() { + Ethernet.maintain(); + return true; + } + + void fillStateData(TDSC_ChannelState &channelState) { + channelState.Fields |= SUPLA_CHANNELSTATE_FIELD_IPV4 | + SUPLA_CHANNELSTATE_FIELD_MAC; + channelState.IPv4 = Ethernet.localIP(); + Ethernet.MACAddress(channelState.MAC); + } + + protected: + EthernetClient client; + uint8_t mac[6]; + bool isDeviceReady; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/network/network.cpp b/lib/SuplaDevice/src/supla/network/network.cpp new file mode 100644 index 00000000..0a612f6e --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/network.cpp @@ -0,0 +1,210 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include + +#include "SuplaDevice.h" +#include "supla-common/log.h" +#include "supla-common/srpc.h" +#include "supla/element.h" +#include "supla/network/network.h" + +namespace Supla { + +Network *Network::netIntf = NULL; + +_supla_int_t data_read(void *buf, _supla_int_t count, void *sdc) { + return Supla::Network::Read(buf, count); +} + +_supla_int_t data_write(void *buf, _supla_int_t count, void *sdc) { + _supla_int_t r = Supla::Network::Write(buf, count); + if (r > 0) { + Network::Instance()->updateLastSent(); + } + return r; +} + +void message_received(void *_srpc, + unsigned _supla_int_t rr_id, + unsigned _supla_int_t call_type, + void *_sdc, + unsigned char proto_version) { + TsrpcReceivedData rd; + char result; + + Network::Instance()->updateLastResponse(); + + if (SUPLA_RESULT_TRUE == (result = srpc_getdata(_srpc, &rd, 0))) { + switch (rd.call_type) { + case SUPLA_SDC_CALL_VERSIONERROR: + ((SuplaDeviceClass *)_sdc)->onVersionError(rd.data.sdc_version_error); + break; + case SUPLA_SD_CALL_REGISTER_DEVICE_RESULT: + ((SuplaDeviceClass *)_sdc) + ->onRegisterResult(rd.data.sd_register_device_result); + break; + case SUPLA_SD_CALL_CHANNEL_SET_VALUE: { + auto element = Supla::Element::getElementByChannelNumber( + rd.data.sd_channel_new_value->ChannelNumber); + if (element) { + int actionResult = + element->handleNewValueFromServer(rd.data.sd_channel_new_value); + if (actionResult != -1) { + srpc_ds_async_set_channel_result( + _srpc, + rd.data.sd_channel_new_value->ChannelNumber, + rd.data.sd_channel_new_value->SenderID, + actionResult); + } + } else { + Serial.print(F("Error: couldn't find element for a requested channel [")); + Serial.print(rd.data.sd_channel_new_value->ChannelNumber); + Serial.println(F("]")); + } + break; + } + case SUPLA_SDC_CALL_SET_ACTIVITY_TIMEOUT_RESULT: + ((SuplaDeviceClass *)_sdc) + ->channelSetActivityTimeoutResult( + rd.data.sdc_set_activity_timeout_result); + break; + case SUPLA_CSD_CALL_GET_CHANNEL_STATE: { + TDSC_ChannelState state; + memset(&state, 0, sizeof(TDSC_ChannelState)); + state.ReceiverID = rd.data.csd_channel_state_request->SenderID; + state.ChannelNumber = rd.data.csd_channel_state_request->ChannelNumber; + Network::Instance()->fillStateData(state); + ((SuplaDeviceClass *)_sdc)->fillStateData(state); + auto element = Supla::Element::getElementByChannelNumber( + rd.data.csd_channel_state_request->ChannelNumber); + if (element) { + element->handleGetChannelState(state); + } + srpc_csd_async_channel_state_result(_srpc, &state); + break; + } + case SUPLA_SDC_CALL_PING_SERVER_RESULT: + break; + + case SUPLA_DCS_CALL_GET_USER_LOCALTIME_RESULT: { + ((SuplaDeviceClass *)_sdc)->onGetUserLocaltimeResult(rd.data.sdc_user_localtime_result); + break; + } + case SUPLA_SD_CALL_DEVICE_CALCFG_REQUEST: { + TDS_DeviceCalCfgResult result; + result.ReceiverID = rd.data.sd_device_calcfg_request->SenderID; + result.ChannelNumber = rd.data.sd_device_calcfg_request->ChannelNumber; + result.Command = rd.data.sd_device_calcfg_request->Command; + result.Result = SUPLA_CALCFG_RESULT_NOT_SUPPORTED; + result.DataSize = 0; + + if (rd.data.sd_device_calcfg_request->SuperUserAuthorized != 1) { + result.Result = SUPLA_CALCFG_RESULT_UNAUTHORIZED; + } else { + auto element = Supla::Element::getElementByChannelNumber( + rd.data.sd_device_calcfg_request->ChannelNumber); + if (element) { + result.Result = element->handleCalcfgFromServer(rd.data.sd_device_calcfg_request); + } else { + Serial.print(F("Error: couldn't find element for a requested channel [")); + Serial.print(rd.data.sd_channel_new_value->ChannelNumber); + Serial.println(F("]")); + } + } + + srpc_ds_async_device_calcfg_result(_srpc, &result); + break; + } + default: + supla_log(LOG_DEBUG, "Received unknown message from server!"); + break; + } + + srpc_rd_free(&rd); + + } else if (result == SUPLA_RESULT_DATA_ERROR) { + supla_log(LOG_DEBUG, "DATA ERROR!"); + } +} + +Network::Network(IPAddress *ip) { + lastSentMs = 0; + srpc = NULL; + lastPingTimeMs = 0; + serverActivityTimeoutS = 30; + lastResponseMs = 0; + + netIntf = this; + + if (ip == NULL) { + useLocalIp = false; + } else { + useLocalIp = true; + localIp = *ip; + } +} + +bool Network::iterate() { +} + +void Network::updateLastSent() { + lastSentMs = millis(); +} + +void Network::updateLastResponse() { + lastResponseMs = millis(); +} + +void Network::setSrpc(void *_srpc) { + srpc = _srpc; +} + +bool Network::ping() { + _supla_int64_t _millis = millis(); + // If time from last response is longer than "server_activity_timeout + 10 s", + // then inform about failure in communication + if ((_millis - lastResponseMs) / 1000 >= (serverActivityTimeoutS + 10)) { + return false; + } else if (_millis - lastPingTimeMs >= 5000 && + ((_millis - lastResponseMs) / 1000 >= + (serverActivityTimeoutS - 5) || + (_millis - lastSentMs) / 1000 >= (serverActivityTimeoutS - 5))) { + lastPingTimeMs = _millis; + srpc_dcs_async_ping_server(srpc); + } + return true; +} + +void Network::clearTimeCounters() { + _supla_int64_t currentTime = millis(); + lastSentMs = currentTime; + lastResponseMs = currentTime; + lastPingTimeMs = currentTime; +} + +void Network::setActivityTimeout(_supla_int_t activityTimeoutSec) { + serverActivityTimeoutS = activityTimeoutSec; +} + +void Network::setTimeout(int timeoutMs) { + supla_log(LOG_DEBUG, "setTimeout is not implemented for this interface"); +} + +void Network::fillStateData(TDSC_ChannelState &channelState) { + supla_log(LOG_DEBUG, "fillStateData is not implemented for this interface"); +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/network/network.h b/lib/SuplaDevice/src/supla/network/network.h new file mode 100644 index 00000000..447b69ab --- /dev/null +++ b/lib/SuplaDevice/src/supla/network/network.h @@ -0,0 +1,147 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _network_interface_h +#define _network_interface_h + +#include + +#include "supla-common/log.h" +#include "supla-common/proto.h" + +namespace Supla { +class Network { + public: + static Network *Instance() { + return netIntf; + } + + static bool Connected() { + if (Instance() != NULL) { + return Instance()->connected(); + } + return false; + } + + static int Read(void *buf, int count) { + if (Instance() != NULL) { + return Instance()->read(buf, count); + } + return -1; + } + + static int Write(void *buf, int count) { + if (Instance() != NULL) { + return Instance()->write(buf, count); + } + return -1; + } + + static int Connect(const char *server, int port = -1) { + if (Instance() != NULL) { + Instance()->clearTimeCounters(); + return Instance()->connect(server, port); + } + return 0; + } + + static void Disconnect() { + if (Instance() != NULL) { + return Instance()->disconnect(); + } + return; + } + + static void Setup() { + if (Instance() != NULL) { + return Instance()->setup(); + } + return; + } + + static bool IsReady() { + if (Instance() != NULL) { + return Instance()->isReady(); + } + return false; + } + + static bool Iterate() { + if (Instance() != NULL) { + return Instance()->iterate(); + } + return false; + } + + static void SetSrpc(void *_srpc) { + if (Instance() != NULL) { + Instance()->setSrpc(_srpc); + } + } + + static bool Ping() { + if (Instance() != NULL) { + return Instance()->ping(); + } + } + + Network(IPAddress *ip); + virtual int read(void *buf, int count) = 0; + virtual int write(void *buf, int count) = 0; + virtual int connect(const char *server, int port = -1) = 0; + virtual bool connected() = 0; + virtual void disconnect() = 0; + virtual void setup() = 0; + virtual void setTimeout(int); + + virtual bool isReady() = 0; + virtual bool iterate(); + virtual bool ping(); + + virtual void fillStateData(TDSC_ChannelState &channelState); + + void setSrpc(void *_srpc); + void updateLastSent(); + void updateLastResponse(); + void clearTimeCounters(); + void setActivityTimeout(_supla_int_t activityTimeoutSec); + + protected: + static Network *netIntf; + _supla_int64_t lastSentMs; + _supla_int64_t lastResponseMs; + _supla_int64_t lastPingTimeMs; + _supla_int_t serverActivityTimeoutS; + void *srpc; + + bool useLocalIp; + IPAddress localIp; +}; + +// Method passed to SRPC as a callback to read raw data from network interface +_supla_int_t data_read(void *buf, _supla_int_t count, void *sdc); +// Method passed to SRPC as a callback to write raw data to network interface +_supla_int_t data_write(void *buf, _supla_int_t count, void *sdc); +// Method passed to SRPC as a callback to handle response from Supla server +void message_received(void *_srpc, + unsigned _supla_int_t rr_id, + unsigned _supla_int_t call_type, + void *_sdc, + unsigned char proto_version); + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/pv/afore.cpp b/lib/SuplaDevice/src/supla/pv/afore.cpp new file mode 100644 index 00000000..4f8fe29b --- /dev/null +++ b/lib/SuplaDevice/src/supla/pv/afore.cpp @@ -0,0 +1,138 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "afore.h" + +using namespace Supla; +using namespace PV; + +Afore::Afore(IPAddress ip, int port, const char *loginAndPass) + : ip(ip), + port(port), + dataIsReady(false), + totalGeneratedEnergy(0), + currentPower(0), + varFound(false), + bytesCounter(0), + dataFetchInProgress(false), + retryCounter(0), + vFound(false) { + int len = strlen(loginAndPass); + if (len > LOGIN_AND_PASSOWORD_MAX_LENGTH) { + len = LOGIN_AND_PASSOWORD_MAX_LENGTH; + } + strncpy(loginAndPassword, loginAndPass, len); +} + +void Afore::iterateAlways() { + if (dataFetchInProgress) { + if (!pvClient.connected()) { + Serial.println(F("AFORE fetch completed")); + dataFetchInProgress = false; + dataIsReady = true; + } + if (pvClient.available()) { + Serial.print(F("Reading data from afore: ")); + Serial.println(pvClient.available()); + } + while (pvClient.available()) { + char c; + c = pvClient.read(); + if (c == '\n') { + if (varFound) { + if (bytesCounter > 79) bytesCounter = 79; + buf[bytesCounter] = '\0'; + char varName[80]; + char varValue[80]; + sscanf(buf, "%s = \"%s\";", varName, varValue); + if (strncmp(varName, "webdata_now_p", strlen("webdata_now_p")) == 0) { + float curPower = atof(varValue); + Serial.print(F("Current power: ")); + Serial.println(curPower); + currentPower = curPower * 100000; + } + if (strncmp(varName, "webdata_total_e", strlen("webdata_total_e")) == + 0) { + float totalProd = atof(varValue); + Serial.print(F("Total production: ")); + Serial.println(totalProd); + + totalGeneratedEnergy = totalProd * 100000; + } + } + bytesCounter = 0; + vFound = false; + varFound = false; + } else if (c == 'v' || vFound) { + vFound = true; + if (bytesCounter < 80) { + buf[bytesCounter] = c; + } + bytesCounter++; + if (bytesCounter == 4 && !varFound) { + if (strncmp(buf, "var ", 4) == 0) { + varFound = true; + bytesCounter = 0; + } + } + } + } + if (!pvClient.connected()) { + pvClient.stop(); + } + } + if (dataIsReady) { + dataIsReady = false; + setFwdActEnergy(0, totalGeneratedEnergy); + setPowerActive(0, currentPower); + updateChannelValues(); + } +} + +bool Afore::iterateConnected(void *srpc) { + if (!dataFetchInProgress) { + if (lastReadTime == 0 || millis() - lastReadTime > 15000) { + lastReadTime = millis(); + Serial.println(F("AFORE connecting")); + if (pvClient.connect(ip, port)) { + retryCounter = 0; + dataFetchInProgress = true; + Serial.println(F("Succesful connect")); + + pvClient.print("GET /status.html HTTP/1.1\nAuthorization: Basic "); + pvClient.println(loginAndPassword); + pvClient.println("Connection: close"); + pvClient.println(); + + } else { // if connection wasn't successful, try few times. If it fails, + // then assume that inverter is off during the night + Serial.print(F("Failed to connect to Afore at: ")); + Serial.print(ip); + Serial.print(F(":")); + Serial.println(port); + retryCounter++; + if (retryCounter > 3) { + currentPower = 0; + dataIsReady = true; + } + } + } + } + return Element::iterateConnected(srpc); +} + +void Afore::readValuesFromDevice() { +} diff --git a/lib/SuplaDevice/src/supla/pv/afore.h b/lib/SuplaDevice/src/supla/pv/afore.h new file mode 100644 index 00000000..a8113c1d --- /dev/null +++ b/lib/SuplaDevice/src/supla/pv/afore.h @@ -0,0 +1,62 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __afore_h +#define __afore_h + +#include +#include + +#if defined(ARDUINO_ARCH_AVR) +#include +#else +#include +#endif + +#define LOGIN_AND_PASSOWORD_MAX_LENGTH 100 + +namespace Supla { +namespace PV { +class Afore : public Supla::Sensor::OnePhaseElectricityMeter { + public: + Afore(IPAddress ip, int port, const char *loginAndPassword); + void readValuesFromDevice(); + void iterateAlways(); + bool iterateConnected(void *srpc); + + protected: +#if defined(ARDUINO_ARCH_AVR) + EthernetClient pvClient; +#else + WiFiClient pvClient; +#endif + IPAddress ip; + int port; + char loginAndPassword[LOGIN_AND_PASSOWORD_MAX_LENGTH]; + char buf[80]; + _supla_int64_t totalGeneratedEnergy; + _supla_int_t currentPower; + int bytesCounter; + int retryCounter; + bool vFound; + bool varFound; + bool dataIsReady; + bool dataFetchInProgress; +}; +}; // namespace PV +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/pv/fronius.cpp b/lib/SuplaDevice/src/supla/pv/fronius.cpp new file mode 100644 index 00000000..f1beb2dd --- /dev/null +++ b/lib/SuplaDevice/src/supla/pv/fronius.cpp @@ -0,0 +1,190 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "fronius.h" + +using namespace Supla; +using namespace PV; + +enum ParametersToRead { NONE, TOTAL_ENERGY, FAC, IAC, PAC, UAC }; + +Fronius::Fronius(IPAddress ip, int port, int deviceId) + : ip(ip), + port(port), + dataIsReady(false), + totalGeneratedEnergy(0), + currentPower(0), + currentCurrent(0), + currentFreq(0), + currentVoltage(0), + startCharFound(false), + bytesCounter(0), + dataFetchInProgress(false), + retryCounter(0), + valueToFetch(NONE), + deviceId(deviceId) { +} + +void Fronius::iterateAlways() { + if (dataFetchInProgress) { + if (!pvClient.connected()) { + Serial.println(F("Fronius fetch completed")); + dataFetchInProgress = false; + dataIsReady = true; + } + if (pvClient.available()) { + Serial.print(F("Reading data from Fronius: ")); + Serial.println(pvClient.available()); + } + while (pvClient.available()) { + char c; + c = pvClient.read(); + if (c == '\n') { + if (startCharFound) { + if (bytesCounter > 79) bytesCounter = 79; + buf[bytesCounter] = '\0'; + char varName[80]; + char varValue[80]; + sscanf(buf, " %s : %s", varName, varValue); + if (valueToFetch != NONE && strncmp(varName, "Value", strlen("Value")) == 0) { + switch (valueToFetch) { + case TOTAL_ENERGY: { + float totalProd = atof(varValue); + Serial.print(F("Total production: ")); + Serial.print(totalProd); + Serial.println(F(" Wh")); + totalGeneratedEnergy = totalProd * 100; + + break; + } + case PAC: { + float curPower = atof(varValue); + Serial.print(F("Current power: ")); + Serial.println(curPower); + currentPower = curPower * 100000; + + break; + } + case IAC: { + float curCurrent = atof(varValue); + Serial.print(F("Current: ")); + Serial.println(curCurrent); + currentCurrent = curCurrent * 1000; + + break; + } + case FAC: { + float curFreq = atof(varValue); + Serial.print(F("Frequency: ")); + Serial.println(curFreq); + currentFreq = curFreq * 100; + + break; + } + case UAC: { + float curVoltage = atof(varValue); + Serial.print(F("Voltage: ")); + Serial.println(curVoltage); + currentVoltage = curVoltage * 100; + + break; + } + } + valueToFetch = NONE; + } else if (strncmp(varName, "TOTAL_ENERGY\"", strlen("TOTAL_ENERGY")) == 0) { + valueToFetch = TOTAL_ENERGY; + } else if (strncmp(varName, "FAC", strlen("FAC")) == 0) { + valueToFetch = FAC; + } else if (strncmp(varName, "UAC", strlen("UAC")) == 0) { + valueToFetch = UAC; + } else if (strncmp(varName, "IAC", strlen("IAC")) == 0) { + valueToFetch = IAC; + } else if (strncmp(varName, "PAC", strlen("PAC")) == 0) { + valueToFetch = PAC; + } + } + bytesCounter = 0; + startCharFound = false; + } else if (c == '"' || startCharFound) { + startCharFound = true; + if (c == '"') { + c = ' '; + } + if (bytesCounter < 80) { + buf[bytesCounter] = c; + } + bytesCounter++; + } + } + if (!pvClient.connected()) { + pvClient.stop(); + } + } + if (dataIsReady) { + dataIsReady = false; + setFwdActEnergy(0, totalGeneratedEnergy); + setPowerActive(0, currentPower); + setCurrent(0, currentCurrent); + setVoltage(0, currentVoltage); + setFreq(currentFreq); + updateChannelValues(); + } +} + +bool Fronius::iterateConnected(void *srpc) { + if (!dataFetchInProgress) { + if (lastReadTime == 0 || millis() - lastReadTime > 15000) { + lastReadTime = millis(); + Serial.print(F("Fronius connecting ")); + Serial.println(deviceId); + if (pvClient.connect(ip, port)) { + retryCounter = 0; + dataFetchInProgress = true; + Serial.println(F("Succesful connect")); + + char buf[100]; + strcpy(buf, "GET /solar_api/v1/GetInverterRealtimeData.cgi?Scope=Device&DeviceID="); + char idBuf[20]; + sprintf(idBuf, "%d", deviceId); + strcat(buf, idBuf); + strcat(buf, "&DataCollection=CommonInverterData HTTP/1.1"); + pvClient.println(buf); + pvClient.println("Host: localhost"); + pvClient.println("Connection: close"); + pvClient.println(); + + } else { // if connection wasn't successful, try few times. If it fails, + // then assume that inverter is off during the night + Serial.print(F("Failed to connect to Fronius at: ")); + Serial.print(ip); + Serial.print(F(":")); + Serial.println(port); + retryCounter++; + if (retryCounter > 3) { + currentPower = 0; + currentFreq = 0; + currentCurrent = 0; + currentVoltage = 0; + dataIsReady = true; + } + } + } + } + return Element::iterateConnected(srpc); +} + +void Fronius::readValuesFromDevice() { +} diff --git a/lib/SuplaDevice/src/supla/pv/fronius.h b/lib/SuplaDevice/src/supla/pv/fronius.h new file mode 100644 index 00000000..b7278c40 --- /dev/null +++ b/lib/SuplaDevice/src/supla/pv/fronius.h @@ -0,0 +1,63 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __fronius_h +#define __fronius_h + +#include +#include + +#if defined(ARDUINO_ARCH_AVR) +#include +#else +#include +#endif + +namespace Supla { +namespace PV { +class Fronius : public Supla::Sensor::OnePhaseElectricityMeter { + public: + Fronius(IPAddress ip, int port = 80, int deviceId = 1); + void readValuesFromDevice(); + void iterateAlways(); + bool iterateConnected(void *srpc); + + protected: +#if defined(ARDUINO_ARCH_AVR) + EthernetClient pvClient; +#else + WiFiClient pvClient; +#endif + IPAddress ip; + int port; + char buf[80]; + _supla_int64_t totalGeneratedEnergy; + _supla_int_t currentPower; + _supla_int16_t currentCurrent; + _supla_int16_t currentFreq; + _supla_int16_t currentVoltage; + int bytesCounter; + int retryCounter; + int valueToFetch; + int deviceId; + bool startCharFound; + bool dataIsReady; + bool dataFetchInProgress; +}; +}; // namespace PV +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/BME280.h b/lib/SuplaDevice/src/supla/sensor/BME280.h new file mode 100644 index 00000000..1b8cedb7 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/BME280.h @@ -0,0 +1,104 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _bme280_h +#define _bme280_h + +// Dependency: Adafruid BME280 library - use library manager to install it +#include + +#include "therm_hygro_press_meter.h" + +namespace Supla { +namespace Sensor { +class BME280 : public ThermHygroPressMeter { + public: + BME280(int8_t address = 0x77, float altitude = NAN) : address(address), sensorStatus(false), altitude(altitude) { + } + + double getTemp() { + float value = TEMPERATURE_NOT_AVAILABLE; + bool retryDone = false; + do { + if (!sensorStatus || isnan(value)) { + sensorStatus = bme.begin(address); + retryDone = true; + } + value = TEMPERATURE_NOT_AVAILABLE; + if (sensorStatus) { + value = bme.readTemperature(); + } + } while (isnan(value) && !retryDone); + return value; + } + + double getHumi() { + float value = HUMIDITY_NOT_AVAILABLE; + bool retryDone = false; + do { + if (!sensorStatus || isnan(value)) { + sensorStatus = bme.begin(address); + retryDone = true; + } + value = HUMIDITY_NOT_AVAILABLE; + if (sensorStatus) { + value = bme.readHumidity(); + } + } while (isnan(value) && !retryDone); + return value; + } + + double getPressure() { + float value = PRESSURE_NOT_AVAILABLE; + bool retryDone = false; + do { + if (!sensorStatus || isnan(value)) { + sensorStatus = bme.begin(address); + retryDone = true; + } + value = PRESSURE_NOT_AVAILABLE; + if (sensorStatus) { + value = bme.readPressure() / 100.0; + } + } while (isnan(value) && !retryDone); + if (!isnan(altitude)) { + value = bme.seaLevelForAltitude(altitude, value); + } + return value; + } + + void onInit() { + sensorStatus = bme.begin(address); + + pressureChannel.setNewValue(getPressure()); + channel.setNewValue(getTemp(), getHumi()); + } + + void setAltitude(float newAltitude) { + altitude = newAltitude; + } + + protected: + int8_t address; + bool sensorStatus; + float altitude; + Adafruit_BME280 bme; // I2C +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/DHT.h b/lib/SuplaDevice/src/supla/sensor/DHT.h new file mode 100644 index 00000000..65697bc7 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/DHT.h @@ -0,0 +1,104 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _dht_h +#define _dht_h + +#include + +#include "therm_hygro_meter.h" + +namespace Supla { +namespace Sensor { +class DHT: public ThermHygroMeter { + public: + DHT(int pin, int dhtType) : dht(pin, dhtType) { + dht.begin(); + delay(100); + retryCountTemp = 0; + retryCountHumi = 0; + lastValidTemp = TEMPERATURE_NOT_AVAILABLE; + lastValidHumi = HUMIDITY_NOT_AVAILABLE; + } + + double getTemp() { + double value = TEMPERATURE_NOT_AVAILABLE; + value = dht.readTemperature(); + if (isnan(value)) { + value = TEMPERATURE_NOT_AVAILABLE; + } + + if (value == TEMPERATURE_NOT_AVAILABLE) { + retryCountTemp++; + if (retryCountTemp > 3) { + retryCountTemp = 0; + } else { + value = lastValidTemp; + } + } else { + retryCountTemp = 0; + } + lastValidTemp = value; + + return value; + } + + double getHumi() { + double value = HUMIDITY_NOT_AVAILABLE; + value = dht.readHumidity(); + if (isnan(value)) { + value = HUMIDITY_NOT_AVAILABLE; + } + + if (value == HUMIDITY_NOT_AVAILABLE) { + retryCountHumi++; + if (retryCountHumi > 3) { + retryCountHumi = 0; + } else { + value = lastValidHumi; + } + } else { + retryCountHumi = 0; + } + lastValidHumi = value; + + return value; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getTemp(), getHumi()); + } + } + + void onInit() { + channel.setNewValue(getTemp(), getHumi()); + } + + protected: + ::DHT dht; + double lastValidTemp; + double lastValidHumi; + int8_t retryCountTemp; + int8_t retryCountHumi; + +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/DS18B20.h b/lib/SuplaDevice/src/supla/sensor/DS18B20.h new file mode 100644 index 00000000..5a547942 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/DS18B20.h @@ -0,0 +1,198 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _ds18b20_h +#define _ds18b20_h + +#include +#include +#include + +#include "supla-common/log.h" +#include "supla/sensor/thermometer.h" + +namespace Supla { +namespace Sensor { + +class OneWireBus { + public: + OneWireBus(uint8_t pinNumber) + : oneWire(pinNumber), pin(pinNumber), nextBus(nullptr), lastReadTime(0) { + supla_log(LOG_DEBUG, "Initializing OneWire bus at pin %d", pinNumber); + sensors.setOneWire(&oneWire); + sensors.begin(); + if (sensors.isParasitePowerMode()) { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is ON", pinNumber); + } else { + supla_log(LOG_DEBUG, "OneWire(pin %d) Parasite power is OFF", pinNumber); + } + + supla_log(LOG_DEBUG, + "OneWire(pin %d) Found %d devices:", + pinNumber, + sensors.getDeviceCount()); + + // report parasite power requirements + + DeviceAddress address; + char strAddr[64]; + for (int i = 0; i < sensors.getDeviceCount(); i++) { + if (!sensors.getAddress(address, i)) { + supla_log(LOG_DEBUG, "Unable to find address for Device %d", i); + } else { + sprintf( + strAddr, + "{0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X}", + address[0], + address[1], + address[2], + address[3], + address[4], + address[5], + address[6], + address[7]); + supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr); + sensors.setResolution(address, 12); + } + delay(0); + } + sensors.setWaitForConversion(true); + sensors.requestTemperatures(); + sensors.setWaitForConversion(false); + } + + int8_t getIndex(uint8_t *deviceAddress) { + DeviceAddress address; + for (int i = 0; i < sensors.getDeviceCount(); i++) { + if (sensors.getAddress(address, i)) { + bool found = true; + for (int j = 0; j < 8; j++) { + if (deviceAddress[j] != address[j]) { + found = false; + } + } + if (found) { + return i; + } + } + } + return -1; + } + + uint8_t pin; + OneWireBus *nextBus; + unsigned long lastReadTime; + DallasTemperature sensors; + + protected: + OneWire oneWire; +}; + +class DS18B20 : public Thermometer { + public: + DS18B20(uint8_t pin, uint8_t *deviceAddress = nullptr) { + OneWireBus *bus = oneWireBus; + OneWireBus *prevBus = nullptr; + address[0] = 0; + lastValidValue = TEMPERATURE_NOT_AVAILABLE; + retryCounter = 0; + + if (bus) { + while (bus) { + if (bus->pin == pin) { + myBus = bus; + break; + } + prevBus = bus; + bus = bus->nextBus; + } + } + + // There is no OneWire bus created yet for this pin + if (!bus) { + supla_log(LOG_DEBUG, "Creating OneWire bus for pin: %d", pin); + myBus = new OneWireBus(pin); + if (prevBus) { + prevBus->nextBus = myBus; + } else { + oneWireBus = myBus; + } + } + if (deviceAddress == nullptr) { + supla_log(LOG_DEBUG, + "Device address not provided. Using device from index 0"); + } else { + memcpy(address, deviceAddress, 8); + } + } + + void iterateAlways() { + if (myBus->lastReadTime + 10000 < millis()) { + myBus->sensors.requestTemperatures(); + myBus->lastReadTime = millis(); + } + if (myBus->lastReadTime + 5000 < millis() && (lastReadTime != myBus->lastReadTime)) { + channel.setNewValue(getValue()); + lastReadTime = myBus->lastReadTime; + } + } + + double getValue() { + double value = TEMPERATURE_NOT_AVAILABLE; + if (address[0] == 0) { + value = myBus->sensors.getTempCByIndex(0); + } else { + value = myBus->sensors.getTempC(address); + } + + if (value == DEVICE_DISCONNECTED_C || value == 85.0) { + value = TEMPERATURE_NOT_AVAILABLE; + } + + if (value == TEMPERATURE_NOT_AVAILABLE) { + retryCounter++; + if (retryCounter > 3) { + retryCounter = 0; + } else { + value = lastValidValue; + } + } else { + retryCounter = 0; + } + lastValidValue = value; + + return value; + } + + + void onInit() { + channel.setNewValue(getValue()); + } + + protected: + static OneWireBus *oneWireBus; + OneWireBus *myBus; + DeviceAddress address; + int8_t retryCounter; + double lastValidValue; +}; + +OneWireBus *DS18B20::oneWireBus = nullptr; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/HC_SR04.h b/lib/SuplaDevice/src/supla/sensor/HC_SR04.h new file mode 100644 index 00000000..6750ba73 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/HC_SR04.h @@ -0,0 +1,70 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _hc_sr04_h +#define _hc_sr04_h + +#include "supla/channel.h" +#include "supla/sensor/distance.h" + +#define DURATION_COUNT 2 + +namespace Supla { +namespace Sensor { +class HC_SR04 : public Distance { + public: + HC_SR04(int8_t trigPin, int8_t echoPin) : failCount(0), lastDuration(0) { + _trigPin = trigPin; + _echoPin = echoPin; + } + void onInit() { + pinMode(_trigPin, OUTPUT); + pinMode(_echoPin, INPUT); + digitalWrite(_trigPin, LOW); + delayMicroseconds(2); + + channel.setNewValue(getValue()); + channel.setNewValue(getValue()); + } + + virtual double getValue() { + digitalWrite(_trigPin, HIGH); + delayMicroseconds(10); + digitalWrite(_trigPin, LOW); + unsigned long duration = pulseIn(_echoPin, HIGH, 60000); + if (duration > 50) { + lastDuration = duration; + failCount = 0; + } else { + duration = lastDuration; + failCount++; + } + + return failCount <= 3 ? duration * 0.034 / 2 / 100 : DISTANCE_NOT_AVAILABLE; + } + + protected: + int8_t _trigPin; + int8_t _echoPin; + char failCount; + bool ready; + unsigned long lastDuration; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h new file mode 100644 index 00000000..e0f52438 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h @@ -0,0 +1,81 @@ +#ifndef _max6675_k_h +#define _max6675_k_h + +#if defined(ESP8266) +#include +#endif +#ifdef avr +#include +#endif + +#include +#include "supla/channel.h" +#include "supla/sensor/thermometer.h" + +namespace Supla { +namespace Sensor { +class MAX6675_K : public Thermometer { + public: + MAX6675_K(uint8_t pin_CLK, uint8_t pin_CS, uint8_t pin_DO) { + _pin_CLK = pin_CLK; + _pin_CS = pin_CS; + _pin_DO = pin_DO; + } + + double getValue() { + uint16_t value; + + digitalWrite(_pin_CS, LOW); + delay(1); + + value = spi_read(); + value <<= 8; + value |= spi_read(); + + digitalWrite(_pin_CS, HIGH); + + if (value & 0x4) { + return -275; + } + value >>= 3; + + return value * 0.25; + } + + void onInit() { + pinMode(_pin_CS, OUTPUT); + pinMode(_pin_CLK, OUTPUT); + pinMode(_pin_DO, INPUT); + + digitalWrite(_pin_CS, HIGH); + + channel.setNewValue(getValue()); + } + + byte spi_read(void) { + int i; + byte d = 0; + + for (i = 7; i >= 0; i--) { + digitalWrite(_pin_CLK, LOW); + delay(1); + if (digitalRead(_pin_DO)) { + d |= (1 << i); + } + + digitalWrite(_pin_CLK, HIGH); + delay(1); + } + + return d; + } + + protected: + int8_t _pin_CLK; + int8_t _pin_CS; + int8_t _pin_DO; +}; +}; // namespace Sensor +}; // namespace Supla + +#endif \ No newline at end of file diff --git a/lib/SuplaDevice/src/supla/sensor/PzemV2.h b/lib/SuplaDevice/src/supla/sensor/PzemV2.h new file mode 100644 index 00000000..f4728c07 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/PzemV2.h @@ -0,0 +1,88 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _PzemV2_h +#define _PzemV2_h + +#include +// dependence: Arduino communication library for Peacefair PZEM-004T Energy +// monitor https://github.com/olehs/PZEM004T +#include +#include + +#include "one_phase_electricity_meter.h" + +namespace Supla { +namespace Sensor { + +class PZEMv2 : public OnePhaseElectricityMeter { + public: + PZEMv2(int8_t pinRX, int8_t pinTX) : pzem(pinRX, pinTX), ip(192, 168, 1, 1) { + } + + PZEMv2(HardwareSerial *serial) : pzem(serial), ip(192, 168, 1, 1) { + } + + void onInit() { + pzem.setAddress(ip); + readValuesFromDevice(); + updateChannelValues(); + } + + virtual void readValuesFromDevice() { + + float current = pzem.current(ip); + // If current reading is NAN, we assume that PZEM there is no valid communication + // with PZEM. Sensor shouldn't show any data + if (current == PZEM_ERROR_VALUE) { + resetReadParameters(); + return; + } + + float powerFactor = 0; + float reactive = 0; + float voltage = pzem.voltage(ip); + float active = pzem.power(ip); + float apparent = (voltage * current); + if (apparent > active) { + reactive = sqrt(apparent * apparent - active * active); + } else { + reactive = 0; + } + if (active > apparent) { + powerFactor = 1; + } else if (apparent == 0) { + powerFactor = 0; + } else { + powerFactor = (active / apparent); + } + + setVoltage(0, voltage * 100); + setCurrent(0, current * 1000); + setPowerActive(0, active * 100000); + setFwdActEnergy(0, pzem.energy(ip) * 100); + setPowerApparent(0, apparent * 100000); + setPowerReactive(0, reactive * 10000); + setPowerFactor(0, powerFactor * 1000); + } + + PZEM004T pzem; + IPAddress ip; +}; +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/PzemV3.h b/lib/SuplaDevice/src/supla/sensor/PzemV3.h new file mode 100644 index 00000000..263e6d6f --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/PzemV3.h @@ -0,0 +1,84 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _PzemV3_h +#define _PzemV3_h + +#include +// dependence: Arduino library for the Updated PZEM-004T v3.0 Power and Energy +// meter https://github.com/mandulaj/PZEM-004T-v30 +#include +#include + +#include "one_phase_electricity_meter.h" + +namespace Supla { +namespace Sensor { + +class PZEMv3 : public OnePhaseElectricityMeter { + public: + PZEMv3(int8_t pinRX, int8_t pinTX) : pzem(pinRX, pinTX) { + } + + PZEMv3(HardwareSerial *serial) : pzem(serial) { + } + + void onInit() { + readValuesFromDevice(); + updateChannelValues(); + } + + virtual void readValuesFromDevice() { + float current = pzem.current(); + // If current reading is NAN, we assume that PZEM there is no valid communication + // with PZEM. Sensor shouldn't show any data + if (isnan(current)) { + resetReadParameters(); + return; + } + + float voltage = pzem.voltage(); + float active = pzem.power(); + float apparent = (voltage * current); + float reactive = 0; + if (apparent > active) { + reactive = sqrt(apparent * apparent - active * active); + } else { + reactive = 0; + } + + setVoltage(0, voltage * 100); + setCurrent(0, current * 1000); + setPowerActive(0, active * 100000); + setFwdActEnergy(0, pzem.energy() * 100000); + setFreq(pzem.frequency() * 100); + setPowerFactor(0, pzem.pf() * 1000); + setPowerApparent(0, apparent * 100000); + setPowerReactive(0, reactive * 10000); + } + + void resetStorage() { + pzem.resetEnergy(); + } + + protected: + PZEM004Tv30 pzem; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/SHT3x.h b/lib/SuplaDevice/src/supla/sensor/SHT3x.h new file mode 100644 index 00000000..9f25c919 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/SHT3x.h @@ -0,0 +1,78 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _sht3x_h +#define _sht3x_h + +// Dependency: Risele SHT3x library - use library manager to install it +// https://github.com/closedcube/ClosedCube_SHT31D_Arduino + +#include "ClosedCube_SHT31D.h" + +#include "therm_hygro_meter.h" + +namespace Supla { +namespace Sensor { +class SHT3x : public ThermHygroMeter { + public: + SHT3x(int8_t address = 0x44) : address(address) { + } + + double getTemp() { + float value = TEMPERATURE_NOT_AVAILABLE; + + SHT31D result = sht.readTempAndHumidity( + SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50); + + if (result.error == SHT3XD_NO_ERROR) { + value = result.t; + } else { + Serial.print("SHT [ERROR] Code #"); + Serial.println(result.error); + } + return value; + } + + double getHumi() { + float value = HUMIDITY_NOT_AVAILABLE; + + SHT31D result = sht.readTempAndHumidity( + SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50); + + if (result.error == SHT3XD_NO_ERROR) { + value = result.rh; + } else { + Serial.print("SHT [ERROR] Code #"); + Serial.println(result.error); + } + return value; + } + + void onInit() { + sht.begin(address); + + channel.setNewValue(getTemp(), getHumi()); + } + + protected: + int8_t address; + ::ClosedCube_SHT31D sht; // I2C +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/Si7021.h b/lib/SuplaDevice/src/supla/sensor/Si7021.h new file mode 100644 index 00000000..0b6b70f6 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/Si7021.h @@ -0,0 +1,88 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _si7021_h +#define _si7021_h + +// Dependency: Adafruid Si7021 library - use library manager to install it +// https://github.com/adafruit/Adafruit_Si7021 + +#include "Adafruit_Si7021.h" +#include "therm_hygro_meter.h" + +namespace Supla { +namespace Sensor { +class Si7021 : public ThermHygroMeter { + public: + Si7021() { + } + + double getTemp() { + float value = TEMPERATURE_NOT_AVAILABLE; + value = sensor.readTemperature(); + + if (isnan(value)) { + value = TEMPERATURE_NOT_AVAILABLE; + } + + return value; + } + + double getHumi() { + float value = HUMIDITY_NOT_AVAILABLE; + value = sensor.readHumidity(); + + if (isnan(value)) { + value = HUMIDITY_NOT_AVAILABLE; + } + + return value; + } + + void onInit() { + sensor = Adafruit_Si7021(); + sensor.begin(); + + Serial.print("Found model "); + switch (sensor.getModel()) { + case SI_Engineering_Samples: + Serial.print("SI engineering samples"); + break; + case SI_7013: + Serial.print("Si7013"); + break; + case SI_7020: + Serial.print("Si7020"); + break; + case SI_7021: + Serial.print("Si7021"); + break; + case SI_UNKNOWN: + default: + Serial.print("Unknown"); + } + + channel.setNewValue(getTemp(), getHumi()); + } + + protected: + ::Adafruit_Si7021 sensor; // I2C +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h new file mode 100644 index 00000000..b07ec66d --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h @@ -0,0 +1,154 @@ +#ifndef _si7021_sonoff_h +#define _si7021_sonoff_h + +#include + +namespace Supla { +namespace Sensor { +class Si7021Sonoff : public ThermHygroMeter { + public: + Si7021Sonoff(int pin) { + _pin = pin; + pinMode(_pin, INPUT); + delay(100); + retryCountTemp = 0; + retryCountHumi = 0; + lastValidTemp = TEMPERATURE_NOT_AVAILABLE; + lastValidHumi = HUMIDITY_NOT_AVAILABLE; + } + + double getTemp() { + double value = TEMPERATURE_NOT_AVAILABLE; + ReadTemp(); + value = _temp; + if (isnan(value)) { + value = TEMPERATURE_NOT_AVAILABLE; + } + + if (value == TEMPERATURE_NOT_AVAILABLE) { + retryCountTemp++; + if (retryCountTemp > 3) { + retryCountTemp = 0; + } + else { + value = lastValidTemp; + } + } + else { + retryCountTemp = 0; + } + lastValidTemp = value; + + return value; + } + + double getHumi() { + double value = HUMIDITY_NOT_AVAILABLE; + ReadTemp(); + value = _humidity; + if (isnan(value)) { + value = HUMIDITY_NOT_AVAILABLE; + } + + if (value == HUMIDITY_NOT_AVAILABLE) { + retryCountHumi++; + if (retryCountHumi > 3) { + retryCountHumi = 0; + } + else { + value = lastValidHumi; + } + } + else { + retryCountHumi = 0; + } + lastValidHumi = value; + + return value; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getTemp(), getHumi()); + } + } + + void onInit() { + channel.setNewValue(getTemp(), getHumi()); + } + + private: + bool ReadTemp() { + _temp = NAN; + _humidity = NAN; + + uint8_t d[5]; + d[0] = d[1] = d[2] = d[3] = d[4] = 0; + + pinMode(_pin, OUTPUT); + digitalWrite(_pin, LOW); + delayMicroseconds(500); + digitalWrite(_pin, HIGH); + delayMicroseconds(20); + pinMode(_pin, INPUT); + + uint32_t i = 0; + if (WaitState(0) and WaitState(1) and WaitState(0)) { + for (i = 0; i < 40; i++) { + if (!WaitState(1)) { + break; + } + delayMicroseconds(35); + if (digitalRead(_pin) == HIGH) { + d[i / 8] |= (1 << (7 - i % 8)); + } + if (!WaitState(0)) { + break; + } + } + } + + if (i < 40) { + return false; + } + + uint8_t checksum = (d[0] + d[1] + d[2] + d[3]) & 0xFF; + if (d[4] == checksum) { + _temp = (((d[2] & 0x7F) << 8) | d[3]) * 0.1; + _humidity = ((d[0] << 8) | d[1]) * 0.1; + if (d[2] & 0x80) { + _temp *= -1; + } + } + + if (isnan(_temp) || isnan(_humidity)) { + Serial.println(F("Invalid NAN reading")); + return false; + } + return true; + } + + bool WaitState(bool state) { + unsigned long timeout = micros(); + while (micros() - timeout < 100) { + if (digitalRead(_pin) == state) + return true; + delayMicroseconds(1); + } + return false; + } + + protected: + int8_t _pin; + double _temp = NAN, _humidity = NAN; + double lastValidTemp; + double lastValidHumi; + int8_t retryCountTemp; + int8_t retryCountHumi; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/binary.h b/lib/SuplaDevice/src/supla/sensor/binary.h new file mode 100644 index 00000000..8bd7e52d --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/binary.h @@ -0,0 +1,65 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _binary_h +#define _binary_h + +#include + +#include "../channel.h" +#include "../element.h" +#include "../io.h" + +namespace Supla { +namespace Sensor { +class Binary : public Element { + public: + Binary(int pin, bool pullUp = false) : pin(pin), pullUp(pullUp), lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_SENSORNO); + } + + bool getValue() { + return Supla::Io::digitalRead(channel.getChannelNumber(), pin) == LOW + ? false + : true; + } + + void iterateAlways() { + if (lastReadTime + 100 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + void onInit() { + pinMode(pin, pullUp ? INPUT_PULLUP : INPUT); + channel.setNewValue(getValue()); + } + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + int pin; + bool pullUp; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/distance.h b/lib/SuplaDevice/src/supla/sensor/distance.h new file mode 100644 index 00000000..953078a1 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/distance.h @@ -0,0 +1,57 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _distance_h +#define _distance_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define DISTANCE_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class Distance : public Element { + public: + Distance() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_DISTANCESENSOR); + channel.setDefault(SUPLA_CHANNELFNC_DISTANCESENSOR); + channel.setNewValue(DISTANCE_NOT_AVAILABLE); + } + + virtual double getValue() { + return DISTANCE_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 500 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h new file mode 100644 index 00000000..2fab6b61 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h @@ -0,0 +1,264 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _electricity_meter_h +#define _electricity_meter_h + +#include +#include + +#include "../channel_extended.h" +#include "../element.h" +#include + +#define MAX_PHASES 3 + +namespace Supla { +namespace Sensor { +class ElectricityMeter : public Element { + public: + ElectricityMeter() : valueChanged(false), lastReadTime(0) { + extChannel.setType(SUPLA_CHANNELTYPE_ELECTRICITY_METER); + extChannel.setDefault(SUPLA_CHANNELFNC_ELECTRICITY_METER); + memset(&emValue, 0, sizeof(emValue)); + emValue.period = 5; + for (int i = 0; i < MAX_PHASES; i++) { + rawCurrent[i] = 0; + } + currentMeasurementAvailable = false; + } + + virtual void updateChannelValues() { + if (!valueChanged) { + return; + } + valueChanged = false; + + emValue.m_count = 1; + + // Update current messurement precision based on last updates + if (currentMeasurementAvailable) { + bool over65A = false; + for (int i = 0; i < MAX_PHASES; i++) { + if (rawCurrent[i] > 65000) { + over65A = true; + } + } + + for (int i = 0; i < MAX_PHASES; i++) { + if (over65A) { + emValue.m[0].current[i] = rawCurrent[i] / 10; + } else { + emValue.m[0].current[i] = rawCurrent[i]; + } + } + + if (over65A) { + emValue.measured_values ^= (!EM_VAR_CURRENT); + emValue.measured_values |= EM_VAR_CURRENT_OVER_65A; + } else { + emValue.measured_values ^= (!EM_VAR_CURRENT_OVER_65A); + emValue.measured_values |= EM_VAR_CURRENT; + } + } + + // Prepare extended channel value + srpc_evtool_v2_emextended2extended(&emValue, extChannel.getExtValue()); + extChannel.setNewValue(emValue); + } + + // energy in 0.00001 kWh + void setFwdActEnergy(char phase, _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_forward_active_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_forward_active_energy[phase] = energy; + emValue.measured_values |= EM_VAR_FORWARD_ACTIVE_ENERGY; + } + } + + // energy in 0.00001 kWh + void setRvrActEnergy(char phase, _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_reverse_active_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_reverse_active_energy[phase] = energy; + emValue.measured_values |= EM_VAR_REVERSE_ACTIVE_ENERGY; + } + } + + // energy in 0.00001 kWh + void setFwdReactEnergy(char phase, _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_forward_reactive_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_forward_reactive_energy[phase] = energy; + emValue.measured_values |= EM_VAR_FORWARD_REACTIVE_ENERGY; + } + } + + // energy in 0.00001 kWh + void setRvrReactEnergy(char phase, _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_reverse_reactive_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_reverse_reactive_energy[phase] = energy; + emValue.measured_values |= EM_VAR_REVERSE_REACTIVE_ENERGY; + } + } + + // voltage in 0.01 V + void setVoltage(char phase, _supla_int16_t voltage) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].voltage[phase] != voltage) { + valueChanged = true; + } + emValue.m[0].voltage[phase] = voltage; + emValue.measured_values |= EM_VAR_VOLTAGE; + } + } + + // current in 0.001 A + void setCurrent(char phase, _supla_int_t current) { + if (phase >= 0 && phase < MAX_PHASES) { + if (rawCurrent[phase] != current) { + valueChanged = true; + } + rawCurrent[phase] = current; + currentMeasurementAvailable = true; + } + } + + // Frequency in 0.01 Hz + void setFreq(_supla_int16_t freq) { + if (emValue.m[0].freq != freq) { + valueChanged = true; + } + emValue.m[0].freq = freq; + emValue.measured_values |= EM_VAR_FREQ; + } + + // power in 0.00001 kW + void setPowerActive(char phase, _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_active[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_active[phase] = power; + emValue.measured_values |= EM_VAR_POWER_ACTIVE; + } + } + + // power in 0.00001 kvar + void setPowerReactive(char phase, _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_reactive[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_reactive[phase] = power; + emValue.measured_values |= EM_VAR_POWER_REACTIVE; + } + } + + // power in 0.00001 kVA + void setPowerApparent(char phase, _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_apparent[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_apparent[phase] = power; + emValue.measured_values |= EM_VAR_POWER_APPARENT; + } + } + + // power in 0.001 + void setPowerFactor(char phase, _supla_int_t powerFactor) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_factor[phase] != powerFactor) { + valueChanged = true; + } + emValue.m[0].power_factor[phase] = powerFactor; + emValue.measured_values |= EM_VAR_POWER_FACTOR; + } + } + + // phase angle in 0.1 degree + void setPhaseAngle(char phase, _supla_int_t phaseAngle) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].phase_angle[phase] != phaseAngle) { + valueChanged = true; + } + emValue.m[0].phase_angle[phase] = phaseAngle; + emValue.measured_values |= EM_VAR_PHASE_ANGLE; + } + } + + void resetReadParameters() { + if (emValue.measured_values != 0) { + emValue.measured_values = 0; + memset(&emValue.m[0], 0, sizeof(TElectricityMeter_Measurement)); + valueChanged = true; + } + } + + // Please implement this class for reading value from elecricity meter device. + // It will be called every 5 s. Use set methods defined above in order to + // set values on channel. Don't use any other method to modify channel values. + virtual void readValuesFromDevice() { + } + + // Put here initialization code for electricity meter device. + // It will be called within SuplaDevce.begin method. + // It should also read first data set, so at the end it should call those two + // methods: + // readValuesFromDevice(); + // updateChannelValues(); + void onInit() { + } + + void iterateAlways() { + if (lastReadTime + 5000 < millis()) { + lastReadTime = millis(); + readValuesFromDevice(); + updateChannelValues(); + } + } + + // Implement this method to reset stored energy value (i.e. to set energy + // counter back to 0 kWh + virtual void resetStorage() { + } + + protected: + Channel *getChannel() { + return &extChannel; + } + TElectricityMeter_ExtendedValue_V2 emValue; + ChannelExtended extChannel; + _supla_int_t rawCurrent[MAX_PHASES]; + bool valueChanged; + bool currentMeasurementAvailable; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/esp_free_heap.h b/lib/SuplaDevice/src/supla/sensor/esp_free_heap.h new file mode 100644 index 00000000..1e42910d --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/esp_free_heap.h @@ -0,0 +1,40 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _esp_free_heap_h +#define _esp_free_heap_h + +#include "supla/sensor/thermometer.h" + +namespace Supla { +namespace Sensor { +class EspFreeHeap : public Thermometer { + public: + void onInit() { + channel.setNewValue(getValue()); + } + + double getValue() { + return ESP.getFreeHeap() / 1024.0; + } + + protected: +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h b/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h new file mode 100644 index 00000000..6c494824 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h @@ -0,0 +1,51 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _general_purpose_measurement_h +#define _general_purpose_measurement_h + +#include "supla/element.h" +#include "supla/channel.h" + +namespace Supla { +namespace Sensor { +class GeneralPurposeMeasurementBase : public Element { + public: + GeneralPurposeMeasurementBase() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_GENERAL_PURPOSE_MEASUREMENT); + } + + virtual double getValue() = 0; + + void iterateAlways() { + if (lastReadTime + 1000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp new file mode 100644 index 00000000..3ba3e912 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include +#include + +#include "impulse_counter.h" + +using namespace Supla::Sensor; + +ImpulseCounter::ImpulseCounter(int _impulsePin, + bool _detectLowToHigh, + bool _inputPullup, + unsigned int _debounceDelay) + : impulsePin(_impulsePin), + debounceDelay(_debounceDelay), + detectLowToHigh(_detectLowToHigh), + lastImpulseMillis(0), + counter(0), + inputPullup(_inputPullup) { + channel.setType(SUPLA_CHANNELTYPE_IMPULSE_COUNTER); + + prevState = (detectLowToHigh == true ? LOW : HIGH); + + supla_log(LOG_DEBUG, + "Creating Impulse Counter: impulsePin(%d), " + "delay(%d ms)", + impulsePin, + debounceDelay); + if (impulsePin <= 0) { + supla_log(LOG_DEBUG, + "SuplaImpulseCounter ERROR - incorrect impulse pin number"); + return; + } +} + +void ImpulseCounter::onInit() { + if (inputPullup) { + pinMode(impulsePin, INPUT_PULLUP); + } else { + pinMode(impulsePin, INPUT); + } +} + +_supla_int64_t ImpulseCounter::getCounter() { + return counter; +} + +void ImpulseCounter::onSaveState() { + Supla::Storage::WriteState((unsigned char *)&counter, sizeof(counter)); +} + +void ImpulseCounter::onLoadState() { + _supla_int64_t data; + if (Supla::Storage::ReadState((unsigned char *)&data, sizeof(data))) { + setCounter(data); + } +} + +void ImpulseCounter::setCounter(_supla_int64_t value) { + counter = value; + channel.setNewValue(value); + supla_log(LOG_DEBUG, + "ImpulseCounter[%d] - set counter to %d", + channel.getChannelNumber(), + static_cast(counter)); +} + +void ImpulseCounter::incCounter() { + counter++; + channel.setNewValue(getCounter()); +} + +void ImpulseCounter::onFastTimer() { + int currentState = digitalRead(impulsePin); + if (prevState == (detectLowToHigh == true ? LOW : HIGH)) { + if (millis() - lastImpulseMillis > debounceDelay) { + if (currentState == (detectLowToHigh == true ? HIGH : LOW)) { + incCounter(); + lastImpulseMillis = millis(); + } + } + } + prevState = currentState; +} + +Supla::Channel *ImpulseCounter::getChannel() { + return &channel; +} + +void ImpulseCounter::runAction(int trigger, int action) { + switch (action) { + case RESET: { + setCounter(0); + break; + } + } +} diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.h b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h new file mode 100644 index 00000000..88da39b3 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h @@ -0,0 +1,71 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_impulse_counter_h_ +#define _supla_impulse_counter_h_ + +#include +#include +#include +#include + +namespace Supla { +namespace Sensor { +class ImpulseCounter : public Element, public Triggerable { + public: + ImpulseCounter(int _impulsePin, + bool _detectLowToHigh = false, + bool inputPullup = true, + unsigned int _debounceDelay = 10); + + void onInit(); + void onLoadState(); + void onSaveState(); + void onFastTimer(); + void runAction(int trigger, int action); + + // Returns value of a counter at given Supla channel + _supla_int64_t getCounter(); + + // Set counter to a given value + void setCounter(_supla_int64_t value); + + // Increment the counter by 1 + void incCounter(); + + protected: + int prevState; // Store previous state of pin (LOW/HIGH). It is used to track + // changes on pin state. + int impulsePin; // Pin where impulses are counted + + unsigned long + lastImpulseMillis; // Stores timestamp of last impulse (used to ignore + // changes of state during 10 ms timeframe) + unsigned int debounceDelay; + bool detectLowToHigh; // defines if we count raining (LOW to HIGH) or falling + // (HIGH to LOW) edge + bool inputPullup; + + _supla_int64_t counter; // Actual count of impulses + + Channel *getChannel(); + Channel channel; + +}; +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/normally_open.h b/lib/SuplaDevice/src/supla/sensor/normally_open.h new file mode 100644 index 00000000..bfa605ac --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/normally_open.h @@ -0,0 +1,33 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _normally_open_h +#define _normally_open_h + +#include "binary.h" + +namespace Supla { +namespace Sensor { +class NormallyOpen : public Binary { + public: + NormallyOpen(int pin, bool pullUp = false) : Binary(pin, pullUp) { + } +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/one_phase_electricity_meter.h b/lib/SuplaDevice/src/supla/sensor/one_phase_electricity_meter.h new file mode 100644 index 00000000..3d271582 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/one_phase_electricity_meter.h @@ -0,0 +1,42 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _one_phase_electricity_meter_h +#define _one_phase_electricity_meter_h + +#include "electricity_meter.h" + +namespace Supla { +namespace Sensor { +class OnePhaseElectricityMeter : public ElectricityMeter { + public: + OnePhaseElectricityMeter() { + extChannel.setFlag(SUPLA_CHANNEL_FLAG_PHASE2_UNSUPPORTED); + extChannel.setFlag(SUPLA_CHANNEL_FLAG_PHASE3_UNSUPPORTED); + } + + virtual void readValuesFromDevice() { + } + + void onInit() { + } + +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/pressure.h b/lib/SuplaDevice/src/supla/sensor/pressure.h new file mode 100644 index 00000000..d320fb33 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/pressure.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _pressure_h +#define _pressure_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define PRESSURE_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class Pressure : public Element { + public: + Pressure() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_PRESSURESENSOR); + channel.setDefault(SUPLA_CHANNELFNC_PRESSURESENSOR); + channel.setNewValue(PRESSURE_NOT_AVAILABLE); + } + + virtual double getValue() { + return PRESSURE_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/rain.h b/lib/SuplaDevice/src/supla/sensor/rain.h new file mode 100644 index 00000000..dd060d27 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/rain.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _rain_h +#define _rain_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define RAIN_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class Rain: public Element { + public: + Rain() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_RAINSENSOR); + channel.setDefault(SUPLA_CHANNELFNC_RAINSENSOR); + channel.setNewValue(RAIN_NOT_AVAILABLE); + } + + virtual double getValue() { + return RAIN_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h new file mode 100644 index 00000000..3e9f019a --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h @@ -0,0 +1,54 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _therm_hygro_meter_h +#define _therm_hygro_meter_h + +#include "thermometer.h" + +#define HUMIDITY_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class ThermHygroMeter : public Thermometer { + public: + ThermHygroMeter() { + channel.setType(SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR); + channel.setDefault(SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE); + } + + virtual double getTemp() { + return TEMPERATURE_NOT_AVAILABLE; + } + + virtual double getHumi() { + return HUMIDITY_NOT_AVAILABLE; + } + + void iterateAlways() { + if (millis() - lastReadTime > 10000) { + lastReadTime = millis(); + channel.setNewValue(getTemp(), getHumi()); + } + } + + protected: +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h new file mode 100644 index 00000000..55441650 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h @@ -0,0 +1,71 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _therm_hygro_press_meter_h +#define _therm_hygro_press_meter_h + +#include "therm_hygro_meter.h" + +#define PRESSURE_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class ThermHygroPressMeter : public ThermHygroMeter { + public: + ThermHygroPressMeter() { + pressureChannel.setType(SUPLA_CHANNELTYPE_PRESSURESENSOR); + pressureChannel.setDefault(SUPLA_CHANNELFNC_PRESSURESENSOR); + } + + virtual double getPressure() { + return PRESSURE_NOT_AVAILABLE; + } + + void iterateAlways() { + if (millis() - lastReadTime > 10000) { + pressureChannel.setNewValue(getPressure()); + } + ThermHygroMeter::iterateAlways(); + } + + bool iterateConnected(void *srpc) { + bool response = true; + if (pressureChannel.isUpdateReady() && + millis() - pressureChannel.lastCommunicationTimeMs > 100) { + pressureChannel.lastCommunicationTimeMs = millis(); + pressureChannel.sendUpdate(srpc); + response = false; + } + + if (!Element::iterateConnected(srpc)) { + response = false; + } + return response; + } + + Element &disableChannelState() { + pressureChannel.unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); + return ThermHygroMeter::disableChannelState(); + } + + protected: + Channel pressureChannel; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/thermometer.h b/lib/SuplaDevice/src/supla/sensor/thermometer.h new file mode 100644 index 00000000..8dd11942 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/thermometer.h @@ -0,0 +1,56 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _thermometer_h +#define _thermometer_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define TEMPERATURE_NOT_AVAILABLE -275 + +namespace Supla { +namespace Sensor { +class Thermometer : public Element { + public: + Thermometer() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_THERMOMETER); + channel.setDefault(SUPLA_CHANNELFNC_THERMOMETER); + } + + virtual double getValue() { + return TEMPERATURE_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/three_phase_PzemV3.h b/lib/SuplaDevice/src/supla/sensor/three_phase_PzemV3.h new file mode 100644 index 00000000..52fb5eeb --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/three_phase_PzemV3.h @@ -0,0 +1,108 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _three_phase_PzemV3_h +#define _three_phase_PzemV3_h + +#include +// dependence: Arduino library for the Updated PZEM-004T v3.0 Power and Energy +// meter https://github.com/mandulaj/PZEM-004T-v30 +#include +#include + +#include "electricity_meter.h" + +namespace Supla { +namespace Sensor { + +class ThreePhasePZEMv3 : public ElectricityMeter { + public: + ThreePhasePZEMv3(int8_t pinRX1, + int8_t pinTX1, + int8_t pinRX2, + int8_t pinTX2, + int8_t pinRX3, + int8_t pinTX3) + : pzem{PZEM004Tv30(pinRX1, pinTX1), + PZEM004Tv30(pinRX2, pinTX2), + PZEM004Tv30(pinRX3, pinTX3)} { + } + + ThreePhasePZEMv3(HardwareSerial *serial1, + HardwareSerial *serial2, + HardwareSerial *serial3) + : pzem{PZEM004Tv30(serial1), + PZEM004Tv30(serial2), + PZEM004Tv30(serial3)} { + } + + void onInit() { + readValuesFromDevice(); + updateChannelValues(); + } + + virtual void readValuesFromDevice() { + bool atLeatOnePzemWasRead = false; + for (int i = 0; i < 3; i++) { + float current = pzem[i].current(); + // If current reading is NAN, we assume that PZEM there is no valid + // communication with PZEM. Sensor shouldn't show any data + if (isnan(current)) { + continue; + } + + atLeatOnePzemWasRead = true; + + float voltage = pzem[i].voltage(); + float active = pzem[i].power(); + float apparent = (voltage * current); + float reactive = 0; + if (apparent > active) { + reactive = sqrt(apparent * apparent - active * active); + } else { + reactive = 0; + } + + setVoltage(i, voltage * 100); + setCurrent(i, current * 1000); + setPowerActive(i, active * 100000); + setFwdActEnergy(i, pzem[i].energy() * 100000); + setPowerFactor(i, pzem[i].pf() * 1000); + setPowerApparent(i, apparent * 100000); + setPowerReactive(i, reactive * 10000); + + setFreq(pzem[i].frequency() * 100); + } + + if (!atLeatOnePzemWasRead) { + resetReadParameters(); + } + } + + void resetStorage() { + for (int i = 0; i < 3; i++) { + pzem[i].resetEnergy(); + } + } + + protected: + PZEM004Tv30 pzem[3]; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp b/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp new file mode 100644 index 00000000..8c383645 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp @@ -0,0 +1,63 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "virtual_binary.h" + +namespace Supla { +namespace Sensor { + +VirtualBinary::VirtualBinary() : state(false), lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_SENSORNO); +} + +bool VirtualBinary::getValue() { + return state; +} + +void VirtualBinary::iterateAlways() { + if (millis() - lastReadTime > 100) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } +} + +void VirtualBinary::onInit() { + channel.setNewValue(getValue()); +} + +void VirtualBinary::runAction(int trigger, int action) { + switch (action) { + case SET: { + state = true; + break; + } + case CLEAR: { + state = false; + break; + } + case TOGGLE: { + state = !state; + break; + } + } +} + +Channel *VirtualBinary::getChannel() { + return &channel; +} + +}; // namespace Sensor +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/sensor/virtual_binary.h b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h new file mode 100644 index 00000000..84e04600 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h @@ -0,0 +1,47 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _virtual_binary_h +#define _virtual_binary_h + +#include + +#include "../channel.h" +#include "../element.h" +#include "../triggerable.h" +#include "../actions.h" + +namespace Supla { +namespace Sensor { +class VirtualBinary : public Element, public Triggerable { + public: + VirtualBinary(); + bool getValue(); + void iterateAlways(); + void onInit(); + void runAction(int trigger, int action); + + protected: + Channel *getChannel(); + Channel channel; + bool state; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/weight.h b/lib/SuplaDevice/src/supla/sensor/weight.h new file mode 100644 index 00000000..8c318cbd --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/weight.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _weight_h +#define _weight_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define WEIGHT_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class Weight : public Element { + public: + Weight() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_WEIGHTSENSOR); + channel.setDefault(SUPLA_CHANNELFNC_WEIGHTSENSOR); + channel.setNewValue(WEIGHT_NOT_AVAILABLE); + } + + virtual double getValue() { + return WEIGHT_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/sensor/wind.h b/lib/SuplaDevice/src/supla/sensor/wind.h new file mode 100644 index 00000000..6869bc83 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/wind.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _wind_h +#define _wind_h + +#include "supla/channel.h" +#include "supla/element.h" + +#define WIND_NOT_AVAILABLE -1 + +namespace Supla { +namespace Sensor { +class Wind: public Element { + public: + Wind() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_WINDSENSOR); + channel.setDefault(SUPLA_CHANNELFNC_WINDSENSOR); + channel.setNewValue(WIND_NOT_AVAILABLE); + } + + virtual double getValue() { + return WIND_NOT_AVAILABLE; + } + + void iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } + } + + + protected: + Channel *getChannel() { + return &channel; + } + Channel channel; + unsigned long lastReadTime; +}; + +}; // namespace Sensor +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/status.cpp b/lib/SuplaDevice/src/supla/status.cpp new file mode 100644 index 00000000..03550de5 --- /dev/null +++ b/lib/SuplaDevice/src/supla/status.cpp @@ -0,0 +1,15 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ diff --git a/lib/SuplaDevice/src/supla/status.h b/lib/SuplaDevice/src/supla/status.h new file mode 100644 index 00000000..4cbec2b9 --- /dev/null +++ b/lib/SuplaDevice/src/supla/status.h @@ -0,0 +1,57 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_status_h +#define _supla_status_h + +namespace Supla { +class Status { + public: + void notInitialized(); + void initialized(); + + void alreadyInitialized(); + void channelLimitExceeded(); + void missingNetworkInterface(); + void invalidGuid(); + void missingCredentials(); + void unknownServerAddress(); + + void connectingToNetworkInterface(); + void connectingToSuplaServer(); + void registerInProgress(); + void registeredAndReady(); + + void networkDisconnected(); + void serverDisconnected(); + + + void iterateFail(); + void protocolVersionError(); + void badCredentials(); + void temporarilyUnavailable(); + void locationConflict(); + void channelConflict(); + void deviceIsDisabled(); + void locationIsDisabled(); + void deviceLimitExceeded(); + void registrationDisabled(); + + +}; +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/storage/eeprom.cpp b/lib/SuplaDevice/src/supla/storage/eeprom.cpp new file mode 100644 index 00000000..c48e81a3 --- /dev/null +++ b/lib/SuplaDevice/src/supla/storage/eeprom.cpp @@ -0,0 +1,83 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +#include "eeprom.h" + +using namespace Supla; + +// By default, write to EEPROM every 3 min +#define SUPLA_EEPROM_WRITING_PERIOD 3*60*1000 + +Eeprom::Eeprom(unsigned int storageStartingOffset) + : Storage(storageStartingOffset), + dataChanged(false) { + setStateSavePeriod(SUPLA_EEPROM_WRITING_PERIOD); +} + +bool Eeprom::init() { +#if defined(ARDUINO_ARCH_ESP8266) + EEPROM.begin(1024); +#elif defined(ARDUINO_ARCH_ESP32) + EEPROM.begin(512); +#endif + delay(15); + + return Storage::init(); +} + +int Eeprom::readStorage(int offset, unsigned char *buf, int size, bool logs) { + if (logs) { + Serial.print(F("readStorage: ")); + Serial.print(size); + Serial.print(F("; Read: [")); + } + for (int i = 0; i < size; i++) { + buf[i] = EEPROM.read(offset + i); + if (logs) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + } + if (logs) { + Serial.println(F("]")); + } + return size; +} + +int Eeprom::writeStorage(int offset, const unsigned char *buf, int size) { + dataChanged = true; + for (int i = 0; i < size; i++) { + EEPROM.write(offset + i, buf[i]); + } + Serial.print(F("Wrote ")); + Serial.print(size); + Serial.print(F(" bytes to storage at ")); + Serial.println(offset); + return size; +} + +void Eeprom::commit() { +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + if (dataChanged) { + EEPROM.commit(); + Serial.println(F("Commit")); + } +#endif + dataChanged = false; +} diff --git a/lib/SuplaDevice/src/supla/storage/eeprom.h b/lib/SuplaDevice/src/supla/storage/eeprom.h new file mode 100644 index 00000000..d7e028a7 --- /dev/null +++ b/lib/SuplaDevice/src/supla/storage/eeprom.h @@ -0,0 +1,39 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_eeprom_h +#define _supla_eeprom_h + +#include "storage.h" + +namespace Supla { + +class Eeprom : public Storage { + public: + Eeprom(unsigned int storageStartingOffset = 0); + bool init(); + void commit(); + + protected: + int readStorage(int, unsigned char *, int, bool); + int writeStorage(int, const unsigned char *, int); + + bool dataChanged; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/storage/fram_spi.h b/lib/SuplaDevice/src/supla/storage/fram_spi.h new file mode 100644 index 00000000..5f479202 --- /dev/null +++ b/lib/SuplaDevice/src/supla/storage/fram_spi.h @@ -0,0 +1,99 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * This extension depends on Adafruit FRAM SPI library + * Please install it from librarary manager in Arduino + */ + +#ifndef _supla_fram_spi_h +#define _supla_fram_spi_h + +#include + +#include "Adafruit_FRAM_SPI.h" +#include "storage.h" + +#define SUPLA_FRAM_WRITING_PERIOD 1000 + +namespace Supla { + +class FramSpi : public Storage { + public: + FramSpi(int8_t clk, + int8_t miso, + int8_t mosi, + int8_t framCs, + unsigned int storageStartingOffset = 0) + : Storage(storageStartingOffset), + fram(clk, miso, mosi, framCs) { + setStateSavePeriod(SUPLA_FRAM_WRITING_PERIOD); + } + + FramSpi(int8_t framCs, unsigned int storageStartingOffset = 0) + : Storage(storageStartingOffset), fram(framCs) { + setStateSavePeriod(SUPLA_FRAM_WRITING_PERIOD); + } + + bool init() { + if (fram.begin()) { + Serial.println(F("Storage: FRAM found")); + } else { + Serial.println(F("Storage: FRAM not found")); + } + + return Storage::init(); + } + + void commit(){}; + + protected: + int readStorage(int offset, unsigned char *buf, int size, bool logs) { + if (logs) { + Serial.print(F("readStorage: ")); + Serial.print(size); + Serial.print(F("; Read: [")); + } + for (int i = 0; i < size; i++) { + buf[i] = fram.read8(offset + i); + if (logs) { + Serial.print(static_cast(buf)[i], HEX); + Serial.print(F(" ")); + } + } + if (logs) { + Serial.println(F("]")); + } + return size; + } + + int writeStorage(int offset, const unsigned char *buf, int size) { + fram.writeEnable(true); + fram.write(offset, const_cast(buf), size); + fram.writeEnable(false); + Serial.print(F("Wrote ")); + Serial.print(size); + Serial.print(F(" bytes to storage at ")); + Serial.println(offset); + return size; + } + + Adafruit_FRAM_SPI fram; +}; + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/storage/storage.cpp b/lib/SuplaDevice/src/supla/storage/storage.cpp new file mode 100644 index 00000000..00f1022f --- /dev/null +++ b/lib/SuplaDevice/src/supla/storage/storage.cpp @@ -0,0 +1,318 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include "storage.h" + +#define SUPLA_STORAGE_VERSION 1 + +using namespace Supla; + +Storage *Storage::instance = nullptr; + +Storage *Storage::Instance() { + return instance; +} + +bool Storage::Init() { + if (Instance()) { + return Instance()->init(); + } + return false; +} + +bool Storage::ReadState(unsigned char *buf, int size) { + if (Instance()) { + return Instance()->readState(buf, size); + } + return false; +} + +bool Storage::WriteState(const unsigned char *buf, int size) { + if (Instance()) { + return Instance()->writeState(buf, size); + } + return false; +} + +bool Storage::LoadDeviceConfig() { + if (Instance()) { + return Instance()->loadDeviceConfig(); + } + return false; +} + +bool Storage::LoadElementConfig() { + if (Instance()) { + return Instance()->loadElementConfig(); + } + return false; +} + +void Storage::PrepareState(bool dryRun) { + if (Instance()) { + Instance()->prepareState(dryRun); + } +} + +bool Storage::FinalizeSaveState() { + if (Instance()) { + return Instance()->finalizeSaveState(); + } + return false; +} + +bool Storage::SaveStateAllowed(unsigned long ms) { + if (Instance()) { + return Instance()->saveStateAllowed(ms); + } + return false; +} + +Storage::Storage(unsigned int storageStartingOffset) + : storageStartingOffset(storageStartingOffset), + currentStateOffset(0), + deviceConfigOffset(0), + elementConfigOffset(0), + elementStateOffset(0), + newSectionSize(0), + sectionsCount(0), + dryRun(false), + lastWriteTimestamp(0), + saveStatePeriod(1000) { + instance = this; +} + +void Storage::prepareState(bool performDryRun) { + dryRun = performDryRun; + newSectionSize = 0; + currentStateOffset = elementStateOffset + sizeof(SectionPreamble); +} + +bool Storage::readState(unsigned char *buf, int size) { + if (elementStateOffset + sizeof(SectionPreamble) + elementStateSize < + currentStateOffset + size) { + Serial.println(F("Warning! Attempt to read state outside of section size")); + return false; + } + currentStateOffset += readStorage(currentStateOffset, buf, size); + return true; +} + +bool Storage::writeState(const unsigned char *buf, int size) { + newSectionSize += size; + + if (size == 0) { + return true; + } + + if (elementStateSize > 0 && + elementStateOffset + sizeof(SectionPreamble) + elementStateSize < + currentStateOffset + size) { + Serial.println( + F("Warning! Attempt to write state outside of section size.")); + Serial.println( + F("Storage: rewriting element state section. All data will be lost.")); + elementStateSize = 0; + elementStateOffset = 0; + return false; + } + + if (dryRun) { + currentStateOffset += size; + return true; + } + + // Calculation of offset for section data - in case sector is missing + if (elementStateOffset == 0) { + Serial.print(F("Initialization of elementStateOffset: ")); + elementStateOffset = storageStartingOffset + sizeof(Preamble); + if (deviceConfigOffset != 0) { + elementStateOffset += sizeof(SectionPreamble) + deviceConfigSize; + } + if (elementConfigOffset != 0) { + elementStateOffset += sizeof(SectionPreamble) + elementConfigSize; + } + Serial.println(elementStateOffset); + + currentStateOffset = elementStateOffset + sizeof(SectionPreamble); + + sectionsCount++; + + // Update Storage preamble with new section count + Serial.println(F("Update Storage preamble")); + unsigned char suplaTag[] = {'S', 'U', 'P', 'L', 'A'}; + Preamble preamble; + memcpy(preamble.suplaTag, suplaTag, 5); + preamble.version = SUPLA_STORAGE_VERSION; + preamble.sectionsCount = sectionsCount; + + updateStorage( + storageStartingOffset, (unsigned char *)&preamble, sizeof(preamble)); + } + + currentStateOffset += updateStorage(currentStateOffset, buf, size); + + return true; +} + +bool Storage::finalizeSaveState() { + if (dryRun) { + dryRun = false; + if (elementStateSize != newSectionSize) { + Serial.println( + F("Element state section size doesn't match current device " + "configuration")); + elementStateOffset = 0; + elementStateSize = 0; + return false; + } + return true; + } + + SectionPreamble preamble; + preamble.type = STORAGE_SECTION_TYPE_ELEMENT_STATE; + preamble.size = newSectionSize; + preamble.crc1 = 0; + preamble.crc2 = 0; + // TODO add crc calculation + + updateStorage( + elementStateOffset, (unsigned char *)&preamble, sizeof(preamble)); + + commit(); + return true; +} + +bool Storage::init() { + Serial.println(F("Storage initialization")); + int currentOffset = storageStartingOffset; + Preamble preamble; + currentOffset += + readStorage(currentOffset, (unsigned char *)&preamble, sizeof(preamble)); + + unsigned char suplaTag[] = {'S', 'U', 'P', 'L', 'A'}; + + if (memcmp(suplaTag, preamble.suplaTag, 5)) { + Serial.println(F("Storage: missing Supla tag. Rewriting...")); + + memcpy(preamble.suplaTag, suplaTag, 5); + preamble.version = SUPLA_STORAGE_VERSION; + preamble.sectionsCount = 0; + + writeStorage( + storageStartingOffset, (unsigned char *)&preamble, sizeof(preamble)); + commit(); + + } else if (preamble.version != SUPLA_STORAGE_VERSION) { + Serial.print(F("Storage: storage version [")); + Serial.print(preamble.version); + Serial.println(F("] is not supported. Storage not initialized")); + return false; + } else { + Serial.print(F("Storage: Number of sections ")); + Serial.println(preamble.sectionsCount); + } + + if (preamble.sectionsCount == 0) { + return true; + } + + for (int i = 0; i < preamble.sectionsCount; i++) { + Serial.print(F("Reading section: ")); + Serial.println(i); + SectionPreamble section; + int sectionOffset = currentOffset; + currentOffset += + readStorage(currentOffset, (unsigned char *)§ion, sizeof(section)); + + Serial.print(F("Section type: ")); + Serial.print(static_cast(section.type)); + Serial.print(F("; size: ")); + Serial.println(section.size); + + if (section.crc1 != section.crc2) { + Serial.println( + F("Warning! CRC copies on section doesn't match. Please check your " + "storage hardware")); + } + + switch (section.type) { + case STORAGE_SECTION_TYPE_DEVICE_CONFIG: { + deviceConfigOffset = sectionOffset; + deviceConfigSize = section.size; + break; + } + case STORAGE_SECTION_TYPE_ELEMENT_CONFIG: { + elementConfigOffset = sectionOffset; + elementConfigSize = section.size; + break; + } + case STORAGE_SECTION_TYPE_ELEMENT_STATE: { + elementStateOffset = sectionOffset; + elementStateSize = section.size; + break; + } + default: { + Serial.println(F("Warning! Unknown section type")); + break; + } + } + currentOffset += section.size; + } + + return true; +} + +bool Storage::loadDeviceConfig() { + return true; +} + +bool Storage::loadElementConfig() { + return true; +} + +int Storage::updateStorage(int offset, const unsigned char *buf, int size) { + if (offset < storageStartingOffset) { + return 0; + } + + unsigned char currentData[size]; + readStorage(offset, currentData, size, false); + + if (memcmp(currentData, buf, size)) { + return writeStorage(offset, buf, size); + } + return size; +} + +void Storage::setStateSavePeriod(unsigned long periodMs) { + if (periodMs < 1000) { + saveStatePeriod = 1000; + } else { + saveStatePeriod = periodMs; + } +} + +bool Storage::saveStateAllowed(unsigned long ms) { + if (ms - lastWriteTimestamp > saveStatePeriod) { + lastWriteTimestamp = ms; + return true; + } + return false; +} + diff --git a/lib/SuplaDevice/src/supla/storage/storage.h b/lib/SuplaDevice/src/supla/storage/storage.h new file mode 100644 index 00000000..6a715ead --- /dev/null +++ b/lib/SuplaDevice/src/supla/storage/storage.h @@ -0,0 +1,100 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_storage_h +#define _supla_storage_h + +#include + +#define STORAGE_SECTION_TYPE_DEVICE_CONFIG 1 +#define STORAGE_SECTION_TYPE_ELEMENT_CONFIG 2 +#define STORAGE_SECTION_TYPE_ELEMENT_STATE 3 + +namespace Supla { + +class Storage { + public: + static Storage *Instance(); + static bool Init(); + static bool ReadState(unsigned char *, int); + static bool WriteState(const unsigned char *, int); + static bool LoadDeviceConfig(); + static bool LoadElementConfig(); + static void PrepareState(bool dryRun = false); + static bool FinalizeSaveState(); + static bool SaveStateAllowed(unsigned long); + + Storage(unsigned int storageStartingOffset = 0); + + // Changes default state save period time + virtual void setStateSavePeriod(unsigned long periodMs); + + virtual bool init(); + virtual bool readState(unsigned char *, int); + virtual bool writeState(const unsigned char *, int); + + virtual bool loadDeviceConfig(); + virtual bool loadElementConfig(); + virtual void prepareState(bool performDryRun); + virtual bool finalizeSaveState(); + virtual bool saveStateAllowed(unsigned long); + + virtual void commit() = 0; + + protected: + virtual int readStorage(int, unsigned char *, int, bool = true) = 0; + virtual int writeStorage(int, const unsigned char *, int) = 0; + virtual int updateStorage(int, const unsigned char *, int); + + unsigned int storageStartingOffset; + unsigned int deviceConfigOffset; + unsigned int elementConfigOffset; + unsigned int elementStateOffset; + + unsigned int deviceConfigSize; + unsigned int elementConfigSize; + unsigned int elementStateSize; + + unsigned int currentStateOffset; + + unsigned int newSectionSize; + int sectionsCount; + bool dryRun; + + unsigned long saveStatePeriod; + unsigned long lastWriteTimestamp; + + static Storage *instance; +}; + +#pragma pack(push, 1) +struct Preamble { + unsigned char suplaTag[5]; + uint16_t version; + uint8_t sectionsCount; +}; + +struct SectionPreamble { + unsigned char type; + uint16_t size; + uint16_t crc1; + uint16_t crc2; +}; +#pragma pack(pop) + +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/supla_lib_config.h b/lib/SuplaDevice/src/supla/supla_lib_config.h new file mode 100644 index 00000000..4e8071bb --- /dev/null +++ b/lib/SuplaDevice/src/supla/supla_lib_config.h @@ -0,0 +1,12 @@ +/* + * Put here all custom defines to customize library functionality + * Supported defines: + * SUPLA_COMM_DEBUG - enables logging of send and received data to/from server + * + */ +#ifndef supla_lib_config_h_ +#define supla_lib_config_h_ + +#define SUPLA_COMM_DEBUG + +#endif diff --git a/lib/SuplaDevice/src/supla/timer.cpp b/lib/SuplaDevice/src/supla/timer.cpp new file mode 100644 index 00000000..e0609777 --- /dev/null +++ b/lib/SuplaDevice/src/supla/timer.cpp @@ -0,0 +1,115 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include + +#include "timer.h" + +#if defined(ARDUINO_ARCH_ESP32) +#include +#endif + +namespace { +#if defined(ARDUINO_ARCH_ESP8266) +ETSTimer supla_esp_timer; +ETSTimer supla_esp_fastTimer; + +void esp_timer_cb(void *timer_arg) { + SuplaDevice.onTimer(); +} + +void esp_fastTimer_cb(void *timer_arg) { + SuplaDevice.onFastTimer(); +} +#elif defined(ARDUINO_ARCH_ESP32) +hw_timer_t *supla_esp_timer = NULL; +hw_timer_t *supla_esp_fastTimer = NULL; + +void IRAM_ATTR esp_timer_cb() { + SuplaDevice.onTimer(); +} + +void IRAM_ATTR esp_fastTimer_cb() { + SuplaDevice.onFastTimer(); +} +#else +ISR(TIMER1_COMPA_vect) { + SuplaDevice.onTimer(); +} +ISR(TIMER2_COMPA_vect) { + SuplaDevice.onFastTimer(); +} +#endif +}; // namespace + +namespace Supla { +void initTimers() { +#if defined(ARDUINO_ARCH_ESP8266) + + os_timer_disarm(&supla_esp_timer); + os_timer_setfn(&supla_esp_timer, (os_timer_func_t *)esp_timer_cb, NULL); + os_timer_arm(&supla_esp_timer, 10, 1); + + os_timer_disarm(&supla_esp_fastTimer); + os_timer_setfn(&supla_esp_fastTimer, (os_timer_func_t *)esp_fastTimer_cb, NULL); + os_timer_arm(&supla_esp_fastTimer, 1, 1); + +#elif defined(ARDUINO_ARCH_ESP32) + supla_esp_timer = timerBegin(0, 80, true); // timer 0, div 80 + timerAttachInterrupt(supla_esp_timer, &esp_timer_cb, true); // attach callback + timerAlarmWrite(supla_esp_timer, 10 * 1000, false); // set time in us + timerAlarmEnable(supla_esp_timer); // enable interrupt + + supla_esp_fastTimer = timerBegin(1, 80, true); + timerAttachInterrupt(supla_esp_fastTimer, + &esp_fastTimer_cb, + true); // attach callback + timerAlarmWrite(supla_esp_fastTimer, 1 * 1000, false); // set time in us + timerAlarmEnable(supla_esp_fastTimer); // enable interrupt +#else + // Timer 1 for interrupt frequency 100 Hz (10 ms) + TCCR1A = 0; // set entire TCCR1A register to 0 + TCCR1B = 0; // same for TCCR1B + TCNT1 = 0; // initialize counter value to 0 + // set compare match register for 1hz increments + OCR1A = 155; // (16*10^6) / (100*1024) - 1 (must be <65536) == 155.25 + // turn on CTC mode + TCCR1B |= (1 << WGM12); + // Set CS12 and CS10 bits for 1024 prescaler + TCCR1B |= (1 << CS12) | (1 << CS10); + // enable timer compare interrupt + TIMSK1 |= (1 << OCIE1A); + sei(); // enable interrupts + + // TIMER 2 for interrupt frequency 2000 Hz (0.5 ms) + cli(); // stop interrupts + TCCR2A = 0; // set entire TCCR2A register to 0 + TCCR2B = 0; // same for TCCR2B + TCNT2 = 0; // initialize counter value to 0 + // set compare match register for 2000 Hz increments + OCR2A = 249; // = 16000000 / (32 * 2000) - 1 (must be <256) + // turn on CTC mode + TCCR2B |= (1 << WGM21); + // Set CS22, CS21 and CS20 bits for 32 prescaler + TCCR2B |= (0 << CS22) | (1 << CS21) | (1 << CS20); + // enable timer compare interrupt + TIMSK2 |= (1 << OCIE2A); + sei(); // allow interrupts +#endif +} + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/timer.h b/lib/SuplaDevice/src/supla/timer.h new file mode 100644 index 00000000..a2fa2049 --- /dev/null +++ b/lib/SuplaDevice/src/supla/timer.h @@ -0,0 +1,24 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _supla_timer_h +#define _supla_timer_h + +namespace Supla { + void initTimers(); +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/tools.cpp b/lib/SuplaDevice/src/supla/tools.cpp new file mode 100644 index 00000000..28f34f74 --- /dev/null +++ b/lib/SuplaDevice/src/supla/tools.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "tools.h" + +void float2DoublePacked(float number, uint8_t *bar, int byteOrder) { + _FLOATCONV fl; + fl.f = number; + _DBLCONV dbl; + dbl.p.s = fl.p.s; + dbl.p.e = fl.p.e - 127 + 1023; // exponent adjust + dbl.p.m = fl.p.m; + +#ifdef IEEE754_ENABLE_MSB + if (byteOrder == LSBFIRST) { +#endif + for (int i = 0; i < 8; i++) { + bar[i] = dbl.b[i]; + } +#ifdef IEEE754_ENABLE_MSB + } else { + for (int i = 0; i < 8; i++) { + bar[i] = dbl.b[7 - i]; + } + } +#endif +} diff --git a/lib/SuplaDevice/src/supla/tools.h b/lib/SuplaDevice/src/supla/tools.h new file mode 100644 index 00000000..0a2c2a6d --- /dev/null +++ b/lib/SuplaDevice/src/supla/tools.h @@ -0,0 +1,27 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _tools_H_ +#define _tools_H_ + +#include "supla-common/IEEE754tools.h" + +void float2DoublePacked(float number, uint8_t *bar, int byteOrder = LSBFIRST); + + +#endif diff --git a/lib/SuplaDevice/src/supla/triggerable.h b/lib/SuplaDevice/src/supla/triggerable.h new file mode 100644 index 00000000..1614de6b --- /dev/null +++ b/lib/SuplaDevice/src/supla/triggerable.h @@ -0,0 +1,28 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _triggerable_h +#define _triggerable_h + +namespace Supla { +class Triggerable { + public: + virtual void runAction(int event, int action) = 0; +}; + +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/uptime.cpp b/lib/SuplaDevice/src/supla/uptime.cpp new file mode 100644 index 00000000..2b447084 --- /dev/null +++ b/lib/SuplaDevice/src/supla/uptime.cpp @@ -0,0 +1,63 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "uptime.h" + +namespace Supla { + +Uptime::Uptime() + : deviceUptime(0), + connectionUptime(0), + lastConnectionResetCause(SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN), + lastMillis(0), + acceptConnectionLostCause(false) { +} + +void Uptime::iterate(unsigned long millis) { + int seconds = (millis - lastMillis) / 1000; + if (seconds > 0) { + lastMillis = millis - ((millis - lastMillis) % 1000); + deviceUptime += seconds; + connectionUptime += seconds; + } +} + +void Uptime::resetConnectionUptime() { + connectionUptime = 0; + acceptConnectionLostCause = true; +} + +void Uptime::setConnectionLostCause(unsigned char cause) { + if (acceptConnectionLostCause) { + lastConnectionResetCause = cause; + acceptConnectionLostCause = false; + } +} + +unsigned _supla_int_t Uptime::getUptime() { + return deviceUptime; +} + +unsigned _supla_int_t Uptime::getConnectionUptime() { + return connectionUptime; +} + +unsigned char Uptime::getLastResetCause() { + return lastConnectionResetCause; +} + + +}; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/uptime.h b/lib/SuplaDevice/src/supla/uptime.h new file mode 100644 index 00000000..8f9ad7ec --- /dev/null +++ b/lib/SuplaDevice/src/supla/uptime.h @@ -0,0 +1,47 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _uptime_h +#define _uptime_h + +#include "../supla-common/proto.h" + +namespace Supla { + +class Uptime { + public: + Uptime(); + + void iterate(unsigned long millis); + void resetConnectionUptime(); + void setConnectionLostCause(unsigned char cause); + + unsigned _supla_int_t getUptime(); + unsigned _supla_int_t getConnectionUptime(); + unsigned char getLastResetCause(); + + + protected: + unsigned long lastMillis; + unsigned _supla_int_t deviceUptime; + unsigned _supla_int_t connectionUptime; + unsigned char lastConnectionResetCause; + bool acceptConnectionLostCause; +}; + +}; + +#endif From f6b260fd5821a6f3806af5693d0fb74cfc08cc9e Mon Sep 17 00:00:00 2001 From: Espablo Date: Mon, 16 Nov 2020 22:46:34 +0100 Subject: [PATCH 003/233] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 89cc49cb..9d4fee1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +##### Visual Studio Code ##### .pio +.vscode .vscode/.browse.c_cpp.db* .vscode/c_cpp_properties.json .vscode/launch.json From bdf111ec8cb605cd19fbf13f81612192abf95f4d Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 17 Nov 2020 08:05:35 +0100 Subject: [PATCH 004/233] =?UTF-8?q?wy=C5=9Bwietlanie=20prawid=C5=82owej=20?= =?UTF-8?q?tre=C5=9B=C4=87ie=20dla=20UI=5FLANGUAGE=3Den?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaConfigESP.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 06b6f622..d2c32cac 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -246,6 +246,7 @@ void ledBlinking_func(void *timer_arg) { } void status_func(int status, const char *msg) { +#ifndef UI_LANGUAGE switch (status) { case 2: ConfigESP->supla_status.msg = "Już zainicjalizowane"; @@ -306,6 +307,9 @@ void status_func(int status, const char *msg) { case 20: ConfigESP->supla_status.msg = "Przekroczono limit urządzeń"; } +#else +ConfigESP->supla_status.msg = msg; +#endif static int lock; if (status == 17 && ConfigESP->configModeESP == NORMAL_MODE) { From 35f239451d1adc9c533e40964a51ce44781f9791 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 17 Nov 2020 08:06:22 +0100 Subject: [PATCH 005/233] #define SUPLA_OTA --- src/GUI-Generic_Config.h | 64 ++++++++++++++++++++++++++++- src/SuplaWebServer.cpp | 11 ++++- src/SuplaWebServer.h | 88 +++++++++++++++++++++------------------- 3 files changed, 118 insertions(+), 45 deletions(-) diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index c8210a35..72cdb4ac 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -1,11 +1,21 @@ #ifndef GUI_Generic_Config_h #define GUI_Generic_Config_h -// #define DEBUG_MODE - +//#define DEBUG_MODE +//#define SUPLA_OTA // Language en - english, pl - polish (default if not defined UI_LANGUAGE) // #define UI_LANGUAGE en +//#define GUI_Generic +//#define GUI_Generic_minimal +//#define GUI_Generic_lite +//#define GUI_Generic_sensors + +/*********************************************************************************************\ + * GUI_Generic +\*********************************************************************************************/ +#ifdef GUI_Generic + #define SUPLA_RELAY #define SUPLA_BUTTON #define SUPLA_LIMIT_SWITCH @@ -33,5 +43,55 @@ // Other #define SUPLA_HC_SR04 #define SUPLA_IMPULSE_COUNTER // NOT SUPPORTED +#endif // GUI_Generic + +/*********************************************************************************************\ + * GUI_Generic_minimal +\*********************************************************************************************/ +#ifdef GUI_Generic_minimal +#define SUPLA_RELAY +#define SUPLA_BUTTON +#define SUPLA_LIMIT_SWITCH +#define SUPLA_CONFIG +#endif // GUI_Generic_minimal + +/*********************************************************************************************\ + * GUI_Generic_lite +\*********************************************************************************************/ +#ifdef GUI_Generic_lite +#define SUPLA_RELAY +#define SUPLA_BUTTON +#define SUPLA_LIMIT_SWITCH +#define SUPLA_CONFIG + +// 1Wire +#define SUPLA_DS18B20 +#define SUPLA_DHT22 +#define SUPLA_SI7021_SONOFF +#endif // GUI_Generic_lite + +/*********************************************************************************************\ + * GUI_Generic_sensors +\*********************************************************************************************/ +#ifdef GUI_Generic_sensors +#define SUPLA_DS18B20 +#define SUPLA_DHT11 +#define SUPLA_DHT22 +#define SUPLA_SI7021_SONOFF + +// i2c +#define SUPLA_BME280 +#define SUPLA_SHT30 +#define SUPLA_SI7021 +// #define SUPLA_HTU21D // 0x40 NOT SUPPORTED +// #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED +// #define SUPLA_BH1750 // 0x23 AND 0x5C NOT SUPPORTED +// #define SUPLA_MAX44009 // A0 NOT SUPPORTED + +// SPI +#define SUPLA_MAX6675 +#endif // GUI_Generic_sensors #endif // GUI-Generic_Config_h + + diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 0eaf1470..5b0e86f0 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -33,7 +33,9 @@ void SuplaWebServer::begin() { strcpy(this->www_username, ConfigManager->get(KEY_LOGIN)->getValue()); strcpy(this->www_password, ConfigManager->get(KEY_LOGIN_PASS)->getValue()); +#ifdef SUPLA_OTA httpUpdater.setup(&httpServer, UPDATE_PATH, www_username, www_password); +#endif httpServer.begin(); } @@ -46,9 +48,11 @@ void SuplaWebServer::createWebServer() { httpServer.on(path, HTTP_GET, std::bind(&SuplaWebServer::handle, this)); path = PATH_START; httpServer.on(path, std::bind(&SuplaWebServer::handleSave, this)); +#ifdef SUPLA_OTA path = PATH_START; path += PATH_UPDATE; httpServer.on(path, std::bind(&SuplaWebServer::handleFirmwareUp, this)); +#endif path = PATH_START; path += PATH_REBOT; httpServer.on(path, std::bind(&SuplaWebServer::supla_webpage_reboot, this)); @@ -126,7 +130,7 @@ void SuplaWebServer::handleSave() { break; } } - +#ifdef SUPLA_OTA void SuplaWebServer::handleFirmwareUp() { if (ConfigESP->configModeESP == NORMAL_MODE) { if (!httpServer.authenticate(www_username, www_password)) @@ -134,6 +138,7 @@ void SuplaWebServer::handleFirmwareUp() { } this->sendContent(supla_webpage_upddate()); } +#endif void SuplaWebServer::handleDeviceSettings() { if (ConfigESP->configModeESP == NORMAL_MODE) { @@ -297,6 +302,7 @@ String SuplaWebServer::supla_webpage_start(int save) { content += S_DEVICE_SETTINGS; content += F(""); content += F("

"); +#ifdef SUPLA_OTA content += F(""); @@ -313,6 +320,7 @@ String SuplaWebServer::supla_webpage_start(int save) { return content; } +#ifdef SUPLA_OTA String SuplaWebServer::supla_webpage_upddate() { String content = ""; content += F("
"); @@ -334,6 +342,7 @@ String SuplaWebServer::supla_webpage_upddate() { return content; } +#endif void SuplaWebServer::supla_webpage_reboot() { if (ConfigESP->configModeESP == NORMAL_MODE) { diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 99351509..6265ec4b 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -17,7 +17,9 @@ #ifndef SuplaWebServer_h #define SuplaWebServer_h -#include +#ifdef SUPLA_OTA + #include +#endif #include #include @@ -40,7 +42,7 @@ #define PATH_DEVICE_SETTINGS "devicesettings" #define PATH_DEFAULT_SETTINGS "defaultsettings" #define PATH_LOGIN_SETTINGS "loginsettings" -#define PATH_SAVE_BOARD "saveboard" +#define PATH_SAVE_BOARD "saveboard" #define INPUT_WIFI_SSID "sid" #define INPUT_WIFI_PASS "wpw" @@ -50,48 +52,50 @@ #define INPUT_MODUL_LOGIN "mlg" #define INPUT_MODUL_PASS "mps" #define INPUT_ROLLERSHUTTER "irsr" -#define INPUT_BOARD "board" +#define INPUT_BOARD "board" class SuplaWebServer : public Supla::Element { - public: - String selectGPIO(const char* input, uint8_t function, uint8_t nr = 0); - SuplaWebServer(); - void begin(); - - char www_username[MAX_MLOGIN]; - char www_password[MAX_MPASSWORD]; - char* update_path = (char*)UPDATE_PATH; - - const String SuplaFavicon(); - const String SuplaIconEdit(); - const String SuplaJavaScript(String java_return = PATH_START); - const String SuplaSaveResult(int save); - - void sendContent(const String content); - void rebootESP(); - - ESP8266WebServer httpServer = {80}; - ESP8266HTTPUpdateServer httpUpdater; - - private: - void iterateAlways(); - void handle(); - void handleSave(); - void handleWizardSave(); - void handleFirmwareUp(); - void handleDeviceSettings(); - void handleBoardSave(); - void handleDefaultSettings(); - void handleLoginSettings(); - void createWebServer(); - - String supla_webpage_start(int save); - String supla_webpage_upddate(); - void supla_webpage_reboot(); - String deviceSettings(int save); - String loginSettings(); - - void redirectToIndex(); + public: + String selectGPIO(const char* input, uint8_t function, uint8_t nr = 0); + SuplaWebServer(); + void begin(); + + char www_username[MAX_MLOGIN]; + char www_password[MAX_MPASSWORD]; + char* update_path = (char*)UPDATE_PATH; + + const String SuplaFavicon(); + const String SuplaIconEdit(); + const String SuplaJavaScript(String java_return = PATH_START); + const String SuplaSaveResult(int save); + + void sendContent(const String content); + void rebootESP(); + + ESP8266WebServer httpServer = {80}; +#ifdef SUPLA_OTA + ESP8266HTTPUpdateServer httpUpdater; + void handleFirmwareUp(); + String supla_webpage_upddate(); +#endif + + private: + void iterateAlways(); + void handle(); + void handleSave(); + void handleWizardSave(); + void handleDeviceSettings(); + void handleBoardSave(); + void handleDefaultSettings(); + void handleLoginSettings(); + void createWebServer(); + + String supla_webpage_start(int save); + void supla_webpage_reboot(); + String deviceSettings(int save); + String loginSettings(); + + void redirectToIndex(); }; #endif // SuplaWebServer_h From b010e7dea8982e49733b0e0f4ad5580f920fccba Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 08:51:55 +0100 Subject: [PATCH 006/233] przeniesienie HTTP_COPYRIGHT --- src/SuplaWebServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 0eaf1470..e34b34b9 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -596,12 +596,14 @@ void SuplaWebServer::sendContent(const String content) { httpServer.sendContent_P(HTTP_LOGO); String summary = FPSTR(HTTP_SUMMARY); + summary.replace("{h}", ConfigManager->get(KEY_HOST_NAME)->getValue()); summary.replace("{s}", ConfigESP->getLastStatusSupla()); summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); summary.replace("{m}", ConfigESP->getMacAddress(true)); httpServer.sendContent(summary); + httpServer.sendContent_P(HTTP_COPYRIGHT); // httpServer.send(200, "text/html", ""); for (int i = 0; i < fileSize; i++) { @@ -621,7 +623,6 @@ void SuplaWebServer::sendContent(const String content) { bufferCounter = 0; _buffer = ""; } - httpServer.sendContent_P(HTTP_COPYRIGHT); httpServer.chunkedResponseFinalize(); } From dac84d8079d26c2a5892ee26ab621a643451c53c Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 09:29:03 +0100 Subject: [PATCH 007/233] =?UTF-8?q?uzupe=C5=82nione=20t=C5=82umaczenie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaConfigESP.cpp | 50 +++++++++++++++++------------------------- src/language/en.h | 19 ++++++++++++++++ src/language/pl.h | 19 ++++++++++++++++ 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 06b6f622..7fb00ce8 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -19,6 +19,7 @@ #include "SuplaConfigESP.h" #include "SuplaConfigManager.h" #include "SuplaDeviceGUI.h" +#include "GUIGenericCommon.h" SuplaConfigESP::SuplaConfigESP() { configModeESP = NORMAL_MODE; @@ -45,15 +46,6 @@ SuplaConfigESP::SuplaConfigESP() { configModeInit(); } - // if(String(ConfigManager->get(KEY_WIFI_SSID)->getValue()) == 0 || - // String(ConfigManager->get(KEY_WIFI_PASS)->getValue()) == 0 || - // String(ConfigManager->get(KEY_SUPLA_SERVER)->getValue()) == - // DEFAULT_SERVER || - // String(ConfigManager->get(KEY_SUPLA_EMAIL)->getValue()) == - // DEFAULT_EMAIL){ - // configModeInit(); - // return; - // } SuplaDevice.setStatusFuncImpl(&status_func); } @@ -248,63 +240,61 @@ void ledBlinking_func(void *timer_arg) { void status_func(int status, const char *msg) { switch (status) { case 2: - ConfigESP->supla_status.msg = "Już zainicjalizowane"; + ConfigESP->supla_status.msg = S_ALEREADY_INITIATED; break; case 3: - ConfigESP->supla_status.msg = "Nie przypisane CB"; + ConfigESP->supla_status.msg = S_NOT_ASSIGNED_CB; break; case 4: - ConfigESP->supla_status.msg = - "Nieprawidłowy identyfikator GUID lub rejestracja urządzeń " - "NIEAKTYWNA"; + ConfigESP->supla_status.msg = S_INVALID_GUID_OR_DEVICE_REGISTRATION_INACTIVE; break; case 5: - ConfigESP->supla_status.msg = "Nieznany adres serwera"; + ConfigESP->supla_status.msg = S_UNKNOWN_SEVER_ADDRESS; break; case 6: - ConfigESP->supla_status.msg = "Nieznany identyfikator ID"; + ConfigESP->supla_status.msg = S_UNKNOWN_ID; break; case 7: - ConfigESP->supla_status.msg = "Zainicjowany"; + ConfigESP->supla_status.msg = S_INITIATED; break; case 8: - ConfigESP->supla_status.msg = "Przekroczono limit kanału"; + ConfigESP->supla_status.msg = S_CHANNEL_LIMIT_EXCEEDED; break; case 9: - ConfigESP->supla_status.msg = "Rozłączony"; + ConfigESP->supla_status.msg = S_DISCONNECTED; break; case 10: - ConfigESP->supla_status.msg = "Rejestracja w toku"; + ConfigESP->supla_status.msg = S_REGISRATION_IS_PENDING; break; case 11: - ConfigESP->supla_status.msg = "Błąd zmiennej"; + ConfigESP->supla_status.msg = S_VARIABLE_ERROR; break; case 12: - ConfigESP->supla_status.msg = "Błąd wersji protokołu"; + ConfigESP->supla_status.msg = S_PROTOCOL_VERSION_ERROR; break; case 13: - ConfigESP->supla_status.msg = "Złe poświadczenia"; + ConfigESP->supla_status.msg = S_BAD_CREDENTIALS; break; case 14: - ConfigESP->supla_status.msg = "Tymczasowo niedostępne"; + ConfigESP->supla_status.msg = S_TEMPORARILY_UNAVAILABLE; break; case 15: - ConfigESP->supla_status.msg = "Konflikt lokalizacji"; + ConfigESP->supla_status.msg = S_LOCATION_CONFLICT; break; case 16: - ConfigESP->supla_status.msg = "Konflikt kanałów"; + ConfigESP->supla_status.msg = S_CHANNEL_CONFLICT; break; case 17: - ConfigESP->supla_status.msg = "Zarejestrowany i gotowy"; + ConfigESP->supla_status.msg = S_REGISTERED_AND_READY; break; case 18: - ConfigESP->supla_status.msg = "Urządzenie jest rozłączone"; + ConfigESP->supla_status.msg = S_DEVICE_IS_DISCONNECTED; break; case 19: - ConfigESP->supla_status.msg = "Lokalizacja jest wyłączona"; + ConfigESP->supla_status.msg = S_LOCATION_IS_DISABLED; break; case 20: - ConfigESP->supla_status.msg = "Przekroczono limit urządzeń"; + ConfigESP->supla_status.msg = S_DEVICE_LIMIT_EXCEEDED; } static int lock; diff --git a/src/language/en.h b/src/language/en.h index ab971024..9ee8016f 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -55,5 +55,24 @@ #define S_WRITE_ERROR_UNABLE_TO_READ_FILE_FS_PARTITION_MISSING "Write error - ubable to read file FS. Partition missing" #define S_DATA_SAVED_RESTART_MODULE "Data saved - restart module" #define S_WRITE_ERROR_BAD_DATA "Write error - bad data" +#define S_ALEREADY_INITIATED "Already initiated" +#define S_NOT_ASSIGNED_CB "Not assigned CB" +#define S_INVALID_GUID_OR_DEVICE_REGISTRATION_INACTIVE "Invalid GUID or device registration INACTIVE" +#define S_UNKNOWN_SEVER_ADDRESS "Unknown server address" +#define S_UNKNOWN_ID "Unknown ID" +#define S_INITIATED "Initiated" +#define S_CHANNEL_LIMIT_EXCEEDED "Channel limit exceeded" +#define S_DISCONNECTED "Disconnected" +#define S_REGISRATION_IS_PENDING "Registration is pending" +#define S_VARIABLE_ERROR "Variable error" +#define S_PROTOCOL_VERSION_ERROR "Protocol version error" +#define S_BAD_CREDENTIALS "Bad credentials" +#define S_TEMPORARILY_UNAVAILABLE "Temporarily unavailable" +#define S_LOCATION_CONFLICT "Location conflict" +#define S_CHANNEL_CONFLICT "Channel conflict" +#define S_REGISTERED_AND_READY "Registered and ready" +#define S_DEVICE_IS_DISCONNECTED "The device is disconnected" +#define S_LOCATION_IS_DISABLED "The location is disabled" +#define S_DEVICE_LIMIT_EXCEEDED "Device limit exceeded" #endif // _LANGUAGE_EN_S_H_ diff --git a/src/language/pl.h b/src/language/pl.h index efecb24f..1294ca51 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -55,5 +55,24 @@ #define S_WRITE_ERROR_UNABLE_TO_READ_FILE_FS_PARTITION_MISSING "Błąd zapisu - nie można odczytać pliku - brak partycji FS." #define S_DATA_SAVED_RESTART_MODULE "Dane zapisane - restart modułu." #define S_WRITE_ERROR_BAD_DATA "Błąd zapisu - złe dane." +#define S_ALEREADY_INITIATED "Już zainicjalizowane" +#define S_NOT_ASSIGNED_CB "Nie przypisane CB" +#define S_INVALID_GUID_OR_DEVICE_REGISTRATION_INACTIVE "Nieprawidłowy identyfikator GUID lub rejestracja urządzeń NIEAKTYWNA" +#define S_UNKNOWN_SEVER_ADDRESS "Nieznany adres serwera" +#define S_UNKNOWN_ID "Nieznany identyfikator ID" +#define S_INITIATED "Zainicjowany" +#define S_CHANNEL_LIMIT_EXCEEDED "Przekroczono limit kanału" +#define S_DISCONNECTED "Rozłączony" +#define S_REGISRATION_IS_PENDING "Rejestracja w toku" +#define S_VARIABLE_ERROR "Błąd zmiennej" +#define S_PROTOCOL_VERSION_ERROR "Błąd wersji protokołu" +#define S_BAD_CREDENTIALS "Złe poświadczenia" +#define S_TEMPORARILY_UNAVAILABLE "Tymczasowo niedostępne" +#define S_LOCATION_CONFLICT "Konflikt lokalizacji" +#define S_CHANNEL_CONFLICT "Konflikt kanałów" +#define S_REGISTERED_AND_READY "Zarejestrowany i gotowy" +#define S_DEVICE_IS_DISCONNECTED "Urządzenie jest rozłączone" +#define S_LOCATION_IS_DISABLED "Lokalizacja jest wyłączona" +#define S_DEVICE_LIMIT_EXCEEDED "Przekroczono limit urządzeń" #endif // _LANGUAGE_PL_S_H_ From f204103cae8f14db7a8b2fb9afa85dcf212b5af6 Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 10:19:24 +0100 Subject: [PATCH 008/233] =?UTF-8?q?t=C5=82umaczenie=20PROGMEM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaCommonPROGMEM.h | 23 +++++++++--------- src/language/en.h | 50 +++++++++++++++++++++++++--------------- src/language/pl.h | 50 +++++++++++++++++++++++++--------------- 3 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index f997582a..81abf3e5 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -2,6 +2,7 @@ #define SuplaCommonPROGMEM_h #include #include "SuplaDeviceGUI.h" +#include "GUIGenericCommon.h" const char HTTP_META[] PROGMEM = " Date: Tue, 17 Nov 2020 13:37:24 +0100 Subject: [PATCH 009/233] multi build --- .gitignore | 2 + platformio.ini | 139 ++++++++++++++++++++++++++++++++++++++++++-- tools/copy_files.py | 51 ++++++++++++++++ 3 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 tools/copy_files.py diff --git a/.gitignore b/.gitignore index 9d4fee1a..293347b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +build_output +src/GUI-Generic.ino.cpp diff --git a/platformio.ini b/platformio.ini index c84981a7..4ee448a1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,12 +8,23 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:esp8285] -platform = espressif8266 -board = esp8285 -framework = arduino -upload_resetmethod = nodemcu +[platformio] +default_envs = + GUI_Generic_1M +; GUI_Generic_1M-en +; GUI_Generic_4M +; GUI_Generic_minimal +; GUI_Generic_lite +; GUI_Generic_sensors +; GUI_Generic_DEBUG +; GUI_Generic_blank +;lib_extra_dirs = ~/Documents/Arduino/libraries + + +[common] +upload_speed = 256000 monitor_speed = 74880 +upload_resetmethod = nodemcu lib_deps = milesburton/DallasTemperature@^3.9.1 adafruit/DHT sensor library@^1.4.0 @@ -22,6 +33,124 @@ lib_deps = datacute/DoubleResetDetector@^1.0.3 closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 +build_flags = -w + -DBEARSSL_SSL_BASIC + -D SUPLA_OTA +extra_scripts = tools/copy_files.py + +[env:GUI_Generic_1M] +platform = espressif8266 +framework = arduino +board = esp8285 +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_unflags = -D SUPLA_OTA +build_flags = ${common.build_flags} + -D GUI_Generic + +[env:GUI_Generic_1M-en] +platform = espressif8266 +framework = arduino +board = esp8285 +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_unflags = -D SUPLA_OTA +build_flags = ${common.build_flags} + -D GUI_Generic + -D UI_LANGUAGE=en + +[env:GUI_Generic_4M] +platform = espressif8266 +framework = arduino +board = esp12e +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -D GUI_Generic + +[env:GUI_Generic_minimal] +platform = espressif8266 +board = esp8285 +framework = arduino +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -D GUI_Generic_minimal + +[env:GUI_Generic_lite] +platform = espressif8266 +board = esp8285 +framework = arduino +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -D GUI_Generic_lite + +[env:GUI_Generic_sensors] +platform = espressif8266 +board = esp8285 +framework = arduino +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -D GUI_Generic_sensors + +[env:GUI_Generic_DEBUG] +platform = espressif8266 +framework = arduino +board = nodemcuv2 +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + -D GUI_Generic + -D DEBUG_MODE + +[env:GUI_Generic_blank] +platform = espressif8266 +framework = arduino +board = nodemcuv2 +upload_resetmethod = ${common.upload_resetmethod} +upload_speed = ${common.upload_speed} +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +extra_scripts = ${common.extra_scripts} +build_flags = ${common.build_flags} + +;[env:esp8285] +;platform = espressif8266 +;board = esp8285 +;framework = arduino +;upload_resetmethod = nodemcu +;monitor_speed = 74880 +;lib_deps = +; milesburton/DallasTemperature@^3.9.1 +; adafruit/DHT sensor library@^1.4.0 +; paulstoffregen/OneWire@^2.3.5 +; adafruit/Adafruit BME280 Library@^2.1.1 +; datacute/DoubleResetDetector@^1.0.3 +; closedcube/ClosedCube SHT31D@^1.5.1 +; adafruit/Adafruit Si7021 Library@^1.3.0 ; [env:esp12e] ; platform = espressif8266 diff --git a/tools/copy_files.py b/tools/copy_files.py new file mode 100644 index 00000000..7a3b6c3d --- /dev/null +++ b/tools/copy_files.py @@ -0,0 +1,51 @@ +# Inspired by: https://github.com/arendst/Tasmota/blob/development/pio/name-firmware.py +# Thanks Theo & Jason2866 :) + +Import('env') +import os +import shutil + +OUTPUT_DIR = "build_output{}".format(os.path.sep) + +def copy_to_build_output(sourcedir, variant, file_suffix): + in_file = "{}{}".format(variant, file_suffix) + full_in_file = os.path.join(sourcedir, in_file) + firmware_name = in_file = "{}{}".format(str(sourcedir).split(os.path.sep)[2], file_suffix) + + if os.path.isfile(full_in_file): + if ".bin" in file_suffix: + out_file = "{}bin{}{}".format(OUTPUT_DIR, os.path.sep, firmware_name) + else: + out_file = "{}debug{}{}".format(OUTPUT_DIR, os.path.sep, firmware_name) + + if os.path.isfile(out_file): + os.remove(out_file) + + print("\u001b[33m in file : \u001b[0m {}".format(full_in_file)) + + print("\u001b[33m copy to: \u001b[0m {}".format(out_file)) + shutil.copy(full_in_file, out_file) + + +def bin_elf_copy(source, target, env): + variant = env['PROGNAME'] + split_path = str(source[0]).rsplit(os.path.sep, 1) + + # Create a dump of the used build environment + with open('{}{}{}.env.txt'.format(split_path[0], os.path.sep, variant), 'w') as outfile: + outfile.write(env.Dump()) + outfile.close() + + # check if output directories exist and create if necessary + if not os.path.isdir(OUTPUT_DIR): + os.mkdir(OUTPUT_DIR) + + for d in ['bin', 'debug']: + if not os.path.isdir("{}{}".format(OUTPUT_DIR, d)): + os.mkdir("{}{}".format(OUTPUT_DIR, d)) + + for suff in [".elf", ".bin", ".bin.gz", "-factory.bin", ".env.txt"]: + copy_to_build_output(split_path[0], variant, suff) + + +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", [bin_elf_copy]) From 8fe65d5b851b445ff344fa3a21afaeaffba765e3 Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 14:32:52 +0100 Subject: [PATCH 010/233] =?UTF-8?q?=C5=82atwiejsze=20dodawanie=20szablon?= =?UTF-8?q?=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaConfigManager.cpp | 4 +- src/SuplaTemplateBoard.cpp | 278 ++++++++++++++++--------------------- src/SuplaTemplateBoard.h | 6 + 3 files changed, 131 insertions(+), 157 deletions(-) diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 8d435d23..8e3826aa 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -150,8 +150,8 @@ SuplaConfigManager::SuplaConfigManager() { this->addKey(KEY_SUPLA_SERVER, DEFAULT_SERVER, MAX_SUPLA_SERVER); this->addKey(KEY_SUPLA_EMAIL, DEFAULT_EMAIL, MAX_EMAIL); this->addKey(KEY_MAX_ROLLERSHUTTER, "0", 2); - this->addKey(KEY_MAX_RELAY, "1", 2); - this->addKey(KEY_MAX_BUTTON, "1", 2); + this->addKey(KEY_MAX_RELAY, "0", 2); + this->addKey(KEY_MAX_BUTTON, "0", 2); this->addKey(KEY_MAX_LIMIT_SWITCH, "0", 2); this->addKey(KEY_MAX_DHT22, "1", 2); this->addKey(KEY_MAX_DHT11, "1", 2); diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 59c22ba3..9687aa00 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -2,200 +2,168 @@ #include "SuplaWebPageRelay.h" #include -void chooseTemplateBoard(uint8_t board) { - switch (board) { - case BOARD_ELECTRODRAGON: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(16, FUNCTION_CFG_LED, HIGH); +void setButton(uint8_t gpio, uint8_t event) { + uint8_t nr = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); + String test; + nr = nr + 1; + test = nr; + ConfigManager->set(KEY_MAX_BUTTON, test.c_str()); + ConfigESP->setGpio(gpio, nr, FUNCTION_BUTTON, event); +} - ConfigManager->set(KEY_MAX_BUTTON, "2"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(2, 2, FUNCTION_BUTTON, Supla::ON_PRESS); +void setRelay(uint8_t gpio, uint8_t level) { + uint8_t nr = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); + String test; + nr = nr + 1; + test = nr; + ConfigManager->set(KEY_MAX_RELAY, test.c_str()); + ConfigESP->setGpio(gpio, nr, FUNCTION_RELAY, level, MEMORY_RELAY_RESTORE); +} - ConfigManager->set(KEY_MAX_RELAY, "2"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(13, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - break; - case BOARD_INCAN3: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(2, FUNCTION_CFG_LED, HIGH); +void setLimitSwitch(uint8_t gpio) { + uint8_t nr = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); + String test; + nr = nr + 1; + test = nr; + ConfigManager->set(KEY_MAX_LIMIT_SWITCH, test.c_str()); + ConfigESP->setGpio(gpio, nr, FUNCTION_LIMIT_SWITCH, 0); + ConfigESP->setGpio(4, 1, FUNCTION_LIMIT_SWITCH, 0); +} - ConfigManager->set(KEY_MAX_BUTTON, "2"); - ConfigESP->setGpio(14, 1, FUNCTION_BUTTON, Supla::ON_CHANGE); - ConfigESP->setGpio(12, 2, FUNCTION_BUTTON, Supla::ON_CHANGE); +void setLedCFG(uint8_t gpio, uint8_t level) { + ConfigESP->setGpio(gpio, FUNCTION_CFG_LED, level); +} - ConfigManager->set(KEY_MAX_RELAY, "2"); - ConfigESP->setGpio(5, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(13, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); +void setButtonCFG(uint8_t gpio) { + ConfigESP->setGpio(gpio, FUNCTION_CFG_BUTTON); +} - ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "2"); - ConfigESP->setGpio(4, 1, FUNCTION_LIMIT_SWITCH, 0); - ConfigESP->setGpio(16, 2, FUNCTION_LIMIT_SWITCH, 0); +void chooseTemplateBoard(uint8_t board) { + switch (board) { + case BOARD_ELECTRODRAGON: + setLedCFG(16); + setButtonCFG(0); + setButton(0); + setButton(2); + setRelay(12); + setRelay(13); + break; + case BOARD_INCAN3: + setLedCFG(2, LOW); + setButtonCFG(0); + setButton(14, Supla::ON_CHANGE); + setButton(12, Supla::ON_CHANGE); + setRelay(5); + setRelay(13); + setLimitSwitch(4); + setLimitSwitch(16); break; case BOARD_MELINK: - ConfigESP->setGpio(5, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(12, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(5, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(4, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(12); + setButtonCFG(5); + setButton(5); + setRelay(4); break; case BOARD_NEO_COOLCAM: - ConfigESP->setGpio(13, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(4, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(13, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(4); + setButtonCFG(13); + setButton(13); + setRelay(12); break; case BOARD_SHELLY1: - ConfigESP->setGpio(5, FUNCTION_CFG_BUTTON); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(5, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(4, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setButtonCFG(5); + setButton(5); + setRelay(4); break; case BOARD_SHELLY2: - ConfigESP->setGpio(12, FUNCTION_CFG_BUTTON); - - ConfigManager->set(KEY_MAX_BUTTON, "2"); - ConfigESP->setGpio(12, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(14, 2, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "2"); - ConfigESP->setGpio(4, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(5, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(16); + setButtonCFG(12); + setButton(12); + setButton(14); + setRelay(4); + setRelay(5); break; case BOARD_SONOFF_BASIC: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setRelay(12); break; case BOARD_SONOFF_DUAL_R2: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "2"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(9, 2, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "2"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(5, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setButton(9); + setRelay(12); + setRelay(5); break; case BOARD_SONOFF_S2X: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setRelay(12); ConfigESP->setGpio(14, FUNCTION_SI7021_SONOFF); break; case BOARD_SONOFF_TH: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setRelay(12); ConfigESP->setGpio(14, FUNCTION_SI7021_SONOFF); break; case BOARD_SONOFF_TOUCH: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setRelay(12); break; case BOARD_SONOFF_TOUCH_2CH: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "2"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(9, 2, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "2"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(5, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setButton(9); + setRelay(12); + setRelay(5); break; case BOARD_SONOFF_TOUCH_3CH: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "3"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(9, 2, FUNCTION_BUTTON, Supla::ON_PRESS); - ConfigESP->setGpio(10, 3, FUNCTION_BUTTON, Supla::ON_PRESS); - - ConfigManager->set(KEY_MAX_RELAY, "3"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(5, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(4, 3, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setButton(9); + setButton(10); + setRelay(12); + setRelay(5); + setRelay(4); break; case BOARD_SONOFF_4CH: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(13, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "4"); - ConfigESP->setGpio(0, 1, FUNCTION_BUTTON, Supla::ON_CHANGE); - ConfigESP->setGpio(9, 2, FUNCTION_BUTTON, Supla::ON_CHANGE); - ConfigESP->setGpio(10, 3, FUNCTION_BUTTON, Supla::ON_CHANGE); - ConfigESP->setGpio(14, 4, FUNCTION_BUTTON, Supla::ON_CHANGE); - - ConfigManager->set(KEY_MAX_RELAY, "4"); - ConfigESP->setGpio(12, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(5, 2, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(4, 3, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - ConfigESP->setGpio(15, 4, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); - + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setButton(9); + setButton(10); + setButton(14); + setRelay(12); + setRelay(5); + setRelay(4); + setRelay(15); break; case BOARD_YUNSHAN: - ConfigESP->setGpio(0, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(2, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(3, 1, FUNCTION_BUTTON, Supla::ON_CHANGE); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(4, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(2, LOW); + setButtonCFG(0); + setButton(3, Supla::ON_CHANGE); + setRelay(4); break; case BOARD_YUNTONG_SMART: - ConfigESP->setGpio(12, FUNCTION_CFG_BUTTON); - ConfigESP->setGpio(15, FUNCTION_CFG_LED, HIGH); - - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigESP->setGpio(12, 1, FUNCTION_BUTTON, Supla::ON_CHANGE); - - ConfigManager->set(KEY_MAX_RELAY, "1"); - ConfigESP->setGpio(4, 1, FUNCTION_RELAY, HIGH, MEMORY_RELAY_RESTORE); + setLedCFG(15); + setButtonCFG(12); + setButton(12, Supla::ON_CHANGE); + setRelay(4); break; default: - ConfigManager->set(KEY_MAX_BUTTON, "1"); - ConfigManager->set(KEY_MAX_RELAY, "1"); + ConfigManager->set(KEY_MAX_BUTTON, "0"); + ConfigManager->set(KEY_MAX_RELAY, "0"); ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); break; } diff --git a/src/SuplaTemplateBoard.h b/src/SuplaTemplateBoard.h index b44f9409..ab115713 100644 --- a/src/SuplaTemplateBoard.h +++ b/src/SuplaTemplateBoard.h @@ -2,7 +2,13 @@ #define SuplaTemplateBoard_h #include +#include "SuplaWebPageRelay.h" +void setButton(uint8_t gpio, uint8_t event = 0); +void setRelay(uint8_t gpio, uint8_t level = HIGH); +void setLimitSwitch(uint8_t gpio); +void setLedCFG(uint8_t gpio, uint8_t level = HIGH); +void setButtonCFG(uint8_t gpio); enum _board { From d3ff5be1ef11a04c344aff91893ec126bd2fcb69 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 17 Nov 2020 16:41:54 +0100 Subject: [PATCH 011/233] #define PGMT --- src/SuplaCommonPROGMEM.cpp | 26 ++++++++------------------ src/SuplaCommonPROGMEM.h | 2 ++ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/SuplaCommonPROGMEM.cpp b/src/SuplaCommonPROGMEM.cpp index 592861f4..9e1117db 100644 --- a/src/SuplaCommonPROGMEM.cpp +++ b/src/SuplaCommonPROGMEM.cpp @@ -1,44 +1,34 @@ #include "SuplaCommonPROGMEM.h" #include "SuplaTemplateBoard.h" -static char buffer[15]; - String GIPOString(uint8_t gpio) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(GPIO_P[gpio]))); - return buffer; + return PGMT(GPIO_P[gpio]); } String BME280String(uint8_t adr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(BME280_P[adr]))); - return buffer; + return PGMT(BME280_P[adr]); } String SHT30String(uint8_t adr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(SHT30_P[adr]))); - return buffer; + return PGMT(SHT30_P[adr]); } String StateString(uint8_t adr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(STATE_P[adr]))); - return buffer; + return PGMT(STATE_P[adr]); } String LevelString(uint8_t nr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(LEVEL_P[nr]))); - return buffer; + return PGMT(LEVEL_P[nr]); } String MemoryString(uint8_t nr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(MEMORY_P[nr]))); - return buffer; + return PGMT(MEMORY_P[nr]); } String TriggerString(uint8_t nr) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(TRIGGER_P[nr]))); - return buffer; + return PGMT(TRIGGER_P[nr]); } String BoardString(uint8_t board) { - strcpy_P(buffer, (char*)pgm_read_ptr(&(BOARD_P[board]))); - return buffer; + return PGMT(BOARD_P[board]); } diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 81abf3e5..cef31d98 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -4,6 +4,8 @@ #include "SuplaDeviceGUI.h" #include "GUIGenericCommon.h" +#define PGMT( pgm_ptr ) ( reinterpret_cast< const __FlashStringHelper * >( pgm_ptr ) ) + const char HTTP_META[] PROGMEM = "\n"; From 8f43004fda6484573b8d80c6862bb6f2f7ddb526 Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 17:41:13 +0100 Subject: [PATCH 012/233] =?UTF-8?q?aktualizacja=20szablon=C3=B3w?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaTemplateBoard.cpp | 16 ++++++++++++++++ src/SuplaTemplateBoard.h | 10 +++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 9687aa00..68e5af21 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -58,6 +58,16 @@ void chooseTemplateBoard(uint8_t board) { setLimitSwitch(4); setLimitSwitch(16); break; + case BOARD_INCAN4: + setLedCFG(12); + setButtonCFG(0); + setButton(2, Supla::ON_CHANGE); + setButton(10, Supla::ON_CHANGE); + setRelay(4); + setRelay(14); + setLimitSwitch(4); + setLimitSwitch(16); + break; case BOARD_MELINK: setLedCFG(12); setButtonCFG(5); @@ -104,6 +114,12 @@ void chooseTemplateBoard(uint8_t board) { setRelay(12); ConfigESP->setGpio(14, FUNCTION_SI7021_SONOFF); break; + case BOARD_SONOFF_SV: + setLedCFG(13); + setButtonCFG(0); + setButton(0); + setRelay(12); + break; case BOARD_SONOFF_TH: setLedCFG(13); setButtonCFG(0); diff --git a/src/SuplaTemplateBoard.h b/src/SuplaTemplateBoard.h index ab115713..08f06765 100644 --- a/src/SuplaTemplateBoard.h +++ b/src/SuplaTemplateBoard.h @@ -14,6 +14,7 @@ enum _board { BOARD_ELECTRODRAGON = 1, BOARD_INCAN3, + BOARD_INCAN4, BOARD_MELINK, BOARD_NEO_COOLCAM, BOARD_SHELLY1, @@ -21,6 +22,7 @@ enum _board BOARD_SONOFF_BASIC, BOARD_SONOFF_DUAL_R2, BOARD_SONOFF_S2X, + BOARD_SONOFF_SV, BOARD_SONOFF_TH, BOARD_SONOFF_TOUCH, BOARD_SONOFF_TOUCH_2CH, @@ -34,6 +36,7 @@ enum _board const char BOARD_NULL[] PROGMEM = "BRAK"; const char ELECTRODRAGON[] PROGMEM = "ElectroDragon"; const char INCAN3[] PROGMEM = "inCan3"; +const char INCAN4[] PROGMEM = "inCan4"; const char MELINK[] PROGMEM = "Melink"; const char NEO_COOLCAM[] PROGMEM = "Neo Coolcam"; const char SHELLY1[] PROGMEM = "Shelly 1"; @@ -41,6 +44,7 @@ const char SHELLY2[] PROGMEM = "Shelly 2"; const char SONOFF_BASIC[] PROGMEM = "SONOFF BASIC"; const char SONOFF_DUAL_R2[] PROGMEM = "SONOFF DUAL R2"; const char SONOFF_S2X[] PROGMEM = "SONOFF S2X"; +const char SONOFF_SV[] PROGMEM = "SONOFF SV"; const char SONOFF_TH[] PROGMEM = "SONOFF TH"; const char SONOFF_TOUCH[] PROGMEM = "SONOFF TOUCH"; const char SONOFF_TOUCH_2CH[] PROGMEM = "SONOFF TOUCH DUAL"; @@ -49,9 +53,9 @@ const char SONOFF_4CH[] PROGMEM = "SONOFF 4CH"; const char YUNSHAN[] PROGMEM = "Yunshan"; const char YUNTONG_SMART[] PROGMEM = "YUNTONG Smart"; -const char* const BOARD_P[MAX_MODULE] PROGMEM = {BOARD_NULL, ELECTRODRAGON, INCAN3, MELINK, NEO_COOLCAM, SHELLY1, - SHELLY2, SONOFF_BASIC, SONOFF_DUAL_R2, SONOFF_S2X, SONOFF_TH, SONOFF_TOUCH, - SONOFF_TOUCH_2CH, SONOFF_TOUCH_3CH, SONOFF_4CH, YUNSHAN, YUNTONG_SMART}; +const char* const BOARD_P[MAX_MODULE] PROGMEM = { + BOARD_NULL, ELECTRODRAGON, INCAN3, INCAN4, MELINK, NEO_COOLCAM, SHELLY1, SHELLY2, SONOFF_BASIC, SONOFF_DUAL_R2, + SONOFF_S2X, SONOFF_SV, SONOFF_TH, SONOFF_TOUCH, SONOFF_TOUCH_2CH, SONOFF_TOUCH_3CH, SONOFF_4CH, YUNSHAN, YUNTONG_SMART}; void chooseTemplateBoard(uint8_t board); From 010b0d51a741aee488401837904b5edff204086f Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 17:41:34 +0100 Subject: [PATCH 013/233] =?UTF-8?q?liter=C3=B3wka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/language/en.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/language/en.h b/src/language/en.h index d9e27996..d3497892 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -82,7 +82,7 @@ #define S_ON "ON" #define S_LOW "LOW" #define S_HIGH "HIGH" -#define S_POSITION_MEMORY "POSITION EMORY" +#define S_POSITION_MEMORY "POSITION MEMORY" #define S_REACTION_ON_PRESS "ON PRESS" #define S_REACTION_ON_RELEASE "ON RELEASE" #define S_REACTION_ON_CHANGE "ON CHANGE" From 51fcdf8f354df3fef27e53543c19efb50d33ff4e Mon Sep 17 00:00:00 2001 From: Espablo Date: Tue, 17 Nov 2020 17:51:24 +0100 Subject: [PATCH 014/233] poprawny zapis przy zmianie szablonu --- src/SuplaTemplateBoard.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 68e5af21..928acf35 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -39,6 +39,10 @@ void setButtonCFG(uint8_t gpio) { } void chooseTemplateBoard(uint8_t board) { + ConfigManager->set(KEY_MAX_BUTTON, "0"); + ConfigManager->set(KEY_MAX_RELAY, "0"); + ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); + switch (board) { case BOARD_ELECTRODRAGON: setLedCFG(16); @@ -176,11 +180,5 @@ void chooseTemplateBoard(uint8_t board) { setButton(12, Supla::ON_CHANGE); setRelay(4); break; - - default: - ConfigManager->set(KEY_MAX_BUTTON, "0"); - ConfigManager->set(KEY_MAX_RELAY, "0"); - ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); - break; } } From fd32b8d1f00d39e7c0d8dcf77bbd33a53e6d2a8f Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 17 Nov 2020 18:15:22 +0100 Subject: [PATCH 015/233] =?UTF-8?q?nowy=20spos=C3=B3b=20tworzenia=20html'a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Markup.cpp | 56 ++++++++++++++++++ src/Markup.h | 13 +++++ src/SuplaWebServer.cpp | 129 ++++++----------------------------------- 3 files changed, 86 insertions(+), 112 deletions(-) create mode 100644 src/Markup.cpp create mode 100644 src/Markup.h diff --git a/src/Markup.cpp b/src/Markup.cpp new file mode 100644 index 00000000..634a898f --- /dev/null +++ b/src/Markup.cpp @@ -0,0 +1,56 @@ +#include "Markup.h" + +void addFormHeader(String& html, const String& name) { + html += F("
"); + html += F("

"); + html += name; + html += F("

"); +} + +void addFormHeaderEnd(String& html) { + html += F("
"); +} + +void addTextBox(String& html, + const String& input_id, + const String& value_key, + const String& name, + int minlength, + int maxlength, + bool required, + bool readonly, + bool password) { + html += F(" "); +} + +void addTextBoxPassword( + String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required) { + return addTextBox(html, input_id, value_key, name, minlength, maxlength, required, false, true); +} \ No newline at end of file diff --git a/src/Markup.h b/src/Markup.h new file mode 100644 index 00000000..a10378b8 --- /dev/null +++ b/src/Markup.h @@ -0,0 +1,13 @@ +#ifndef Markup_h +#define Markup_h + +#include "SuplaDeviceGUI.h" + +void addFormHeader(String& html, const String& name); +void addFormHeaderEnd(String& html); +void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required, bool readonly = false, bool password = false); +void addTextBoxPassword(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required); + +#endif // Markup_h + + diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index baf41da5..24b8324f 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -24,6 +24,8 @@ #include "SuplaTemplateBoard.h" #include "GUIGenericCommon.h" +#include "Markup.h" + SuplaWebServer::SuplaWebServer() { } @@ -153,119 +155,22 @@ String SuplaWebServer::supla_webpage_start(int save) { content += SuplaSaveResult(save); content += SuplaJavaScript(); content += F("
"); - content += F("
"); - content += F("

"); - content += S_SETTING_WIFI_SSID; - content += F("

"); - content += F(" "); - content += F("get(KEY_WIFI_PASS)->getValue() != 0) { - content += F("required>"); - } - else { - content += F("'minlength='"); - content += MIN_PASSWORD; - content += F("' length="); - content += MAX_PASSWORD; - content += F(" required>"); - } - content += F(" "); - content += F(" "); - content += F("
"); - content += F("
"); - content += F("

"); - content += S_SETTING_SUPLA; - content += F("

"); - content += F(" "); - content += F(""); - content += F("
"); - content += F("
"); - content += F("

"); - content += S_SETTING_ADMIN; - content += F("

"); - content += F(""); - content += F(""); - content += F(""); - content += F("
"); + addFormHeader(content, S_SETTING_WIFI_SSID); + addTextBox(content, INPUT_WIFI_SSID, KEY_WIFI_SSID, S_WIFI_SSID, 0, MAX_SSID, true); + addTextBoxPassword(content, INPUT_WIFI_PASS, KEY_WIFI_PASS, S_WIFI_PASS, MIN_PASSWORD, MAX_PASSWORD, true); + addTextBox(content, INPUT_HOSTNAME, KEY_HOST_NAME, S_HOST_NAME, 0, MAX_HOSTNAME, true); + addFormHeaderEnd(content); + + addFormHeader(content, S_SETTING_SUPLA); + addTextBox(content, INPUT_SERVER, KEY_SUPLA_SERVER, S_SUPLA_SERVER, 0, MAX_SUPLA_SERVER, true); + addTextBox(content, INPUT_EMAIL, KEY_SUPLA_EMAIL, S_SUPLA_EMAIL, 0, MAX_EMAIL, true); + addFormHeaderEnd(content); + + addFormHeader(content, S_SETTING_ADMIN); + addTextBox(content, INPUT_MODUL_LOGIN, KEY_LOGIN, S_LOGIN, 0, MAX_MLOGIN, true); + addTextBoxPassword(content, INPUT_MODUL_PASS, KEY_LOGIN_PASS, S_LOGIN_PASS, MIN_PASSWORD, MAX_MPASSWORD, true); + addFormHeaderEnd(content); #ifdef SUPLA_ROLLERSHUTTER uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); From dd43e3cce5a65e9b26842068f52551b4f3131349 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 08:15:38 +0100 Subject: [PATCH 016/233] Wybieranie trybu konfiguracyjnego z GUI --- src/GUI-Generic.ino | 7 ++--- src/SuplaCommonPROGMEM.h | 4 +++ src/SuplaConfigESP.cpp | 57 ++++++++++++++++---------------------- src/SuplaConfigESP.h | 8 ++++-- src/SuplaConfigManager.cpp | 1 + src/SuplaConfigManager.h | 1 + src/SuplaDeviceGUI.h | 1 - src/SuplaGuiWiFi.h | 2 +- src/SuplaWebPageConfig.cpp | 23 +++++++++++++++ src/SuplaWebPageConfig.h | 1 + src/language/en.h | 3 ++ src/language/pl.h | 3 ++ 12 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 6991d08d..08df3cc5 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -105,8 +105,8 @@ void setup() { #endif #ifdef SUPLA_CONFIG - Supla::GUI::addConfigESP(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), ConfigESP->getGpio(FUNCTION_CFG_LED), CONFIG_MODE_10_ON_PRESSES, - ConfigESP->getLevel(FUNCTION_CFG_LED)); + Supla::GUI::addConfigESP(ConfigESP->getGpio(FUNCTION_CFG_BUTTON), ConfigESP->getGpio(FUNCTION_CFG_LED), + ConfigManager->get(KEY_CFG_MODE)->getValueInt(), ConfigESP->getLevel(FUNCTION_CFG_LED)); #endif #ifdef SUPLA_DHT11 @@ -188,8 +188,7 @@ void setup() { #ifdef SUPLA_MAX6675 if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { - new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), - ConfigESP->getGpio(FUNCTION_D0)); + new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0)); } #endif diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index cef31d98..3688831d 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -96,6 +96,10 @@ const char REACTION_ON_RELEASE[] PROGMEM = S_REACTION_ON_RELEASE; const char REACTION_ON_CHANGE[] PROGMEM = S_REACTION_ON_CHANGE; const char* const TRIGGER_P[] PROGMEM = {REACTION_ON_PRESS, REACTION_ON_RELEASE, REACTION_ON_CHANGE}; +const char CFG_10_PRESSES[] PROGMEM = S_CFG_10_PRESSES; +const char CFG_5SEK_HOLD[] PROGMEM = S_5SEK_HOLD; +const char* const CFG_MODE_P[] PROGMEM = {CFG_10_PRESSES, CFG_5SEK_HOLD}; + String GIPOString(uint8_t gpio); String BME280String(uint8_t adr); String SHT30String(uint8_t adr); diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 1d6d1132..50ea3335 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -66,42 +66,25 @@ void SuplaConfigESP::addConfigESP(int _pinNumberConfig, int _pinLedConfig, int _ Supla::Control::Button *buttonConfig = new Supla::Control::Button(pinNumberConfig, true, true); if (modeConfigButton == CONFIG_MODE_10_ON_PRESSES) { - buttonConfig->addAction(CONFIG_MODE_10_ON_PRESSES, *ConfigESP, Supla::ON_PRESS); + buttonConfig->setMulticlickTime(400); + buttonConfig->addAction(CONFIG_MODE_10_ON_PRESSES, *ConfigESP, Supla::ON_CLICK_10); } if (modeConfigButton == CONFIG_MODE_5SEK_HOLD) { - buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_PRESS); - buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_RELEASE); + buttonConfig->setHoldTime(5000); + buttonConfig->addAction(CONFIG_MODE_5SEK_HOLD, *ConfigESP, Supla::ON_HOLD); } } void SuplaConfigESP::runAction(int event, int action) { if (action == CONFIG_MODE_10_ON_PRESSES) { - if (millis() - cnfigChangeTimeMs > 10000UL) { - cnfigChangeTimeMs = millis(); - countPresses = 0; - } - countPresses++; - - if (countPresses == 10) { - // Serial.println(F("CONFIG_MODE_3_PRESSES")); + if (event == Supla::ON_CLICK_10) { configModeInit(); - countPresses = 0; - return; } } - if (action == CONFIG_MODE_5SEK_HOLD) { - if (event == Supla::ON_PRESS) { - cnfigChangeTimeMs = millis(); - } - if (event == Supla::ON_RELEASE) { - if (millis() - cnfigChangeTimeMs > 5000UL) { - if (!digitalRead(pinNumberConfig)) { - // Serial.println(F("CONFIG_MODE_5SEK_HOLD")); - configModeInit(); - } - } - cnfigChangeTimeMs = 0; + if (event == Supla::ON_HOLD) { + // Serial.println(F("CONFIG_MODE_5SEK_HOLD")); + configModeInit(); } } @@ -181,7 +164,7 @@ void SuplaConfigESP::configModeInit() { configModeESP = CONFIG_MODE; ledBlinking(100); - WiFi.onEvent(WiFiEvent); + // WiFi.onEvent(WiFiEvent); // Serial.print(F("Creating Access Point")); // Serial.print(F("Setting mode ... ")); // Serial.println(WiFi.mode(WIFI_AP_STA) ? "Ready" : "Failed!"); @@ -189,17 +172,23 @@ void SuplaConfigESP::configModeInit() { WiFi.disconnect(true); WiFi.mode(WIFI_AP_STA); - String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); - while (!WiFi.softAP(CONFIG_WIFI_NAME, "")) { - // Serial.println(F(".")); - delay(100); - } - + // String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); + // while (!WiFi.softAP(CONFIG_WIFI_NAME, "")) { + // Serial.println(F(".")); + // delay(100); + // } // Serial.println(F("Network Created!")); // Serial.print(F("Soft-AP IP address = ")); // Serial.println(WiFi.softAPIP()); } +void SuplaConfigESP::iterateAlways() { + if (configModeESP == CONFIG_MODE) { + String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); + WiFi.softAP(CONFIG_WIFI_NAME, ""); + } +} + const char *SuplaConfigESP::getLastStatusSupla() { return supla_status.msg; } @@ -298,7 +287,7 @@ void status_func(int status, const char *msg) { ConfigESP->supla_status.msg = S_DEVICE_LIMIT_EXCEEDED; } #else -ConfigESP->supla_status.msg = msg; + ConfigESP->supla_status.msg = msg; #endif static int lock; @@ -480,6 +469,8 @@ void SuplaConfigESP::factoryReset() { } ConfigManager->set(KEY_ACTIVE_SENSOR, "0,0,0,0,0"); + ConfigManager->set(KEY_BOARD, "0"); + ConfigManager->set(KEY_CFG_MODE, "0"); for (nr = 0; nr <= MAX_DS18B20; nr++) { key = KEY_DS; diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index f1324fa4..cb662bd5 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -18,10 +18,12 @@ #define SuplaConfigESP_h #include - +#include #include "GUI-Generic_Config.h" enum _configModeESP { NORMAL_MODE, CONFIG_MODE }; +enum _ConfigMode { CONFIG_MODE_10_ON_PRESSES, CONFIG_MODE_5SEK_HOLD }; + typedef struct { int status; @@ -29,7 +31,7 @@ typedef struct { const char *old_msg; } _supla_status; -class SuplaConfigESP : public Supla::Triggerable { +class SuplaConfigESP : public Supla::Triggerable, public Supla::Element { public: SuplaConfigESP(); @@ -77,7 +79,7 @@ class SuplaConfigESP : public Supla::Triggerable { private: void configModeInit(); - + void iterateAlways(); int pinNumberConfig; int pinLedConfig; int modeConfigButton; diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 8e3826aa..c1513d41 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -169,6 +169,7 @@ SuplaConfigManager::SuplaConfigManager() { this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 14); this->addKey(KEY_BOARD, "0", 2); + this->addKey(KEY_CFG_MODE, "0", 2); for (nr = 0; nr <= MAX_DS18B20; nr++) { key = KEY_DS; diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index 27a935b0..43f07512 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -47,6 +47,7 @@ #define KEY_ALTITUDE_BME280 "altbme280" #define KEY_ACTIVE_SENSOR "sensor" #define KEY_BOARD "board" +#define KEY_CFG_MODE "cfgmode" #define GPIO "GPIO" #define SEPARATOR ',' diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index 8ecfc25a..4b9be23a 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -37,7 +37,6 @@ #include -enum ConfigMode { CONFIG_MODE_10_ON_PRESSES, CONFIG_MODE_5SEK_HOLD }; namespace Supla { namespace GUI { diff --git a/src/SuplaGuiWiFi.h b/src/SuplaGuiWiFi.h index 7194892c..f8c322e8 100644 --- a/src/SuplaGuiWiFi.h +++ b/src/SuplaGuiWiFi.h @@ -99,8 +99,8 @@ class GUIESPWifi : public Supla::ESPWifi { } WiFi.reconnect(); } - yield(); } + yield(); } void enableBuffer(bool value) { diff --git a/src/SuplaWebPageConfig.cpp b/src/SuplaWebPageConfig.cpp index b7a3fa10..12e31190 100644 --- a/src/SuplaWebPageConfig.cpp +++ b/src/SuplaWebPageConfig.cpp @@ -100,6 +100,10 @@ void SuplaWebPageConfig::handleConfigSave() { } #endif*/ + if (strcmp(WebServer->httpServer.arg(INPUT_CFG_MODE).c_str(), "") != 0) { + ConfigManager->set(KEY_CFG_MODE, WebServer->httpServer.arg(INPUT_CFG_MODE).c_str()); + } + switch (ConfigManager->save()) { case E_CONFIG_OK: // Serial.println(F("E_CONFIG_OK: Config save")); @@ -151,6 +155,25 @@ String SuplaWebPageConfig::supla_webpage_config(int save) { page += F(""); page += WebServer->selectGPIO(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); page += F(""); + + page += F(""); + page += F("
"); diff --git a/src/SuplaWebPageConfig.h b/src/SuplaWebPageConfig.h index 9e3aaac7..7733bb33 100644 --- a/src/SuplaWebPageConfig.h +++ b/src/SuplaWebPageConfig.h @@ -9,6 +9,7 @@ #define INPUT_CFG_LED_GPIO "cfgl" #define INPUT_CFG_BTN_GPIO "cfgb" #define INPUT_CFG_LED_LEVEL "icll" +#define INPUT_CFG_MODE "cfgm" class SuplaWebPageConfig { public: diff --git a/src/language/en.h b/src/language/en.h index d9e27996..e85b21d1 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -26,6 +26,7 @@ #define S_SENSORS_I2C "SENSORS i2c" #define S_SENSORS_SPI "SENSORS SPI" #define S_LED_BUTTON_CFG "LED, BUTTON CONFIG" +#define S_CFG_MODE "CFG mode" #define S_QUANTITY "QUANTITY" #define S_GPIO_SETTINGS_FOR_RELAYS "GPIO settings for relays" #define S_RELAY "RELAYS" @@ -86,5 +87,7 @@ #define S_REACTION_ON_PRESS "ON PRESS" #define S_REACTION_ON_RELEASE "ON RELEASE" #define S_REACTION_ON_CHANGE "ON CHANGE" +#define S_CFG_10_PRESSES "10 ON PRESSES" +#define S_5SEK_HOLD "5 SEK HOLD" #endif // _LANGUAGE_EN_S_H_ diff --git a/src/language/pl.h b/src/language/pl.h index cada9693..e6fc7200 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -26,6 +26,7 @@ #define S_SENSORS_I2C "SENSORY i2c" #define S_SENSORS_SPI "SENSORY SPI" #define S_LED_BUTTON_CFG "LED, BUTTON CONFIG" +#define S_CFG_MODE "Tryb" #define S_QUANTITY "ILOŚĆ" #define S_GPIO_SETTINGS_FOR_RELAYS "Ustawienie GPIO dla przekaźników" #define S_RELAY "PRZEKAŹNIK" @@ -86,5 +87,7 @@ #define S_REACTION_ON_PRESS "WCIŚNIĘCIE" #define S_REACTION_ON_RELEASE "ZWOLNIENIE" #define S_REACTION_ON_CHANGE "ZMIANA STANU" +#define S_CFG_10_PRESSES "10 WCIŚNIĘĆ" +#define S_5SEK_HOLD "5 SEK" #endif // _LANGUAGE_PL_S_H_ From 5ed16c989dcecada9e6501a19846b2820d334b8d Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 08:17:05 +0100 Subject: [PATCH 017/233] aktualizacja SuplaDevice --- lib/SuplaDevice/src/SuplaDevice.cpp | 9 +++- lib/SuplaDevice/src/SuplaSomfy.cpp | 2 +- .../src/supla-common/IEEE754tools.h | 2 +- lib/SuplaDevice/src/supla-common/proto.h | 50 +++++++++++-------- lib/SuplaDevice/src/supla-common/srpc.c | 2 +- lib/SuplaDevice/src/supla/clock/clock.cpp | 2 +- .../src/supla/control/bistable_relay.cpp | 5 +- lib/SuplaDevice/src/supla/control/button.cpp | 20 +++++--- .../src/supla/control/light_relay.cpp | 2 +- lib/SuplaDevice/src/supla/control/relay.cpp | 11 ++-- lib/SuplaDevice/src/supla/control/relay.h | 6 +-- .../src/supla/control/rgbw_base.cpp | 7 +-- lib/SuplaDevice/src/supla/control/rgbw_base.h | 2 +- .../src/supla/control/roller_shutter.cpp | 21 +++++--- .../src/supla/control/roller_shutter.h | 3 +- lib/SuplaDevice/src/supla/element.cpp | 3 ++ lib/SuplaDevice/src/supla/events.h | 3 +- lib/SuplaDevice/src/supla/io.cpp | 2 + lib/SuplaDevice/src/supla/local_action.h | 7 +-- lib/SuplaDevice/src/supla/network/esp_wifi.h | 2 + lib/SuplaDevice/src/supla/network/network.cpp | 18 +++++-- lib/SuplaDevice/src/supla/network/network.h | 1 + lib/SuplaDevice/src/supla/pv/afore.cpp | 9 ++-- lib/SuplaDevice/src/supla/pv/afore.h | 2 +- lib/SuplaDevice/src/supla/pv/fronius.cpp | 9 ++-- lib/SuplaDevice/src/supla/pv/fronius.h | 8 +-- lib/SuplaDevice/src/supla/sensor/DS18B20.h | 44 ++++++++-------- .../src/supla/sensor/electricity_meter.h | 26 +++++----- .../src/supla/sensor/impulse_counter.cpp | 9 ++-- .../src/supla/sensor/impulse_counter.h | 4 +- .../src/supla/sensor/virtual_binary.cpp | 3 +- .../src/supla/sensor/virtual_binary.h | 2 +- lib/SuplaDevice/src/supla/storage/eeprom.cpp | 6 +-- lib/SuplaDevice/src/supla/storage/eeprom.h | 4 +- lib/SuplaDevice/src/supla/storage/fram_spi.h | 4 +- lib/SuplaDevice/src/supla/storage/storage.cpp | 15 +++--- lib/SuplaDevice/src/supla/storage/storage.h | 6 +-- lib/SuplaDevice/src/supla/timer.cpp | 2 + lib/SuplaDevice/src/supla/tools.cpp | 1 + lib/SuplaDevice/src/supla/uptime.cpp | 4 +- 40 files changed, 197 insertions(+), 141 deletions(-) diff --git a/lib/SuplaDevice/src/SuplaDevice.cpp b/lib/SuplaDevice/src/SuplaDevice.cpp index b4ddfbab..210a909e 100644 --- a/lib/SuplaDevice/src/SuplaDevice.cpp +++ b/lib/SuplaDevice/src/SuplaDevice.cpp @@ -134,12 +134,12 @@ bool SuplaDeviceClass::begin(unsigned char version) { return false; } - if (Supla::Channel::reg_dev.ServerName[0] == NULL) { + if (Supla::Channel::reg_dev.ServerName[0] == '\0') { status(STATUS_UNKNOWN_SERVER_ADDRESS, "Unknown server address"); return false; } - if (Supla::Channel::reg_dev.Email[0] == NULL) { + if (Supla::Channel::reg_dev.Email[0] == '\0') { status(STATUS_MISSING_CREDENTIALS, "Unknown email address"); return false; } @@ -356,6 +356,11 @@ void SuplaDeviceClass::iterate(void) { void SuplaDeviceClass::onVersionError(TSDC_SuplaVersionError *version_error) { status(STATUS_PROTOCOL_VERSION_ERROR, "Protocol version error"); + Serial.print(F("Protocol version error. Server min: ")); + Serial.print(version_error->server_version_min); + Serial.print(F("; Server version: ")); + Serial.println(version_error->server_version); + Supla::Network::Disconnect(); wait_for_iterate = millis() + 5000; diff --git a/lib/SuplaDevice/src/SuplaSomfy.cpp b/lib/SuplaDevice/src/SuplaSomfy.cpp index f08b98c8..a5969ccf 100644 --- a/lib/SuplaDevice/src/SuplaSomfy.cpp +++ b/lib/SuplaDevice/src/SuplaSomfy.cpp @@ -68,7 +68,7 @@ void SuplaSomfy::SendCommand(somfy_frame_t *frame, uint8_t sync) { delayMicroseconds(9415); // Silence pulse digitalWrite(_dataPin, LOW); - delayMicroseconds(89565); + delayMicroseconds((unsigned int)89565); } // Hardware sync (Two sync for the first frame, seven for the next frame) diff --git a/lib/SuplaDevice/src/supla-common/IEEE754tools.h b/lib/SuplaDevice/src/supla-common/IEEE754tools.h index ef72ed96..5a42beec 100644 --- a/lib/SuplaDevice/src/supla-common/IEEE754tools.h +++ b/lib/SuplaDevice/src/supla-common/IEEE754tools.h @@ -54,7 +54,7 @@ typedef union _DBLCONV // IEEEdouble p; _DBL p; double d; // !! is a 32bit float for UNO. - uint8_t b[4]; + uint8_t b[8]; } _DBLCONV; // diff --git a/lib/SuplaDevice/src/supla-common/proto.h b/lib/SuplaDevice/src/supla-common/proto.h index b8c56a02..84bfc8b0 100644 --- a/lib/SuplaDevice/src/supla-common/proto.h +++ b/lib/SuplaDevice/src/supla-common/proto.h @@ -270,7 +270,12 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #endif #define SUPLA_CHANNELVALUE_SIZE 8 + +#ifdef __AVR__ +#define SUPLA_CHANNELEXTENDEDVALUE_SIZE 256 +#else #define SUPLA_CHANNELEXTENDEDVALUE_SIZE 1024 +#endif #define SUPLA_CHANNELTYPE_SENSORNO 1000 #define SUPLA_CHANNELTYPE_SENSORNC 1010 // DEPRECATED @@ -312,7 +317,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNELTYPE_GENERAL_PURPOSE_MEASUREMENT 9000 // ver. >= 12 #define SUPLA_CHANNELTYPE_ENGINE 10000 // ver. >= 12 #define SUPLA_CHANNELTYPE_ACTIONTRIGGER 11000 // ver. >= 12 -#define SUPLA_CHANNELTYPE_SMARTGLASS 12000 // ver. >= 12 +#define SUPLA_CHANNELTYPE_DIGIGLASS 12000 // ver. >= 12 #define SUPLA_CHANNELDRIVER_MCP23008 2 @@ -330,7 +335,9 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNELFNC_CONTROLLINGTHEDOORLOCK 90 #define SUPLA_CHANNELFNC_OPENINGSENSOR_DOOR 100 #define SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER 110 +#define SUPLA_CHANNELFNC_CONTROLLINGTHEROOFWINDOW 115 // ver. >= 13 #define SUPLA_CHANNELFNC_OPENINGSENSOR_ROLLERSHUTTER 120 +#define SUPLA_CHANNELFNC_OPENINGSENSOR_ROOFWINDOW 125 // ver. >= 13 #define SUPLA_CHANNELFNC_POWERSWITCH 130 #define SUPLA_CHANNELFNC_LIGHTSWITCH 140 #define SUPLA_CHANNELFNC_RING 150 @@ -361,7 +368,7 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_CHANNELFNC_GENERAL_PURPOSE_MEASUREMENT 520 // ver. >= 12 #define SUPLA_CHANNELFNC_CONTROLLINGTHEENGINESPEED 600 // ver. >= 12 #define SUPLA_CHANNELFNC_ACTIONTRIGGER 700 // ver. >= 12 -#define SUPLA_CHANNELFNC_SMARTGLASS 800 // ver. >= 12 +#define SUPLA_CHANNELFNC_DIGIGLASS 800 // ver. >= 12 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATEWAYLOCK 0x00000001 #define SUPLA_BIT_FUNC_CONTROLLINGTHEGATE 0x00000002 @@ -370,20 +377,22 @@ extern char sproto_tag[SUPLA_TAG_SIZE]; #define SUPLA_BIT_FUNC_CONTROLLINGTHEROLLERSHUTTER 0x00000010 #define SUPLA_BIT_FUNC_POWERSWITCH 0x00000020 #define SUPLA_BIT_FUNC_LIGHTSWITCH 0x00000040 -#define SUPLA_BIT_FUNC_STAIRCASETIMER 0x00000080 // ver. >= 8 -#define SUPLA_BIT_FUNC_THERMOMETER 0x00000100 // ver. >= 12 -#define SUPLA_BIT_FUNC_HUMIDITYANDTEMPERATURE 0x00000200 // ver. >= 12 -#define SUPLA_BIT_FUNC_HUMIDITY 0x00000400 // ver. >= 12 -#define SUPLA_BIT_FUNC_WINDSENSOR 0x00000800 // ver. >= 12 -#define SUPLA_BIT_FUNC_PRESSURESENSOR 0x00001000 // ver. >= 12 -#define SUPLA_BIT_FUNC_RAINSENSOR 0x00002000 // ver. >= 12 -#define SUPLA_BIT_FUNC_WEIGHTSENSOR 0x00004000 // ver. >= 12 +#define SUPLA_BIT_FUNC_STAIRCASETIMER 0x00000080 // ver. >= 8 +#define SUPLA_BIT_FUNC_THERMOMETER 0x00000100 // ver. >= 12 +#define SUPLA_BIT_FUNC_HUMIDITYANDTEMPERATURE 0x00000200 // ver. >= 12 +#define SUPLA_BIT_FUNC_HUMIDITY 0x00000400 // ver. >= 12 +#define SUPLA_BIT_FUNC_WINDSENSOR 0x00000800 // ver. >= 12 +#define SUPLA_BIT_FUNC_PRESSURESENSOR 0x00001000 // ver. >= 12 +#define SUPLA_BIT_FUNC_RAINSENSOR 0x00002000 // ver. >= 12 +#define SUPLA_BIT_FUNC_WEIGHTSENSOR 0x00004000 // ver. >= 12 +#define SUPLA_BIT_FUNC_CONTROLLINGTHEROOFWINDOW 0x00008000 // ver. >= 13 #define SUPLA_EVENT_CONTROLLINGTHEGATEWAYLOCK 10 #define SUPLA_EVENT_CONTROLLINGTHEGATE 20 #define SUPLA_EVENT_CONTROLLINGTHEGARAGEDOOR 30 #define SUPLA_EVENT_CONTROLLINGTHEDOORLOCK 40 #define SUPLA_EVENT_CONTROLLINGTHEROLLERSHUTTER 50 +#define SUPLA_EVENT_CONTROLLINGTHEROOFWINDOW 55 #define SUPLA_EVENT_POWERONOFF 60 #define SUPLA_EVENT_LIGHTONOFF 70 #define SUPLA_EVENT_STAIRCASETIMERONOFF 80 // ver. >= 9 @@ -1092,7 +1101,11 @@ typedef struct { #define EM_VAR_REVERSE_ACTIVE_ENERGY_BALANCED 0x4000 #define EM_VAR_ALL 0xFFFF +#ifdef __AVR__ +#define EM_MEASUREMENT_COUNT 1 +#else #define EM_MEASUREMENT_COUNT 5 +#endif #ifdef USE_DEPRECATED_EMEV_V1 // [IODevice->Server->Client] @@ -1239,20 +1252,17 @@ typedef struct { char Name[ZWAVE_NODE_NAME_MAXSIZE]; // UTF8. Last variable in struct! } TCalCfg_ZWave_Node; // v. >= 12 -/* typedef struct { - unsigned int MinimumSec : 24; - unsigned int MaximumSec : 24; - unsigned int ValueSec : 24; - unsigned int IntervalStepSec : 24; + unsigned _supla_int_t MinimumSec : 24; + unsigned _supla_int_t MaximumSec : 24; + unsigned _supla_int_t ValueSec : 24; + unsigned _supla_int_t IntervalStepSec : 24; } TCalCfg_ZWave_WakeupSettingsReport; typedef struct { - unsigned int TimeSec : 24; + unsigned _supla_int_t TimeSec : 24; } TCalCfg_ZWave_WakeUpTime; -*/ - typedef struct { _supla_int_t Command; unsigned char Progress; // 0 - 100% @@ -1322,13 +1332,13 @@ typedef struct { char onOff; } TRGBW_Value; // v. >= 10 -#define SMARTGLASS_FLAG_HORIZONATAL 0x1 +#define DIGIGLASS_FLAG_HORIZONATAL 0x1 typedef struct { unsigned char sectionCount; // 1 - 16 unsigned char flags; unsigned short opaqueSections; -} TSmartglass_Value; +} TDigiglass_Value; typedef struct { unsigned char sec; // 0-59 diff --git a/lib/SuplaDevice/src/supla-common/srpc.c b/lib/SuplaDevice/src/supla-common/srpc.c index 80f94ffd..66b9e7d8 100644 --- a/lib/SuplaDevice/src/supla-common/srpc.c +++ b/lib/SuplaDevice/src/supla-common/srpc.c @@ -1512,7 +1512,7 @@ srpc_sdc_async_versionerror(void *_srpc, unsigned char remote_version) { _supla_int_t SRPC_ICACHE_FLASH srpc_dcs_async_ping_server(void *_srpc) { TDCS_SuplaPingServer ps; -#if defined(ESP8266) || defined(ESP32) +#if defined(ESP8266) unsigned int time = system_get_time(); ps.now.tv_sec = time / 1000000; ps.now.tv_usec = time % 1000000; diff --git a/lib/SuplaDevice/src/supla/clock/clock.cpp b/lib/SuplaDevice/src/supla/clock/clock.cpp index 39a78cc5..ffcd3f4b 100644 --- a/lib/SuplaDevice/src/supla/clock/clock.cpp +++ b/lib/SuplaDevice/src/supla/clock/clock.cpp @@ -20,7 +20,7 @@ using namespace Supla; -Clock::Clock() : localtime(0), lastMillis(0), lastServerUpdate(0) {}; +Clock::Clock() : localtime(0), lastServerUpdate(0), lastMillis(0) {}; bool Clock::isReady() { return true; diff --git a/lib/SuplaDevice/src/supla/control/bistable_relay.cpp b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp index 7efa96c9..3fabf297 100644 --- a/lib/SuplaDevice/src/supla/control/bistable_relay.cpp +++ b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp @@ -32,8 +32,8 @@ BistableRelay::BistableRelay(int pin, statusPullUp(statusPullUp), statusHighIsOn(statusHighIsOn), disarmTimeMs(0), - busy(false), - lastReadTime(0) { + lastReadTime(0), + busy(false) { stateOnInit = STATE_ON_INIT_KEEP; } @@ -108,6 +108,7 @@ void BistableRelay::turnOn(_supla_int_t duration) { } void BistableRelay::turnOff(_supla_int_t duration) { + (void)(duration); if (busy) { return; } diff --git a/lib/SuplaDevice/src/supla/control/button.cpp b/lib/SuplaDevice/src/supla/control/button.cpp index 53d2b25d..950b9f3e 100644 --- a/lib/SuplaDevice/src/supla/control/button.cpp +++ b/lib/SuplaDevice/src/supla/control/button.cpp @@ -19,14 +19,15 @@ enum StateResults {PRESSED, RELEASED, TO_PRESSED, TO_RELEASED}; Supla::Control::ButtonState::ButtonState(int pin, bool pullUp, bool invertLogic) - : pin(pin), - pullUp(pullUp), - invertLogic(invertLogic), - newStatusCandidate(LOW), - debounceTimeMs(0), + : debounceTimeMs(0), filterTimeMs(0), debounceDelayMs(50), - swNoiseFilterDelayMs(20) { + swNoiseFilterDelayMs(20), + pin(pin), + newStatusCandidate(LOW), + prevState(LOW), + pullUp(pullUp), + invertLogic(invertLogic) { } int Supla::Control::ButtonState::update() { @@ -66,10 +67,10 @@ Supla::Control::Button::Button(int pin, bool pullUp, bool invertLogic) : state(pin, pullUp, invertLogic), holdTimeMs(0), multiclickTimeMs(0), - enableExtDetection(false), + clickCounter(0), lastStateChangeMs(0), + enableExtDetection(false), holdSend(false), - clickCounter(0), bistable(false) { } @@ -124,6 +125,9 @@ void Supla::Control::Button::onTimer() { case 9: runAction(ON_CLICK_9); break; + case 10: + runAction(ON_CLICK_10); + break; } } holdSend = false; diff --git a/lib/SuplaDevice/src/supla/control/light_relay.cpp b/lib/SuplaDevice/src/supla/control/light_relay.cpp index 33b2b7d5..747a3f69 100644 --- a/lib/SuplaDevice/src/supla/control/light_relay.cpp +++ b/lib/SuplaDevice/src/supla/control/light_relay.cpp @@ -24,7 +24,7 @@ struct LightRelayStateData { unsigned short lifespan; _supla_int_t turnOnSecondsCumulative; }; -#pragma pop +#pragma pack(pop) LightRelay::LightRelay(int pin, bool highIsOn) : Relay(pin, diff --git a/lib/SuplaDevice/src/supla/control/relay.cpp b/lib/SuplaDevice/src/supla/control/relay.cpp index ae70bd1d..081d1fe7 100644 --- a/lib/SuplaDevice/src/supla/control/relay.cpp +++ b/lib/SuplaDevice/src/supla/control/relay.cpp @@ -31,12 +31,12 @@ using namespace Control; Relay::Relay(int pin, bool highIsOn, _supla_int_t functions) : pin(pin), - durationMs(0), highIsOn(highIsOn), - durationTimestamp(0), stateOnInit(STATE_ON_INIT_OFF), - keepTurnOnDurationMs(false), - storedTurnOnDurationMs(0) { + durationMs(0), + storedTurnOnDurationMs(0), + durationTimestamp(0), + keepTurnOnDurationMs(false) { channel.setType(SUPLA_CHANNELTYPE_RELAY); channel.setFuncList(functions); } @@ -117,7 +117,8 @@ void Relay::toggle(_supla_int_t duration) { } } -void Relay::runAction(int trigger, int action) { +void Relay::runAction(int event, int action) { + (void)(event); switch (action) { case TURN_ON: { turnOn(); diff --git a/lib/SuplaDevice/src/supla/control/relay.h b/lib/SuplaDevice/src/supla/control/relay.h index 98a79735..f7a8813f 100644 --- a/lib/SuplaDevice/src/supla/control/relay.h +++ b/lib/SuplaDevice/src/supla/control/relay.h @@ -58,7 +58,7 @@ class Relay : public Element, public Triggerable { virtual bool isOn(); virtual void toggle(_supla_int_t duration = 0); - void runAction(int trigger, int action); + void runAction(int event, int action); void onInit(); void onLoadState(); @@ -74,8 +74,8 @@ class Relay : public Element, public Triggerable { int8_t stateOnInit; - _supla_int_t durationMs; - _supla_int_t storedTurnOnDurationMs; + unsigned _supla_int_t durationMs; + unsigned _supla_int_t storedTurnOnDurationMs; unsigned long durationTimestamp; bool keepTurnOnDurationMs; diff --git a/lib/SuplaDevice/src/supla/control/rgbw_base.cpp b/lib/SuplaDevice/src/supla/control/rgbw_base.cpp index d7ec2c90..461b69b8 100644 --- a/lib/SuplaDevice/src/supla/control/rgbw_base.cpp +++ b/lib/SuplaDevice/src/supla/control/rgbw_base.cpp @@ -24,13 +24,13 @@ namespace Control { RGBWBase::RGBWBase() : buttonStep(10), - lastColorBrightness(100), - lastBrightness(100), curRed(0), curGreen(255), curBlue(0), curColorBrightness(0), curBrightness(0), + lastColorBrightness(100), + lastBrightness(100), defaultDimmedBrightness(20), dimIterationDirection(false), iterationDelayCounter(0), @@ -121,7 +121,8 @@ uint8_t RGBWBase::addWithLimit(int value, int addition, int limit) { return value + addition; } -void RGBWBase::runAction(int trigger, int action) { +void RGBWBase::runAction(int event, int action) { + (void)(event); switch (action) { case TURN_ON: { turnOn(); diff --git a/lib/SuplaDevice/src/supla/control/rgbw_base.h b/lib/SuplaDevice/src/supla/control/rgbw_base.h index 6000b063..452a66cf 100644 --- a/lib/SuplaDevice/src/supla/control/rgbw_base.h +++ b/lib/SuplaDevice/src/supla/control/rgbw_base.h @@ -44,7 +44,7 @@ class RGBWBase : public Element, public Triggerable { virtual void turnOn(); virtual void turnOff(); virtual void toggle(); - void runAction(int trigger, int action); + void runAction(int event, int action); void setStep(int step); void setDefaultDimmedBrightness(int dimmedBrightness); void setFadeEffectTime(int timeMs); diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp index 0d9a2102..630fb105 100644 --- a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp @@ -26,26 +26,26 @@ struct RollerShutterStateData { uint32_t openingTimeMs; int8_t currentPosition; // 0 - closed; 100 - opened }; -#pragma pop +#pragma pack(pop) RollerShutter::RollerShutter(int pinUp, int pinDown, bool highIsOn) - : highIsOn(highIsOn), - pinUp(pinUp), - pinDown(pinDown), + : closingTimeMs(0), openingTimeMs(0), - closingTimeMs(0), calibrate(true), comfortDownValue(20), comfortUpValue(80), newTargetPositionAvailable(false), + highIsOn(highIsOn), currentDirection(STOP_DIR), lastDirection(STOP_DIR), currentPosition(UNKNOWN_POSITION), + targetPosition(STOP_POSITION), + pinUp(pinUp), + pinDown(pinDown), lastMovementStartTime(0), doNothingTime(0), calibrationTime(0), operationTimeout(0) { - targetPosition = STOP_POSITION; lastPositionBeforeMovement = currentPosition; channel.setType(SUPLA_CHANNELTYPE_RELAY); channel.setDefault(SUPLA_CHANNELFNC_CONTROLLINGTHEROLLERSHUTTER); @@ -124,7 +124,8 @@ void RollerShutter::setOpenCloseTime(uint32_t newClosingTimeMs, } } -void RollerShutter::runAction(int trigger, int action) { +void RollerShutter::runAction(int event, int action) { + (void)(event); switch (action) { case CLOSE_OR_STOP: { if (inMove()) { @@ -477,7 +478,7 @@ void RollerShutter::configComfortDownValue(uint8_t position) { } void RollerShutter::onLoadState() { - RollerShutterStateData data; + RollerShutterStateData data = RollerShutterStateData(); if (Supla::Storage::ReadState((unsigned char *)&data, sizeof(data))) { closingTimeMs = data.closingTimeMs; openingTimeMs = data.openingTimeMs; @@ -505,5 +506,9 @@ void RollerShutter::onSaveState() { Supla::Storage::WriteState((unsigned char *)&data, sizeof(data)); } +int RollerShutter::getCurrentPosition() { + return currentPosition; +} + }; // namespace Control }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.h b/lib/SuplaDevice/src/supla/control/roller_shutter.h index 0b479cd7..26e46f81 100644 --- a/lib/SuplaDevice/src/supla/control/roller_shutter.h +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.h @@ -40,7 +40,7 @@ class RollerShutter : public Element, public Triggerable { RollerShutter(int pinUp, int pinDown, bool highIsOn = true); int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); - void runAction(int trigger, int action); + void runAction(int event, int action); void close(); // Sets target position to 100% void open(); // Sets target position to 0% @@ -48,6 +48,7 @@ class RollerShutter : public Element, public Triggerable { void moveUp(); // start opening roller shutter regardless of its position (keep motor going up) void moveDown(); // starts closing roller shutter regardless of its position (keep motor going down) void setTargetPosition(int newPosition); + int getCurrentPosition(); void configComfortUpValue(uint8_t position); void configComfortDownValue(uint8_t position); diff --git a/lib/SuplaDevice/src/supla/element.cpp b/lib/SuplaDevice/src/supla/element.cpp index 0839a8f8..b7f1b22c 100644 --- a/lib/SuplaDevice/src/supla/element.cpp +++ b/lib/SuplaDevice/src/supla/element.cpp @@ -78,6 +78,7 @@ void Element::onTimer(){}; void Element::onFastTimer(){}; int Element::handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue) { + (void)(newValue); return -1; } @@ -95,10 +96,12 @@ Channel *Element::getChannel() { } void Element::handleGetChannelState(TDSC_ChannelState &channelState) { + (void)(channelState); return; } int Element::handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) { + (void)(request); return SUPLA_CALCFG_RESULT_NOT_SUPPORTED; } diff --git a/lib/SuplaDevice/src/supla/events.h b/lib/SuplaDevice/src/supla/events.h index 88014963..c72c122f 100644 --- a/lib/SuplaDevice/src/supla/events.h +++ b/lib/SuplaDevice/src/supla/events.h @@ -33,7 +33,8 @@ enum Event { ON_CLICK_6, ON_CLICK_7, ON_CLICK_8, - ON_CLICK_9 + ON_CLICK_9, + ON_CLICK_10 }; }; diff --git a/lib/SuplaDevice/src/supla/io.cpp b/lib/SuplaDevice/src/supla/io.cpp index fa7a24e0..59cccc81 100644 --- a/lib/SuplaDevice/src/supla/io.cpp +++ b/lib/SuplaDevice/src/supla/io.cpp @@ -47,10 +47,12 @@ Io::Io() { } int Io::customDigitalRead(int channelNumber, uint8_t pin) { + (void)(channelNumber); return ::digitalRead(pin); } void Io::customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val) { + (void)(channelNumber); ::digitalWrite(pin, val); } diff --git a/lib/SuplaDevice/src/supla/local_action.h b/lib/SuplaDevice/src/supla/local_action.h index 71e52dbd..4ec39711 100644 --- a/lib/SuplaDevice/src/supla/local_action.h +++ b/lib/SuplaDevice/src/supla/local_action.h @@ -17,6 +17,7 @@ #ifndef _local_action_h #define _local_action_h +#include #include "triggerable.h" #define MAX_TRIGGERABLE_CLIENTS 10 @@ -26,8 +27,8 @@ namespace Supla { class TriggerableClient { public: Triggerable *client; - int onEvent; - int action; + uint8_t onEvent; + uint8_t action; }; class LocalAction { @@ -41,7 +42,7 @@ class LocalAction { protected: TriggerableClient clients[MAX_TRIGGERABLE_CLIENTS]; - int registeredClientsCount; + uint8_t registeredClientsCount; }; }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/network/esp_wifi.h b/lib/SuplaDevice/src/supla/network/esp_wifi.h index 78175be6..b819e23f 100644 --- a/lib/SuplaDevice/src/supla/network/esp_wifi.h +++ b/lib/SuplaDevice/src/supla/network/esp_wifi.h @@ -143,6 +143,7 @@ class ESPWifi : public Supla::Network { wifiConfigured = true; gotIpEventHandler = WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &event) { + (void)(event); Serial.print(F("local IP: ")); Serial.println(WiFi.localIP()); Serial.print(F("subnetMask: ")); @@ -156,6 +157,7 @@ class ESPWifi : public Supla::Network { }); disconnectedEventHandler = WiFi.onStationModeDisconnected( [](const WiFiEventStationModeDisconnected &event) { + (void)(event); Serial.println(F("WiFi station disconnected")); }); diff --git a/lib/SuplaDevice/src/supla/network/network.cpp b/lib/SuplaDevice/src/supla/network/network.cpp index 0a612f6e..4cf98681 100644 --- a/lib/SuplaDevice/src/supla/network/network.cpp +++ b/lib/SuplaDevice/src/supla/network/network.cpp @@ -25,11 +25,13 @@ namespace Supla { Network *Network::netIntf = NULL; -_supla_int_t data_read(void *buf, _supla_int_t count, void *sdc) { +_supla_int_t data_read(void *buf, _supla_int_t count, void *userParams) { + (void)(userParams); return Supla::Network::Read(buf, count); } -_supla_int_t data_write(void *buf, _supla_int_t count, void *sdc) { +_supla_int_t data_write(void *buf, _supla_int_t count, void *userParams) { + (void)(userParams); _supla_int_t r = Supla::Network::Write(buf, count); if (r > 0) { Network::Instance()->updateLastSent(); @@ -42,12 +44,15 @@ void message_received(void *_srpc, unsigned _supla_int_t call_type, void *_sdc, unsigned char proto_version) { + (void)(rr_id); + (void)(call_type); + (void)(proto_version); TsrpcReceivedData rd; - char result; + char getDataResult; Network::Instance()->updateLastResponse(); - if (SUPLA_RESULT_TRUE == (result = srpc_getdata(_srpc, &rd, 0))) { + if (SUPLA_RESULT_TRUE == (getDataResult = srpc_getdata(_srpc, &rd, 0))) { switch (rd.call_type) { case SUPLA_SDC_CALL_VERSIONERROR: ((SuplaDeviceClass *)_sdc)->onVersionError(rd.data.sdc_version_error); @@ -135,7 +140,7 @@ void message_received(void *_srpc, srpc_rd_free(&rd); - } else if (result == SUPLA_RESULT_DATA_ERROR) { + } else if (getDataResult == SUPLA_RESULT_DATA_ERROR) { supla_log(LOG_DEBUG, "DATA ERROR!"); } } @@ -158,6 +163,7 @@ Network::Network(IPAddress *ip) { } bool Network::iterate() { + return false; } void Network::updateLastSent() { @@ -200,10 +206,12 @@ void Network::setActivityTimeout(_supla_int_t activityTimeoutSec) { } void Network::setTimeout(int timeoutMs) { + (void)(timeoutMs); supla_log(LOG_DEBUG, "setTimeout is not implemented for this interface"); } void Network::fillStateData(TDSC_ChannelState &channelState) { + (void)(channelState); supla_log(LOG_DEBUG, "fillStateData is not implemented for this interface"); } diff --git a/lib/SuplaDevice/src/supla/network/network.h b/lib/SuplaDevice/src/supla/network/network.h index 447b69ab..9316eca2 100644 --- a/lib/SuplaDevice/src/supla/network/network.h +++ b/lib/SuplaDevice/src/supla/network/network.h @@ -96,6 +96,7 @@ class Network { if (Instance() != NULL) { return Instance()->ping(); } + return false; } Network(IPAddress *ip); diff --git a/lib/SuplaDevice/src/supla/pv/afore.cpp b/lib/SuplaDevice/src/supla/pv/afore.cpp index 4f8fe29b..7b6f5484 100644 --- a/lib/SuplaDevice/src/supla/pv/afore.cpp +++ b/lib/SuplaDevice/src/supla/pv/afore.cpp @@ -22,14 +22,15 @@ using namespace PV; Afore::Afore(IPAddress ip, int port, const char *loginAndPass) : ip(ip), port(port), - dataIsReady(false), + buf(), totalGeneratedEnergy(0), currentPower(0), - varFound(false), bytesCounter(0), - dataFetchInProgress(false), retryCounter(0), - vFound(false) { + vFound(false), + varFound(false), + dataIsReady(false), + dataFetchInProgress(false) { int len = strlen(loginAndPass); if (len > LOGIN_AND_PASSOWORD_MAX_LENGTH) { len = LOGIN_AND_PASSOWORD_MAX_LENGTH; diff --git a/lib/SuplaDevice/src/supla/pv/afore.h b/lib/SuplaDevice/src/supla/pv/afore.h index a8113c1d..85a978e6 100644 --- a/lib/SuplaDevice/src/supla/pv/afore.h +++ b/lib/SuplaDevice/src/supla/pv/afore.h @@ -47,7 +47,7 @@ class Afore : public Supla::Sensor::OnePhaseElectricityMeter { int port; char loginAndPassword[LOGIN_AND_PASSOWORD_MAX_LENGTH]; char buf[80]; - _supla_int64_t totalGeneratedEnergy; + unsigned _supla_int64_t totalGeneratedEnergy; _supla_int_t currentPower; int bytesCounter; int retryCounter; diff --git a/lib/SuplaDevice/src/supla/pv/fronius.cpp b/lib/SuplaDevice/src/supla/pv/fronius.cpp index f1beb2dd..3e2200b4 100644 --- a/lib/SuplaDevice/src/supla/pv/fronius.cpp +++ b/lib/SuplaDevice/src/supla/pv/fronius.cpp @@ -24,18 +24,19 @@ enum ParametersToRead { NONE, TOTAL_ENERGY, FAC, IAC, PAC, UAC }; Fronius::Fronius(IPAddress ip, int port, int deviceId) : ip(ip), port(port), - dataIsReady(false), + buf(), totalGeneratedEnergy(0), currentPower(0), currentCurrent(0), currentFreq(0), currentVoltage(0), - startCharFound(false), bytesCounter(0), - dataFetchInProgress(false), retryCounter(0), valueToFetch(NONE), - deviceId(deviceId) { + deviceId(deviceId), + startCharFound(false), + dataIsReady(false), + dataFetchInProgress(false) { } void Fronius::iterateAlways() { diff --git a/lib/SuplaDevice/src/supla/pv/fronius.h b/lib/SuplaDevice/src/supla/pv/fronius.h index b7278c40..1f761732 100644 --- a/lib/SuplaDevice/src/supla/pv/fronius.h +++ b/lib/SuplaDevice/src/supla/pv/fronius.h @@ -44,11 +44,11 @@ class Fronius : public Supla::Sensor::OnePhaseElectricityMeter { IPAddress ip; int port; char buf[80]; - _supla_int64_t totalGeneratedEnergy; + unsigned _supla_int64_t totalGeneratedEnergy; _supla_int_t currentPower; - _supla_int16_t currentCurrent; - _supla_int16_t currentFreq; - _supla_int16_t currentVoltage; + unsigned _supla_int16_t currentCurrent; + unsigned _supla_int16_t currentFreq; + unsigned _supla_int16_t currentVoltage; int bytesCounter; int retryCounter; int valueToFetch; diff --git a/lib/SuplaDevice/src/supla/sensor/DS18B20.h b/lib/SuplaDevice/src/supla/sensor/DS18B20.h index 5a547942..7b27d211 100644 --- a/lib/SuplaDevice/src/supla/sensor/DS18B20.h +++ b/lib/SuplaDevice/src/supla/sensor/DS18B20.h @@ -30,7 +30,7 @@ namespace Sensor { class OneWireBus { public: OneWireBus(uint8_t pinNumber) - : oneWire(pinNumber), pin(pinNumber), nextBus(nullptr), lastReadTime(0) { + : pin(pinNumber), nextBus(nullptr), lastReadTime(0), oneWire(pinNumber) { supla_log(LOG_DEBUG, "Initializing OneWire bus at pin %d", pinNumber); sensors.setOneWire(&oneWire); sensors.begin(); @@ -144,40 +144,40 @@ class DS18B20 : public Thermometer { myBus->sensors.requestTemperatures(); myBus->lastReadTime = millis(); } - if (myBus->lastReadTime + 5000 < millis() && (lastReadTime != myBus->lastReadTime)) { + if (myBus->lastReadTime + 5000 < millis() && + (lastReadTime != myBus->lastReadTime)) { channel.setNewValue(getValue()); lastReadTime = myBus->lastReadTime; } } double getValue() { - double value = TEMPERATURE_NOT_AVAILABLE; - if (address[0] == 0) { - value = myBus->sensors.getTempCByIndex(0); - } else { - value = myBus->sensors.getTempC(address); - } + double value = TEMPERATURE_NOT_AVAILABLE; + if (address[0] == 0) { + value = myBus->sensors.getTempCByIndex(0); + } else { + value = myBus->sensors.getTempC(address); + } - if (value == DEVICE_DISCONNECTED_C || value == 85.0) { - value = TEMPERATURE_NOT_AVAILABLE; - } + if (value == DEVICE_DISCONNECTED_C || value == 85.0) { + value = TEMPERATURE_NOT_AVAILABLE; + } - if (value == TEMPERATURE_NOT_AVAILABLE) { - retryCounter++; - if (retryCounter > 3) { - retryCounter = 0; - } else { - value = lastValidValue; - } - } else { + if (value == TEMPERATURE_NOT_AVAILABLE) { + retryCounter++; + if (retryCounter > 3) { retryCounter = 0; + } else { + value = lastValidValue; } - lastValidValue = value; + } else { + retryCounter = 0; + } + lastValidValue = value; - return value; + return value; } - void onInit() { channel.setNewValue(getValue()); } diff --git a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h index 2fab6b61..3180e7e3 100644 --- a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h +++ b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h @@ -81,7 +81,7 @@ class ElectricityMeter : public Element { } // energy in 0.00001 kWh - void setFwdActEnergy(char phase, _supla_int64_t energy) { + void setFwdActEnergy(int phase, unsigned _supla_int64_t energy) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.total_forward_active_energy[phase] != energy) { valueChanged = true; @@ -92,7 +92,7 @@ class ElectricityMeter : public Element { } // energy in 0.00001 kWh - void setRvrActEnergy(char phase, _supla_int64_t energy) { + void setRvrActEnergy(int phase, unsigned _supla_int64_t energy) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.total_reverse_active_energy[phase] != energy) { valueChanged = true; @@ -103,7 +103,7 @@ class ElectricityMeter : public Element { } // energy in 0.00001 kWh - void setFwdReactEnergy(char phase, _supla_int64_t energy) { + void setFwdReactEnergy(int phase, unsigned _supla_int64_t energy) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.total_forward_reactive_energy[phase] != energy) { valueChanged = true; @@ -114,7 +114,7 @@ class ElectricityMeter : public Element { } // energy in 0.00001 kWh - void setRvrReactEnergy(char phase, _supla_int64_t energy) { + void setRvrReactEnergy(int phase, unsigned _supla_int64_t energy) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.total_reverse_reactive_energy[phase] != energy) { valueChanged = true; @@ -125,7 +125,7 @@ class ElectricityMeter : public Element { } // voltage in 0.01 V - void setVoltage(char phase, _supla_int16_t voltage) { + void setVoltage(int phase, unsigned _supla_int16_t voltage) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].voltage[phase] != voltage) { valueChanged = true; @@ -136,7 +136,7 @@ class ElectricityMeter : public Element { } // current in 0.001 A - void setCurrent(char phase, _supla_int_t current) { + void setCurrent(int phase, unsigned _supla_int_t current) { if (phase >= 0 && phase < MAX_PHASES) { if (rawCurrent[phase] != current) { valueChanged = true; @@ -147,7 +147,7 @@ class ElectricityMeter : public Element { } // Frequency in 0.01 Hz - void setFreq(_supla_int16_t freq) { + void setFreq(unsigned _supla_int16_t freq) { if (emValue.m[0].freq != freq) { valueChanged = true; } @@ -156,7 +156,7 @@ class ElectricityMeter : public Element { } // power in 0.00001 kW - void setPowerActive(char phase, _supla_int_t power) { + void setPowerActive(int phase, _supla_int_t power) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].power_active[phase] != power) { valueChanged = true; @@ -167,7 +167,7 @@ class ElectricityMeter : public Element { } // power in 0.00001 kvar - void setPowerReactive(char phase, _supla_int_t power) { + void setPowerReactive(int phase, _supla_int_t power) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].power_reactive[phase] != power) { valueChanged = true; @@ -178,7 +178,7 @@ class ElectricityMeter : public Element { } // power in 0.00001 kVA - void setPowerApparent(char phase, _supla_int_t power) { + void setPowerApparent(int phase, _supla_int_t power) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].power_apparent[phase] != power) { valueChanged = true; @@ -189,7 +189,7 @@ class ElectricityMeter : public Element { } // power in 0.001 - void setPowerFactor(char phase, _supla_int_t powerFactor) { + void setPowerFactor(int phase, _supla_int_t powerFactor) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].power_factor[phase] != powerFactor) { valueChanged = true; @@ -200,7 +200,7 @@ class ElectricityMeter : public Element { } // phase angle in 0.1 degree - void setPhaseAngle(char phase, _supla_int_t phaseAngle) { + void setPhaseAngle(int phase, _supla_int_t phaseAngle) { if (phase >= 0 && phase < MAX_PHASES) { if (emValue.m[0].phase_angle[phase] != phaseAngle) { valueChanged = true; @@ -252,7 +252,7 @@ class ElectricityMeter : public Element { } TElectricityMeter_ExtendedValue_V2 emValue; ChannelExtended extChannel; - _supla_int_t rawCurrent[MAX_PHASES]; + unsigned _supla_int_t rawCurrent[MAX_PHASES]; bool valueChanged; bool currentMeasurementAvailable; unsigned long lastReadTime; diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp index 3ba3e912..d710eea3 100644 --- a/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp @@ -29,11 +29,11 @@ ImpulseCounter::ImpulseCounter(int _impulsePin, bool _inputPullup, unsigned int _debounceDelay) : impulsePin(_impulsePin), + lastImpulseMillis(0), debounceDelay(_debounceDelay), detectLowToHigh(_detectLowToHigh), - lastImpulseMillis(0), - counter(0), - inputPullup(_inputPullup) { + inputPullup(_inputPullup), + counter(0) { channel.setType(SUPLA_CHANNELTYPE_IMPULSE_COUNTER); prevState = (detectLowToHigh == true ? LOW : HIGH); @@ -104,7 +104,8 @@ Supla::Channel *ImpulseCounter::getChannel() { return &channel; } -void ImpulseCounter::runAction(int trigger, int action) { +void ImpulseCounter::runAction(int event, int action) { + (void)(event); switch (action) { case RESET: { setCounter(0); diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.h b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h index 88da39b3..6d6da313 100644 --- a/lib/SuplaDevice/src/supla/sensor/impulse_counter.h +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h @@ -35,7 +35,7 @@ class ImpulseCounter : public Element, public Triggerable { void onLoadState(); void onSaveState(); void onFastTimer(); - void runAction(int trigger, int action); + void runAction(int event, int action); // Returns value of a counter at given Supla channel _supla_int64_t getCounter(); @@ -59,7 +59,7 @@ class ImpulseCounter : public Element, public Triggerable { // (HIGH to LOW) edge bool inputPullup; - _supla_int64_t counter; // Actual count of impulses + unsigned _supla_int64_t counter; // Actual count of impulses Channel *getChannel(); Channel channel; diff --git a/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp b/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp index 8c383645..c0a66b5f 100644 --- a/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp +++ b/lib/SuplaDevice/src/supla/sensor/virtual_binary.cpp @@ -38,7 +38,8 @@ void VirtualBinary::onInit() { channel.setNewValue(getValue()); } -void VirtualBinary::runAction(int trigger, int action) { +void VirtualBinary::runAction(int event, int action) { + (void)(event); switch (action) { case SET: { state = true; diff --git a/lib/SuplaDevice/src/supla/sensor/virtual_binary.h b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h index 84e04600..5c30c5f6 100644 --- a/lib/SuplaDevice/src/supla/sensor/virtual_binary.h +++ b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h @@ -32,7 +32,7 @@ class VirtualBinary : public Element, public Triggerable { bool getValue(); void iterateAlways(); void onInit(); - void runAction(int trigger, int action); + void runAction(int event, int action); protected: Channel *getChannel(); diff --git a/lib/SuplaDevice/src/supla/storage/eeprom.cpp b/lib/SuplaDevice/src/supla/storage/eeprom.cpp index c48e81a3..79cd9f7b 100644 --- a/lib/SuplaDevice/src/supla/storage/eeprom.cpp +++ b/lib/SuplaDevice/src/supla/storage/eeprom.cpp @@ -27,7 +27,7 @@ using namespace Supla; Eeprom::Eeprom(unsigned int storageStartingOffset) : Storage(storageStartingOffset), dataChanged(false) { - setStateSavePeriod(SUPLA_EEPROM_WRITING_PERIOD); + setStateSavePeriod((unsigned long)SUPLA_EEPROM_WRITING_PERIOD); } bool Eeprom::init() { @@ -41,7 +41,7 @@ bool Eeprom::init() { return Storage::init(); } -int Eeprom::readStorage(int offset, unsigned char *buf, int size, bool logs) { +int Eeprom::readStorage(unsigned int offset, unsigned char *buf, int size, bool logs) { if (logs) { Serial.print(F("readStorage: ")); Serial.print(size); @@ -60,7 +60,7 @@ int Eeprom::readStorage(int offset, unsigned char *buf, int size, bool logs) { return size; } -int Eeprom::writeStorage(int offset, const unsigned char *buf, int size) { +int Eeprom::writeStorage(unsigned int offset, const unsigned char *buf, int size) { dataChanged = true; for (int i = 0; i < size; i++) { EEPROM.write(offset + i, buf[i]); diff --git a/lib/SuplaDevice/src/supla/storage/eeprom.h b/lib/SuplaDevice/src/supla/storage/eeprom.h index d7e028a7..b9699ada 100644 --- a/lib/SuplaDevice/src/supla/storage/eeprom.h +++ b/lib/SuplaDevice/src/supla/storage/eeprom.h @@ -28,8 +28,8 @@ class Eeprom : public Storage { void commit(); protected: - int readStorage(int, unsigned char *, int, bool); - int writeStorage(int, const unsigned char *, int); + int readStorage(unsigned int, unsigned char *, int, bool); + int writeStorage(unsigned int, const unsigned char *, int); bool dataChanged; }; diff --git a/lib/SuplaDevice/src/supla/storage/fram_spi.h b/lib/SuplaDevice/src/supla/storage/fram_spi.h index 5f479202..d35e02a3 100644 --- a/lib/SuplaDevice/src/supla/storage/fram_spi.h +++ b/lib/SuplaDevice/src/supla/storage/fram_spi.h @@ -61,7 +61,7 @@ class FramSpi : public Storage { void commit(){}; protected: - int readStorage(int offset, unsigned char *buf, int size, bool logs) { + int readStorage(unsigned int offset, unsigned char *buf, int size, bool logs) { if (logs) { Serial.print(F("readStorage: ")); Serial.print(size); @@ -80,7 +80,7 @@ class FramSpi : public Storage { return size; } - int writeStorage(int offset, const unsigned char *buf, int size) { + int writeStorage(unsigned int offset, const unsigned char *buf, int size) { fram.writeEnable(true); fram.write(offset, const_cast(buf), size); fram.writeEnable(false); diff --git a/lib/SuplaDevice/src/supla/storage/storage.cpp b/lib/SuplaDevice/src/supla/storage/storage.cpp index 00f1022f..52dfaf8e 100644 --- a/lib/SuplaDevice/src/supla/storage/storage.cpp +++ b/lib/SuplaDevice/src/supla/storage/storage.cpp @@ -85,15 +85,18 @@ bool Storage::SaveStateAllowed(unsigned long ms) { Storage::Storage(unsigned int storageStartingOffset) : storageStartingOffset(storageStartingOffset), - currentStateOffset(0), deviceConfigOffset(0), elementConfigOffset(0), elementStateOffset(0), + deviceConfigSize(0), + elementConfigSize(0), + elementStateSize(0), + currentStateOffset(0), newSectionSize(0), sectionsCount(0), dryRun(false), - lastWriteTimestamp(0), - saveStatePeriod(1000) { + saveStatePeriod(1000), + lastWriteTimestamp(0) { instance = this; } @@ -200,7 +203,7 @@ bool Storage::finalizeSaveState() { bool Storage::init() { Serial.println(F("Storage initialization")); - int currentOffset = storageStartingOffset; + unsigned int currentOffset = storageStartingOffset; Preamble preamble; currentOffset += readStorage(currentOffset, (unsigned char *)&preamble, sizeof(preamble)); @@ -236,7 +239,7 @@ bool Storage::init() { Serial.print(F("Reading section: ")); Serial.println(i); SectionPreamble section; - int sectionOffset = currentOffset; + unsigned int sectionOffset = currentOffset; currentOffset += readStorage(currentOffset, (unsigned char *)§ion, sizeof(section)); @@ -286,7 +289,7 @@ bool Storage::loadElementConfig() { return true; } -int Storage::updateStorage(int offset, const unsigned char *buf, int size) { +int Storage::updateStorage(unsigned int offset, const unsigned char *buf, int size) { if (offset < storageStartingOffset) { return 0; } diff --git a/lib/SuplaDevice/src/supla/storage/storage.h b/lib/SuplaDevice/src/supla/storage/storage.h index 6a715ead..66284302 100644 --- a/lib/SuplaDevice/src/supla/storage/storage.h +++ b/lib/SuplaDevice/src/supla/storage/storage.h @@ -55,9 +55,9 @@ class Storage { virtual void commit() = 0; protected: - virtual int readStorage(int, unsigned char *, int, bool = true) = 0; - virtual int writeStorage(int, const unsigned char *, int) = 0; - virtual int updateStorage(int, const unsigned char *, int); + virtual int readStorage(unsigned int, unsigned char *, int, bool = true) = 0; + virtual int writeStorage(unsigned int, const unsigned char *, int) = 0; + virtual int updateStorage(unsigned int, const unsigned char *, int); unsigned int storageStartingOffset; unsigned int deviceConfigOffset; diff --git a/lib/SuplaDevice/src/supla/timer.cpp b/lib/SuplaDevice/src/supla/timer.cpp index e0609777..b77eb54a 100644 --- a/lib/SuplaDevice/src/supla/timer.cpp +++ b/lib/SuplaDevice/src/supla/timer.cpp @@ -29,10 +29,12 @@ ETSTimer supla_esp_timer; ETSTimer supla_esp_fastTimer; void esp_timer_cb(void *timer_arg) { + (void)(timer_arg); SuplaDevice.onTimer(); } void esp_fastTimer_cb(void *timer_arg) { + (void)(timer_arg); SuplaDevice.onFastTimer(); } #elif defined(ARDUINO_ARCH_ESP32) diff --git a/lib/SuplaDevice/src/supla/tools.cpp b/lib/SuplaDevice/src/supla/tools.cpp index 28f34f74..a4c9038c 100644 --- a/lib/SuplaDevice/src/supla/tools.cpp +++ b/lib/SuplaDevice/src/supla/tools.cpp @@ -19,6 +19,7 @@ #include "tools.h" void float2DoublePacked(float number, uint8_t *bar, int byteOrder) { + (void)(byteOrder); _FLOATCONV fl; fl.f = number; _DBLCONV dbl; diff --git a/lib/SuplaDevice/src/supla/uptime.cpp b/lib/SuplaDevice/src/supla/uptime.cpp index 2b447084..09b624b1 100644 --- a/lib/SuplaDevice/src/supla/uptime.cpp +++ b/lib/SuplaDevice/src/supla/uptime.cpp @@ -19,10 +19,10 @@ namespace Supla { Uptime::Uptime() - : deviceUptime(0), + : lastMillis(0), + deviceUptime(0), connectionUptime(0), lastConnectionResetCause(SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN), - lastMillis(0), acceptConnectionLostCause(false) { } From ca1994b15f824d45239aacaf9428dfe8587685c5 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 09:33:57 +0100 Subject: [PATCH 018/233] placeholder --- src/Markup.cpp | 28 +++++++++++++++++++++++----- src/Markup.h | 3 ++- src/SuplaWebServer.cpp | 5 +++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index 634a898f..4ffa0bfc 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -15,26 +15,33 @@ void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, + const String& placeholder, int minlength, int maxlength, bool required, bool readonly, bool password) { - html += F(" "); } +void addTextBox(String& html, + const String& input_id, + const String& value_key, + const String& name, + int minlength, + int maxlength, + bool required, + bool readonly) { + return addTextBox(html, input_id, value_key, name, "", minlength, maxlength, required, readonly, false); +} + void addTextBoxPassword( String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required) { - return addTextBox(html, input_id, value_key, name, minlength, maxlength, required, false, true); + return addTextBox(html, input_id, value_key, name, "", minlength, maxlength, required, false, true); } \ No newline at end of file diff --git a/src/Markup.h b/src/Markup.h index a10378b8..45947583 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -5,7 +5,8 @@ void addFormHeader(String& html, const String& name); void addFormHeaderEnd(String& html); -void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required, bool readonly = false, bool password = false); +void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, const String& placeholder, int minlength, int maxlength, bool required, bool readonly = false, bool password = false); +void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required, bool readonly = false); void addTextBoxPassword(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required); #endif // Markup_h diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 24b8324f..90922969 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -163,8 +163,8 @@ String SuplaWebServer::supla_webpage_start(int save) { addFormHeaderEnd(content); addFormHeader(content, S_SETTING_SUPLA); - addTextBox(content, INPUT_SERVER, KEY_SUPLA_SERVER, S_SUPLA_SERVER, 0, MAX_SUPLA_SERVER, true); - addTextBox(content, INPUT_EMAIL, KEY_SUPLA_EMAIL, S_SUPLA_EMAIL, 0, MAX_EMAIL, true); + addTextBox(content, INPUT_SERVER, KEY_SUPLA_SERVER, S_SUPLA_SERVER, DEFAULT_SERVER, 0, MAX_SUPLA_SERVER, true); + addTextBox(content, INPUT_EMAIL, KEY_SUPLA_EMAIL, S_SUPLA_EMAIL, DEFAULT_EMAIL, 0, MAX_EMAIL, true); addFormHeaderEnd(content); addFormHeader(content, S_SETTING_ADMIN); @@ -172,6 +172,7 @@ String SuplaWebServer::supla_webpage_start(int save) { addTextBoxPassword(content, INPUT_MODUL_PASS, KEY_LOGIN_PASS, S_LOGIN_PASS, MIN_PASSWORD, MAX_MPASSWORD, true); addFormHeaderEnd(content); + #ifdef SUPLA_ROLLERSHUTTER uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); if (maxrollershutter >= 2) { From c6f991abdac7d7260b7ae4d6d4c7a42c41109c40 Mon Sep 17 00:00:00 2001 From: Espablo Date: Wed, 18 Nov 2020 11:52:29 +0100 Subject: [PATCH 019/233] czyszczenie platformio.ini --- platformio.ini | 54 ++++++++++---------------------------------------- 1 file changed, 11 insertions(+), 43 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4ee448a1..40e38266 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,16 +9,16 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = - GUI_Generic_1M -; GUI_Generic_1M-en -; GUI_Generic_4M -; GUI_Generic_minimal -; GUI_Generic_lite -; GUI_Generic_sensors -; GUI_Generic_DEBUG -; GUI_Generic_blank -;lib_extra_dirs = ~/Documents/Arduino/libraries +; default_envs = +; GUI_Generic_1M +; GUI_Generic_1M-en +; GUI_Generic_4M +; GUI_Generic_minimal +; GUI_Generic_lite +; GUI_Generic_sensors +; GUI_Generic_DEBUG +; GUI_Generic_blank +; lib_extra_dirs = ~/Documents/Arduino/libraries [common] @@ -136,36 +136,4 @@ monitor_speed = ${common.monitor_speed} lib_deps = ${common.lib_deps} extra_scripts = ${common.extra_scripts} build_flags = ${common.build_flags} - -;[env:esp8285] -;platform = espressif8266 -;board = esp8285 -;framework = arduino -;upload_resetmethod = nodemcu -;monitor_speed = 74880 -;lib_deps = -; milesburton/DallasTemperature@^3.9.1 -; adafruit/DHT sensor library@^1.4.0 -; paulstoffregen/OneWire@^2.3.5 -; adafruit/Adafruit BME280 Library@^2.1.1 -; datacute/DoubleResetDetector@^1.0.3 -; closedcube/ClosedCube SHT31D@^1.5.1 -; adafruit/Adafruit Si7021 Library@^1.3.0 - -; [env:esp12e] -; platform = espressif8266 -; board = esp12e -; framework = arduino -; ; board_build.filesystem = littlefs -; ; board_build.flash_mode = dout -; upload_resetmethod = nodemcu -; ; upload_flags = ef -; monitor_speed = 74880 -; lib_deps = -; milesburton/DallasTemperature@^3.9.1 -; adafruit/DHT sensor library@^1.4.0 -; paulstoffregen/OneWire@^2.3.5 -; adafruit/Adafruit BME280 Library@^2.1.1 -; datacute/DoubleResetDetector@^1.0.3 -; closedcube/ClosedCube SHT31D@^1.5.1 -; adafruit/Adafruit Si7021 Library@^1.3.0 + \ No newline at end of file From 16fb61789d47161d3b889367ac0a9f05cf4f6ebd Mon Sep 17 00:00:00 2001 From: Espablo Date: Wed, 18 Nov 2020 11:54:09 +0100 Subject: [PATCH 020/233] zmiana nazwy z set na add --- src/SuplaTemplateBoard.cpp | 206 ++++++++++++++++++------------------- src/SuplaTemplateBoard.h | 10 +- 2 files changed, 108 insertions(+), 108 deletions(-) diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 928acf35..72da3f0e 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -2,7 +2,7 @@ #include "SuplaWebPageRelay.h" #include -void setButton(uint8_t gpio, uint8_t event) { +void addButton(uint8_t gpio, uint8_t event) { uint8_t nr = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); String test; nr = nr + 1; @@ -11,7 +11,7 @@ void setButton(uint8_t gpio, uint8_t event) { ConfigESP->setGpio(gpio, nr, FUNCTION_BUTTON, event); } -void setRelay(uint8_t gpio, uint8_t level) { +void addRelay(uint8_t gpio, uint8_t level) { uint8_t nr = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); String test; nr = nr + 1; @@ -20,7 +20,7 @@ void setRelay(uint8_t gpio, uint8_t level) { ConfigESP->setGpio(gpio, nr, FUNCTION_RELAY, level, MEMORY_RELAY_RESTORE); } -void setLimitSwitch(uint8_t gpio) { +void addLimitSwitch(uint8_t gpio) { uint8_t nr = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); String test; nr = nr + 1; @@ -30,11 +30,11 @@ void setLimitSwitch(uint8_t gpio) { ConfigESP->setGpio(4, 1, FUNCTION_LIMIT_SWITCH, 0); } -void setLedCFG(uint8_t gpio, uint8_t level) { +void addLedCFG(uint8_t gpio, uint8_t level) { ConfigESP->setGpio(gpio, FUNCTION_CFG_LED, level); } -void setButtonCFG(uint8_t gpio) { +void addButtonCFG(uint8_t gpio) { ConfigESP->setGpio(gpio, FUNCTION_CFG_BUTTON); } @@ -42,143 +42,143 @@ void chooseTemplateBoard(uint8_t board) { ConfigManager->set(KEY_MAX_BUTTON, "0"); ConfigManager->set(KEY_MAX_RELAY, "0"); ConfigManager->set(KEY_MAX_LIMIT_SWITCH, "0"); - + switch (board) { case BOARD_ELECTRODRAGON: - setLedCFG(16); - setButtonCFG(0); - setButton(0); - setButton(2); - setRelay(12); - setRelay(13); + addLedCFG(16); + addButtonCFG(0); + addButton(0); + addButton(2); + addRelay(12); + addRelay(13); break; case BOARD_INCAN3: - setLedCFG(2, LOW); - setButtonCFG(0); - setButton(14, Supla::ON_CHANGE); - setButton(12, Supla::ON_CHANGE); - setRelay(5); - setRelay(13); - setLimitSwitch(4); - setLimitSwitch(16); + addLedCFG(2, LOW); + addButtonCFG(0); + addButton(14, Supla::ON_CHANGE); + addButton(12, Supla::ON_CHANGE); + addRelay(5); + addRelay(13); + addLimitSwitch(4); + addLimitSwitch(16); break; case BOARD_INCAN4: - setLedCFG(12); - setButtonCFG(0); - setButton(2, Supla::ON_CHANGE); - setButton(10, Supla::ON_CHANGE); - setRelay(4); - setRelay(14); - setLimitSwitch(4); - setLimitSwitch(16); + addLedCFG(12); + addButtonCFG(0); + addButton(2, Supla::ON_CHANGE); + addButton(10, Supla::ON_CHANGE); + addRelay(4); + addRelay(14); + addLimitSwitch(4); + addLimitSwitch(16); break; case BOARD_MELINK: - setLedCFG(12); - setButtonCFG(5); - setButton(5); - setRelay(4); + addLedCFG(12); + addButtonCFG(5); + addButton(5); + addRelay(4); break; case BOARD_NEO_COOLCAM: - setLedCFG(4); - setButtonCFG(13); - setButton(13); - setRelay(12); + addLedCFG(4); + addButtonCFG(13); + addButton(13); + addRelay(12); break; case BOARD_SHELLY1: - setButtonCFG(5); - setButton(5); - setRelay(4); + addButtonCFG(5); + addButton(5); + addRelay(4); break; case BOARD_SHELLY2: - setLedCFG(16); - setButtonCFG(12); - setButton(12); - setButton(14); - setRelay(4); - setRelay(5); + addLedCFG(16); + addButtonCFG(12); + addButton(12); + addButton(14); + addRelay(4); + addRelay(5); break; case BOARD_SONOFF_BASIC: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setRelay(12); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addRelay(12); break; case BOARD_SONOFF_DUAL_R2: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setButton(9); - setRelay(12); - setRelay(5); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addButton(9); + addRelay(12); + addRelay(5); break; case BOARD_SONOFF_S2X: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setRelay(12); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addRelay(12); ConfigESP->setGpio(14, FUNCTION_SI7021_SONOFF); break; case BOARD_SONOFF_SV: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setRelay(12); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addRelay(12); break; case BOARD_SONOFF_TH: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setRelay(12); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addRelay(12); ConfigESP->setGpio(14, FUNCTION_SI7021_SONOFF); break; case BOARD_SONOFF_TOUCH: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setRelay(12); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addRelay(12); break; case BOARD_SONOFF_TOUCH_2CH: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setButton(9); - setRelay(12); - setRelay(5); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addButton(9); + addRelay(12); + addRelay(5); break; case BOARD_SONOFF_TOUCH_3CH: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setButton(9); - setButton(10); - setRelay(12); - setRelay(5); - setRelay(4); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addButton(9); + addButton(10); + addRelay(12); + addRelay(5); + addRelay(4); break; case BOARD_SONOFF_4CH: - setLedCFG(13); - setButtonCFG(0); - setButton(0); - setButton(9); - setButton(10); - setButton(14); - setRelay(12); - setRelay(5); - setRelay(4); - setRelay(15); + addLedCFG(13); + addButtonCFG(0); + addButton(0); + addButton(9); + addButton(10); + addButton(14); + addRelay(12); + addRelay(5); + addRelay(4); + addRelay(15); break; case BOARD_YUNSHAN: - setLedCFG(2, LOW); - setButtonCFG(0); - setButton(3, Supla::ON_CHANGE); - setRelay(4); + addLedCFG(2, LOW); + addButtonCFG(0); + addButton(3, Supla::ON_CHANGE); + addRelay(4); break; case BOARD_YUNTONG_SMART: - setLedCFG(15); - setButtonCFG(12); - setButton(12, Supla::ON_CHANGE); - setRelay(4); + addLedCFG(15); + addButtonCFG(12); + addButton(12, Supla::ON_CHANGE); + addRelay(4); break; } } diff --git a/src/SuplaTemplateBoard.h b/src/SuplaTemplateBoard.h index 08f06765..1db9d859 100644 --- a/src/SuplaTemplateBoard.h +++ b/src/SuplaTemplateBoard.h @@ -4,11 +4,11 @@ #include #include "SuplaWebPageRelay.h" -void setButton(uint8_t gpio, uint8_t event = 0); -void setRelay(uint8_t gpio, uint8_t level = HIGH); -void setLimitSwitch(uint8_t gpio); -void setLedCFG(uint8_t gpio, uint8_t level = HIGH); -void setButtonCFG(uint8_t gpio); +void addButton(uint8_t gpio, uint8_t event = 0); +void addRelay(uint8_t gpio, uint8_t level = HIGH); +void addLimitSwitch(uint8_t gpio); +void addLedCFG(uint8_t gpio, uint8_t level = HIGH); +void addButtonCFG(uint8_t gpio); enum _board { From bd25b9b0f3e77af3773c6667b4afece8e902026e Mon Sep 17 00:00:00 2001 From: Espablo Date: Wed, 18 Nov 2020 11:55:29 +0100 Subject: [PATCH 021/233] =?UTF-8?q?t=C5=82umaczenie=20c.d.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaTemplateBoard.h | 3 ++- src/language/en.h | 3 +++ src/language/pl.h | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/SuplaTemplateBoard.h b/src/SuplaTemplateBoard.h index 1db9d859..f53b83d1 100644 --- a/src/SuplaTemplateBoard.h +++ b/src/SuplaTemplateBoard.h @@ -3,6 +3,7 @@ #include #include "SuplaWebPageRelay.h" +#include "GUIGenericCommon.h" void addButton(uint8_t gpio, uint8_t event = 0); void addRelay(uint8_t gpio, uint8_t level = HIGH); @@ -33,7 +34,7 @@ enum _board MAX_MODULE }; -const char BOARD_NULL[] PROGMEM = "BRAK"; +const char BOARD_NULL[] PROGMEM = S_ABSENT; const char ELECTRODRAGON[] PROGMEM = "ElectroDragon"; const char INCAN3[] PROGMEM = "inCan3"; const char INCAN4[] PROGMEM = "inCan4"; diff --git a/src/language/en.h b/src/language/en.h index d3497892..3bf0ffb9 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -87,4 +87,7 @@ #define S_REACTION_ON_RELEASE "ON RELEASE" #define S_REACTION_ON_CHANGE "ON CHANGE" +//#### SuplaTemplateBoard.h #### +#define S_ABSENT "ABSENT" + #endif // _LANGUAGE_EN_S_H_ diff --git a/src/language/pl.h b/src/language/pl.h index cada9693..eeea48a5 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -87,4 +87,7 @@ #define S_REACTION_ON_RELEASE "ZWOLNIENIE" #define S_REACTION_ON_CHANGE "ZMIANA STANU" +//#### SuplaTemplateBoard.h #### +#define S_ABSENT "BRAK" + #endif // _LANGUAGE_PL_S_H_ From 32cbe94da493fc4874453be5ef5a30e572dfb406 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 12:38:47 +0100 Subject: [PATCH 022/233] SHT30 -> SHT3x --- src/GUI-Generic.ino | 14 +++++++------- src/GUI-Generic_Config.h | 6 ++---- src/SuplaCommonPROGMEM.cpp | 6 +++--- src/SuplaCommonPROGMEM.h | 4 ++-- src/SuplaWebPageSensor.cpp | 24 ++++++++++++------------ src/SuplaWebPageSensor.h | 16 ++++++++-------- src/SuplaWebServer.cpp | 2 +- 7 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 08df3cc5..d2f0b39c 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -30,7 +30,7 @@ #include #include "SuplaWebPageSensor.h" #endif -#ifdef SUPLA_SHT30 +#ifdef SUPLA_SHT3x #include #endif #ifdef SUPLA_SI7021 @@ -143,7 +143,7 @@ void setup() { } #endif -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); @@ -163,15 +163,15 @@ void setup() { } #endif -#ifdef SUPLA_SHT30 - switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT30).toInt()) { - case SHT30_ADDRESS_0X44: +#ifdef SUPLA_SHT3x + switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT3x).toInt()) { + case SHT3x_ADDRESS_0X44: new Supla::Sensor::SHT3x(0x44); break; - case SHT30_ADDRESS_0X45: + case SHT3x_ADDRESS_0X45: new Supla::Sensor::SHT3x(0x45); break; - case SHT30_ADDRESS_0X44_AND_0X45: + case SHT3x_ADDRESS_0X44_AND_0X45: new Supla::Sensor::SHT3x(0x44); new Supla::Sensor::SHT3x(0x45); break; diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index 72cdb4ac..0cb1b168 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -30,7 +30,7 @@ // i2c #define SUPLA_BME280 -#define SUPLA_SHT30 +#define SUPLA_SHT3x #define SUPLA_SI7021 // #define SUPLA_HTU21D // 0x40 NOT SUPPORTED // #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED @@ -81,7 +81,7 @@ // i2c #define SUPLA_BME280 -#define SUPLA_SHT30 +#define SUPLA_SHT3x #define SUPLA_SI7021 // #define SUPLA_HTU21D // 0x40 NOT SUPPORTED // #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED @@ -93,5 +93,3 @@ #endif // GUI_Generic_sensors #endif // GUI-Generic_Config_h - - diff --git a/src/SuplaCommonPROGMEM.cpp b/src/SuplaCommonPROGMEM.cpp index 9e1117db..ef630650 100644 --- a/src/SuplaCommonPROGMEM.cpp +++ b/src/SuplaCommonPROGMEM.cpp @@ -9,8 +9,8 @@ String BME280String(uint8_t adr) { return PGMT(BME280_P[adr]); } -String SHT30String(uint8_t adr) { - return PGMT(SHT30_P[adr]); +String SHT3xString(uint8_t adr) { + return PGMT(SHT3x_P[adr]); } String StateString(uint8_t adr) { @@ -30,5 +30,5 @@ String TriggerString(uint8_t nr) { } String BoardString(uint8_t board) { - return PGMT(BOARD_P[board]); + return PGMT(BOARD_P[board]); } diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 3688831d..5eb0f595 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -80,7 +80,7 @@ const char ADR77[] PROGMEM = "0x77"; const char ADR76_ADR77[] PROGMEM = "0x76 & 0x77"; const char* const BME280_P[] PROGMEM = {OFF, ADR76, ADR77, ADR76_ADR77}; -const char* const SHT30_P[] PROGMEM = {OFF, ADR44, ADR45, ADR44_ADR45}; +const char* const SHT3x_P[] PROGMEM = {OFF, ADR44, ADR45, ADR44_ADR45}; const char* const STATE_P[] PROGMEM = {OFF, ON}; @@ -102,7 +102,7 @@ const char* const CFG_MODE_P[] PROGMEM = {CFG_10_PRESSES, CFG_5SEK_HOLD}; String GIPOString(uint8_t gpio); String BME280String(uint8_t adr); -String SHT30String(uint8_t adr); +String SHT3xString(uint8_t adr); String StateString(uint8_t adr); String LevelString(uint8_t nr); String MemoryString(uint8_t nr); diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index aa85d1b4..6d2de379 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -30,7 +30,7 @@ void SuplaWebPageSensor::createWebPageSensor() { #endif #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) path = PATH_START; path += PATH_I2C; WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2c, this)); @@ -493,7 +493,7 @@ String SuplaWebPageSensor::supla_webpage_1wire(int save) { } #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) void SuplaWebPageSensor::handlei2c() { if (ConfigESP->configModeESP == NORMAL_MODE) { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) @@ -511,7 +511,7 @@ void SuplaWebPageSensor::handlei2cSave() { String key, input; uint8_t nr, current_value, last_value; -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) input = INPUT_SDA_GPIO; key = GPIO; key += WebServer->httpServer.arg(input).toInt(); @@ -567,11 +567,11 @@ void SuplaWebPageSensor::handlei2cSave() { } #endif -#ifdef SUPLA_SHT30 +#ifdef SUPLA_SHT3x key = KEY_ACTIVE_SENSOR; - input = INPUT_SHT30; + input = INPUT_SHT3x; if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { - ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SHT30, WebServer->httpServer.arg(input).toInt()); + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_SHT3x, WebServer->httpServer.arg(input).toInt()); } #endif @@ -646,7 +646,7 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { page += PATH_SAVE_I2C; page += F("'>"); -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT30) +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) page += F("

"); page += S_GPIO_SETTINGS_FOR; page += F(" i2c

"); @@ -687,13 +687,13 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { page += F(""); #endif -#ifdef SUPLA_SHT30 +#ifdef SUPLA_SHT3x page += F(""); #endif diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 95e7f7e9..41f8e933 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -20,7 +20,7 @@ #define INPUT_SCL_GPIO "sclg" #define INPUT_BME280 "bme280" #define INPUT_ALTITUDE_BME280 "abme280" -#define INPUT_SHT30 "sht30" +#define INPUT_SHT3x "SHT3x" #define INPUT_SI7021 "si7021" #define INPUT_SI7021_SONOFF "si7021sonoff" #define INPUT_TRIG_GPIO "trig" @@ -36,7 +36,7 @@ enum _sensorI2C { SENSOR_BME280, - SENSOR_SHT30, + SENSOR_SHT3x, SENSOR_SI7021 }; @@ -54,12 +54,12 @@ enum _bmeAdress }; #endif -#ifdef SUPLA_SHT30 +#ifdef SUPLA_SHT3x enum _shtAdress { - SHT30_ADDRESS_0X44 = 1, - SHT30_ADDRESS_0X45, - SHT30_ADDRESS_0X44_AND_0X45 + SHT3x_ADDRESS_0X44 = 1, + SHT3x_ADDRESS_0X45, + SHT3x_ADDRESS_0X44_AND_0X45 }; #endif @@ -79,7 +79,7 @@ class SuplaWebPageSensor { void showDS18B20(String& content, bool readonly = false); #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) void handlei2c(); void handlei2cSave(); #endif @@ -97,7 +97,7 @@ class SuplaWebPageSensor { #endif #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) String supla_webpage_i2c(int save); #endif #if defined(SUPLA_MAX6675) diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 90922969..063ff460 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -327,7 +327,7 @@ String SuplaWebServer::deviceSettings(int save) { content += F("

"); #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) content += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" HC-SR04

"); - page += F(""); - page += WebServer->selectGPIO(INPUT_TRIG_GPIO, FUNCTION_TRIG); - page += F(""); - page += F(""); - page += WebServer->selectGPIO(INPUT_ECHO_GPIO, FUNCTION_ECHO); - page += F(""); - page += F("
"); -#endif page += F(""); @@ -931,3 +885,116 @@ String SuplaWebPageSensor::supla_webpage_spi(int save) { return page; } #endif + +#if defined(SUPLA_HC_SR04) +void SuplaWebPageSensor::handleOther() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_webpage_other(0)); +} + +void SuplaWebPageSensor::handleOtherSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String key, input; + uint8_t nr, current_value, last_value; + +#ifdef SUPLA_HC_SR04 + input = INPUT_TRIG_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_TRIG) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_TRIG)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_TRIG) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_TRIG)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_TRIG); + } + else { + WebServer->sendContent(supla_webpage_other(6)); + return; + } + } + + input = INPUT_ECHO_GPIO; + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigESP->getGpio(FUNCTION_ECHO) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { + ConfigESP->clearGpio(ConfigESP->getGpio(FUNCTION_ECHO)); + } + if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { + key = GPIO; + key += WebServer->httpServer.arg(input).toInt(); + if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF || + (ConfigESP->getGpio(FUNCTION_ECHO) == WebServer->httpServer.arg(input).toInt() && + ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_ECHO)) { + ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), FUNCTION_ECHO); + } + else { + WebServer->sendContent(supla_webpage_other(6)); + return; + } + } +#endif + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + WebServer->sendContent(supla_webpage_other(1)); + break; + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_other(2)); + break; + } +} + +String SuplaWebPageSensor::supla_webpage_other(int save) { + uint8_t nr, suported, selected; + String page, key; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_OTHER); + page += F("
"); + +#ifdef SUPLA_HC_SR04 + page += F("

"); + page += S_GPIO_SETTINGS_FOR; + page += F(" HC-SR04

"); + page += F(""); + page += WebServer->selectGPIO(INPUT_TRIG_GPIO, FUNCTION_TRIG); + page += F(""); + page += F(""); + page += WebServer->selectGPIO(INPUT_ECHO_GPIO, FUNCTION_ECHO); + page += F(""); + page += F("
"); +#endif + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + page += F("
"); + return page; +} +#endif \ No newline at end of file diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 95e7f7e9..5f2997b0 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -12,6 +12,8 @@ #define PATH_SAVE_I2C "savei2c" #define PATH_SPI "spi" #define PATH_SAVE_SPI "savespi" +#define PATH_OTHER "other" +#define PATH_SAVE_OTHER "saveother" #define INPUT_MULTI_DS_GPIO "mdsg" #define INPUT_DHT11_GPIO "dht11" @@ -79,7 +81,7 @@ class SuplaWebPageSensor { void showDS18B20(String& content, bool readonly = false); #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) void handlei2c(); void handlei2cSave(); #endif @@ -89,6 +91,11 @@ class SuplaWebPageSensor { void handleSpiSave(); #endif +#if defined(SUPLA_HC_SR04) + void handleOther(); + void handleOtherSave(); +#endif + private: #if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) String supla_webpage_1wire(int save); @@ -97,12 +104,17 @@ class SuplaWebPageSensor { #endif #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) String supla_webpage_i2c(int save); #endif + #if defined(SUPLA_MAX6675) String supla_webpage_spi(int save); #endif + +#if defined(SUPLA_HC_SR04) + String supla_webpage_other(int save); +#endif }; extern SuplaWebPageSensor* WebPageSensor; diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 24b8324f..adf8c7e5 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -326,7 +326,7 @@ String SuplaWebServer::deviceSettings(int save) { content += F("

"); #endif -#if defined(SUPLA_BME280) || defined(SUPLA_HC_SR04) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) content += F(""); + content += F("

"); +#endif + #ifdef SUPLA_CONFIG content += F(""); - html += F("

"); + html += F("

"); html += name; html += F("

"); } @@ -13,15 +13,15 @@ void addFormHeaderEnd(String& html) { void addTextBox(String& html, const String& input_id, - const String& value_key, const String& name, + const String& value_key, const String& placeholder, int minlength, int maxlength, bool required, bool readonly, bool password) { - html += F(" 0) { - html += F(" length="); + html += F("' length='"); html += maxlength; + html += F("'"); } if (readonly) { - html += F(" readonly "); + html += F(" readonly"); } if (required) { - html += F(" required "); + html += F(" required"); } - html += F("> "); } From 9b7ea30feef7e5f6dc28d2d528fcf1abff275570 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 19:56:31 +0100 Subject: [PATCH 025/233] =?UTF-8?q?nowy=20spos=C3=B3b=20dodawania=20html?= =?UTF-8?q?=20-=20sensory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Markup.cpp | 100 +++++++++++++++++--- src/Markup.h | 30 +++++- src/SuplaCommonPROGMEM.cpp | 12 --- src/SuplaCommonPROGMEM.h | 5 +- src/SuplaWebPageConfig.cpp | 5 +- src/SuplaWebPageControl.cpp | 5 +- src/SuplaWebPageRelay.cpp | 3 +- src/SuplaWebPageSensor.cpp | 180 ++++++++---------------------------- src/SuplaWebServer.cpp | 46 ++------- src/SuplaWebServer.h | 1 - 10 files changed, 169 insertions(+), 218 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index 360cb848..cfcb0cc2 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -1,8 +1,9 @@ #include "Markup.h" +#include "SuplaCommonPROGMEM.h" void addFormHeader(String& html, const String& name) { html += F("
"); - html += F("

"); + html += F("

"); html += name; html += F("

"); } @@ -59,18 +60,91 @@ void addTextBox(String& html, html += F(" "); } -void addTextBox(String& html, - const String& input_id, - const String& value_key, - const String& name, - int minlength, - int maxlength, - bool required, - bool readonly) { - return addTextBox(html, input_id, value_key, name, "", minlength, maxlength, required, readonly, false); +void addTextBox( + String& html, const String& input_id, const String& name, const String& value_key, int minlength, int maxlength, bool required, bool readonly) { + return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, readonly, false); } void addTextBoxPassword( - String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required) { - return addTextBox(html, input_id, value_key, name, "", minlength, maxlength, required, false, true); -} \ No newline at end of file + String& html, const String& input_id, const String& name, const String& value_key, int minlength, int maxlength, bool required) { + return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, false, true); +} + +void addNumberBox(String& html, const String& input_id, const String& name, const String& value_key, uint16_t max) { + html += F(""); +} + +void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { + html += F(""); + html += addListGPIOSelect(input_id.c_str(), function, nr); + html += F(""); +} + +void addListBox(String& html, const String& input_id, const String& name, const char* const* array_P, uint8_t size, uint8_t selected) { + html += F(""); +} + + String addListGPIOSelect(const char* input, uint8_t function, uint8_t nr) { + String page = ""; + page += F(""); + return page; + } \ No newline at end of file diff --git a/src/Markup.h b/src/Markup.h index 45947583..fe90cae7 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -5,10 +5,32 @@ void addFormHeader(String& html, const String& name); void addFormHeaderEnd(String& html); -void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, const String& placeholder, int minlength, int maxlength, bool required, bool readonly = false, bool password = false); -void addTextBox(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required, bool readonly = false); -void addTextBoxPassword(String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required); +void addTextBox(String& html, + const String& input_id, + const String& name, + const String& value_key, + const String& placeholder, + int minlength, + int maxlength, + bool required, + bool readonly = false, + bool password = false); +void addTextBox(String& html, + const String& input_id, + const String& name, + const String& value_key, + int minlength, + int maxlength, + bool required, + bool readonly = false); +void addTextBoxPassword( + String& html, const String& input_id, const String& value_key, const String& name, int minlength, int maxlength, bool required); -#endif // Markup_h +void addNumberBox (String& html, const String& input_id, const String& name, const String& value_key, uint16_t max); + +void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr = 0); +void addListBox(String& html, const String& input_id, const String& name, const char* const* list_P, uint8_t size, uint8_t selected); +String addListGPIOSelect(const char* input, uint8_t function, uint8_t nr = 0); +#endif // Markup_h diff --git a/src/SuplaCommonPROGMEM.cpp b/src/SuplaCommonPROGMEM.cpp index ef630650..25415725 100644 --- a/src/SuplaCommonPROGMEM.cpp +++ b/src/SuplaCommonPROGMEM.cpp @@ -1,18 +1,6 @@ #include "SuplaCommonPROGMEM.h" #include "SuplaTemplateBoard.h" -String GIPOString(uint8_t gpio) { - return PGMT(GPIO_P[gpio]); -} - -String BME280String(uint8_t adr) { - return PGMT(BME280_P[adr]); -} - -String SHT3xString(uint8_t adr) { - return PGMT(SHT3x_P[adr]); -} - String StateString(uint8_t adr) { return PGMT(STATE_P[adr]); } diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 5eb0f595..48902040 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -4,7 +4,7 @@ #include "SuplaDeviceGUI.h" #include "GUIGenericCommon.h" -#define PGMT( pgm_ptr ) ( reinterpret_cast< const __FlashStringHelper * >( pgm_ptr ) ) +#define PGMT(pgm_ptr) (reinterpret_cast(pgm_ptr)) const char HTTP_META[] PROGMEM = ""); page += F(""); - page += WebServer->selectGPIO(INPUT_CFG_LED_GPIO, FUNCTION_CFG_LED); + page += addListGPIOSelect(INPUT_CFG_LED_GPIO, FUNCTION_CFG_LED); page += F(""); if (selected != 17) { @@ -153,7 +154,7 @@ String SuplaWebPageConfig::supla_webpage_config(int save) { page += F(""); - page += WebServer->selectGPIO(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); + page += addListGPIOSelect(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); page += F(""); page += F("
"); } pagebutton += F(""); - pagebutton += WebServer->selectGPIO(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr); + pagebutton += addListGPIOSelect(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr); pagebutton += F(""); } pagebutton += F("
"); @@ -196,7 +197,7 @@ String SuplaWebPageControl::supla_webpage_control(int save) { pagebutton += F(". "); pagebutton += S_LIMIT_SWITCH; pagebutton += F(""); - pagebutton += WebServer->selectGPIO(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr); + pagebutton += addListGPIOSelect(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr); pagebutton += F(""); } pagebutton += F(""); diff --git a/src/SuplaWebPageRelay.cpp b/src/SuplaWebPageRelay.cpp index fc84be82..fe7e461c 100644 --- a/src/SuplaWebPageRelay.cpp +++ b/src/SuplaWebPageRelay.cpp @@ -3,6 +3,7 @@ #include "SuplaWebServer.h" #include "SuplaCommonPROGMEM.h" #include "GUIGenericCommon.h" +#include "Markup.h" #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) SuplaWebPageRelay *WebPageRelay = new SuplaWebPageRelay(); @@ -135,7 +136,7 @@ String SuplaWebPageRelay::supla_webpage_relay(int save) { pagerelay += F(""); } pagerelay += F(""); - pagerelay += WebServer->selectGPIO(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr); + pagerelay += addListGPIOSelect(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr); pagerelay += F(""); } pagerelay += F(""); diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 5f2997b0..828723ba 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -15,25 +15,27 @@ #define PATH_OTHER "other" #define PATH_SAVE_OTHER "saveother" -#define INPUT_MULTI_DS_GPIO "mdsg" -#define INPUT_DHT11_GPIO "dht11" -#define INPUT_DHT22_GPIO "dht22" -#define INPUT_SDA_GPIO "sdag" -#define INPUT_SCL_GPIO "sclg" -#define INPUT_BME280 "bme280" -#define INPUT_ALTITUDE_BME280 "abme280" -#define INPUT_SHT30 "sht30" -#define INPUT_SI7021 "si7021" -#define INPUT_SI7021_SONOFF "si7021sonoff" -#define INPUT_TRIG_GPIO "trig" -#define INPUT_ECHO_GPIO "echo" -#define INPUT_MAX_DHT11 "mdht11" -#define INPUT_MAX_DHT22 "mdht22" -#define INPUT_MAX_DS18B20 "maxds" -#define INPUT_CLK_GPIO "clk" -#define INPUT_CS_GPIO "cs" -#define INPUT_D0_GPIO "d0" -#define INPUT_MAX6675 "max6675" +#define INPUT_MULTI_DS_GPIO "mdsg" +#define INPUT_DHT11_GPIO "dht11" +#define INPUT_DHT22_GPIO "dht22" +#define INPUT_SDA_GPIO "sdag" +#define INPUT_SCL_GPIO "sclg" +#define INPUT_BME280 "bme280" +#define INPUT_ALTITUDE_BME280 "abme280" +#define INPUT_SHT30 "sht30" +#define INPUT_SI7021 "si7021" +#define INPUT_SI7021_SONOFF "si7021sonoff" +#define INPUT_TRIG_GPIO "trig" +#define INPUT_ECHO_GPIO "echo" +#define INPUT_MAX_DHT11 "mdht11" +#define INPUT_MAX_DHT22 "mdht22" +#define INPUT_MAX_DS18B20 "maxds" +#define INPUT_CLK_GPIO "clk" +#define INPUT_CS_GPIO "cs" +#define INPUT_D0_GPIO "d0" +#define INPUT_MAX6675 "max6675" +#define INPUT_IMPULSE_COUNTER_GPIO "ic" +#define INPUT_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT "icdt" enum _sensorI2C { diff --git a/src/language/en.h b/src/language/en.h index 53a2f9a7..bc2849f9 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -91,4 +91,8 @@ //#### SuplaTemplateBoard.h #### #define S_ABSENT "ABSENT" +//#### SuplaWebPageSensor.cpp #### +#define S_IMPULSE_COUNTER "Impulse counter" +#define S_DEBOUNCE_TIMEOUT "Debounce timeout" + #endif // _LANGUAGE_EN_S_H_ diff --git a/src/language/pl.h b/src/language/pl.h index 3f3388d6..eec65c00 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -91,4 +91,8 @@ //#### SuplaTemplateBoard.h #### #define S_ABSENT "BRAK" +//#### SuplaWebPageSensor.cpp #### +#define S_IMPULSE_COUNTER "Licznik impulsów" +#define S_DEBOUNCE_TIMEOUT "Limit czasu" + #endif // _LANGUAGE_PL_S_H_ From beccb167fa630b20ce0e5127a0aae98d7dbea7a5 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 18 Nov 2020 21:54:17 +0100 Subject: [PATCH 027/233] Update SuplaWebPageSensor.cpp --- src/SuplaWebPageSensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index a58d4d81..27ab1226 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -631,7 +631,7 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { #ifdef SUPLA_SI7021 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt(); - size = sizeof(STATE_P) / sizeof(STATE_P[0]); + size = sizeof(STATE_P) / sizeof(STATE_P[0]); addListBox(page, INPUT_SI7021, "Si7021", STATE_P, size, selected); #endif } From 32ca0bffed2934062591387155681a3006d537f4 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 19 Nov 2020 07:57:37 +0100 Subject: [PATCH 028/233] addListGPIOLinkBox --- src/Markup.cpp | 90 +++++++++++++++++++++++--------------- src/Markup.h | 5 ++- src/SuplaWebPageSensor.cpp | 67 +++++----------------------- src/SuplaWebServer.h | 1 - 4 files changed, 71 insertions(+), 92 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index cfcb0cc2..59fa1fba 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -95,15 +95,35 @@ void addListGPIOBox(String& html, const String& input_id, const String& name, ui html += F(""); } -void addListBox(String& html, const String& input_id, const String& name, const char* const* array_P, uint8_t size, uint8_t selected) { - html += F(""); + + Serial.println(name); + Serial.println(sizeof(array_P)); for (uint8_t suported = 0; suported < size; suported++) { html += F(""); @@ -877,37 +870,17 @@ String SuplaWebPageSensor::supla_webpage_other(int save) { page += F("'>"); #ifdef SUPLA_HC_SR04 - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" HC-SR04

"); - page += F(""); - page += addListGPIOSelect(INPUT_TRIG_GPIO, FUNCTION_TRIG); - page += F(""); - page += F(""); - page += addListGPIOSelect(INPUT_ECHO_GPIO, FUNCTION_ECHO); - page += F(""); - page += F("
"); + addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " HC-SR04"); + addListGPIOBox(page, INPUT_TRIG_GPIO, "TRIG", FUNCTION_TRIG); + addListGPIOBox(page, INPUT_ECHO_GPIO, "ECHO", FUNCTION_ECHO); + addFormHeaderEnd(page); #endif #ifdef SUPLA_IMPULSE_COUNTER - page += F("

"); - page += S_GPIO_SETTINGS_FOR; - page += F(" "); - page += S_IMPULSE_COUNTER; - page += F("

"); - page += F(""); - page += addListGPIOSelect(INPUT_IMPULSE_COUNTER_GPIO, FUNCTION_IMPULSE_COUNTER); - page += F(""); + addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " " + S_IMPULSE_COUNTER); + addListGPIOBox(page, INPUT_IMPULSE_COUNTER_GPIO, "IC GPIO", FUNCTION_IMPULSE_COUNTER); if (ConfigESP->getGpio(FUNCTION_IMPULSE_COUNTER) != OFF_GPIO) { - page += F(""); + addNumberBox(page, INPUT_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, S_DEBOUNCE_TIMEOUT, KEY_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, 99999999); selected = ConfigManager->get(KEY_IMPULSE_COUNTER_RAISING_EDGE)->getValueInt(); addListBox(page, INPUT_IMPULSE_COUNTER_RAISING_EDGE, S_RAISING_EDGE, STATE_P, 2, selected); selected = ConfigManager->get(KEY_IMPULSE_COUNTER_RAISING_EDGE)->getValueInt(); @@ -923,7 +896,7 @@ String SuplaWebPageSensor::supla_webpage_other(int save) { page += count; page += F("'>
"); } - page += F("
"); + addFormHeaderEnd(page); #endif page += F("
"); pagerelay += F("
"); From 86975384ff952e91e2e90e958f69e9c0976b2895 Mon Sep 17 00:00:00 2001 From: Espablo Date: Mon, 23 Nov 2020 03:25:59 +0100 Subject: [PATCH 052/233] =?UTF-8?q?strona=20control=20wy=C5=9Bwietlanie=20?= =?UTF-8?q?i=20zapis=20po=20nowemu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaWebPageControl.cpp | 125 ++++++------------------------------ 1 file changed, 19 insertions(+), 106 deletions(-) diff --git a/src/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp index 92ed5b31..725a8ddf 100644 --- a/src/SuplaWebPageControl.cpp +++ b/src/SuplaWebPageControl.cpp @@ -50,35 +50,13 @@ void SuplaWebPageControl::handleControlSave() { uint8_t nr, current_value, last_value; #ifdef SUPLA_BUTTON last_value = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); - current_value = WebServer->httpServer.arg(INPUT_MAX_BUTTON).toInt(); - - if (last_value > 0) { - for (nr = 1; nr <= last_value; nr++) { - input = INPUT_BUTTON_GPIO; - input += nr; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(nr, FUNCTION_BUTTON) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { - ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_BUTTON)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_BUTTON, 2); - } - else if (ConfigESP->getGpio(nr, FUNCTION_BUTTON) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_BUTTON) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_BUTTON, ConfigESP->getLevel(nr, FUNCTION_BUTTON)); - } - else { - WebServer->sendContent(supla_webpage_control(6)); - return; - } - } + for (nr = 1; nr <= last_value; nr++) { + if (!WebServer->saveGPIO(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr, INPUT_MAX_BUTTON)) { + WebServer->sendContent(supla_webpage_control(6)); + return; } } + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_BUTTON).c_str(), "") != 0) { ConfigManager->set(KEY_MAX_BUTTON, WebServer->httpServer.arg(INPUT_MAX_BUTTON).c_str()); } @@ -86,35 +64,13 @@ void SuplaWebPageControl::handleControlSave() { #ifdef SUPLA_LIMIT_SWITCH last_value = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); - current_value = WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).toInt(); - - if (last_value > 0) { - for (nr = 1; nr <= last_value; nr++) { - input = INPUT_LIMIT_SWITCH_GPIO; - input += nr; - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH) != WebServer->httpServer.arg(input).toInt() || - WebServer->httpServer.arg(input).toInt() == OFF_GPIO || ConfigManager->get(key.c_str())->getElement(NR).toInt() > current_value) { - ConfigESP->clearGpio(ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH)); - } - if (WebServer->httpServer.arg(input).toInt() != OFF_GPIO) { - key = GPIO; - key += WebServer->httpServer.arg(input).toInt(); - if (ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_OFF) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_LIMIT_SWITCH, 0); - } - else if (ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH) == WebServer->httpServer.arg(input).toInt() && - ConfigManager->get(key.c_str())->getElement(FUNCTION).toInt() == FUNCTION_LIMIT_SWITCH) { - ConfigESP->setGpio(WebServer->httpServer.arg(input).toInt(), nr, FUNCTION_LIMIT_SWITCH, ConfigESP->getLevel(nr, FUNCTION_LIMIT_SWITCH)); - } - else { - WebServer->sendContent(supla_webpage_control(6)); - return; - } - } + for (nr = 1; nr <= last_value; nr++) { + if (!WebServer->saveGPIO(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr, INPUT_MAX_LIMIT_SWITCH)) { + WebServer->sendContent(supla_webpage_control(6)); + return; } } + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str(), "") != 0) { ConfigManager->set(KEY_MAX_LIMIT_SWITCH, WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str()); } @@ -140,67 +96,24 @@ String SuplaWebPageControl::supla_webpage_control(int save) { pagebutton += WebServer->SuplaJavaScript(PATH_CONTROL); pagebutton += F("
"); #if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_BUTTON) && defined(SUPLA_ROLLERSHUTTER)) - pagebutton += F("'>

"); - pagebutton += S_GPIO_SETTINGS_FOR_BUTTONS; - pagebutton += F("

"); - pagebutton += F(""); + addFormHeader(pagebutton, String(S_GPIO_SETTINGS_FOR_BUTTONS)); + addNumberBox(pagebutton, INPUT_MAX_BUTTON, S_QUANTITY, KEY_MAX_BUTTON, ConfigESP->countFreeGpio(FUNCTION_BUTTON)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); nr++) { - pagebutton += F(""); - pagebutton += addListGPIOSelect(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr); - pagebutton += F(""); + addListGPIOLinkBox(pagebutton, INPUT_BUTTON_GPIO, S_BUTTON, FUNCTION_BUTTON, PATH_BUTTON_SET, nr); } - pagebutton += F("
"); + addFormHeaderEnd(pagebutton); #endif #ifdef SUPLA_LIMIT_SWITCH - pagebutton += F("

"); - pagebutton += S_GPIO_SETTINGS_FOR_LIMIT_SWITCH; - pagebutton += F("

"); - pagebutton += F(""); + addFormHeader(pagebutton, String(S_GPIO_SETTINGS_FOR_LIMIT_SWITCH)); + addNumberBox(pagebutton, INPUT_MAX_LIMIT_SWITCH, S_QUANTITY, KEY_MAX_LIMIT_SWITCH, ConfigESP->countFreeGpio(FUNCTION_LIMIT_SWITCH)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { - pagebutton += F(""); - pagebutton += addListGPIOSelect(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr); - pagebutton += F(""); + addListGPIOBox(pagebutton, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); } - pagebutton += F("
"); + addFormHeaderEnd(pagebutton); #endif pagebutton += F(""); return page; } +#endif + +#ifdef SUPLA_IMPULSE_COUNTER +void SuplaWebPageSensor::handleImpulseCounterSet() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(supla_impulse_counter_set(0)); +} + +void SuplaWebPageSensor::handleImpulseCounterSaveSet() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + String readUrl, nr, key, input; + uint8_t place; + + String path = PATH_START; + path += PATH_SAVE_IMPULSE_COUNTER_SET; + readUrl = WebServer->httpServer.uri(); + + place = readUrl.indexOf(path); + nr = readUrl.substring(place + path.length(), place + path.length() + 3); + key = GPIO; + key += ConfigESP->getGpio(nr.toInt(), FUNCTION_IMPULSE_COUNTER); + + input = INPUT_IMPULSE_COUNTER_PULL_UP; + input += nr; + ConfigManager->setElement(key.c_str(), MEMORY, WebServer->httpServer.arg(input).toInt()); + + input = INPUT_IMPULSE_COUNTER_RAISING_EDGE; + input += nr; + ConfigManager->setElement(key.c_str(), LEVEL, WebServer->httpServer.arg(input).toInt()); + + ConfigManager->set(KEY_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, WebServer->httpServer.arg(INPUT_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT).c_str()); + Supla::GUI::impulseCounter[nr.toInt() - 1]->setCounter((unsigned long long)WebServer->httpServer.arg(INPUT_IMPULSE_COUNTER_CHANGE_VALUE).toInt()); + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + // Serial.println(F("E_CONFIG_OK: Dane zapisane")); + WebServer->sendContent(supla_webpage_other(1)); + break; + + case E_CONFIG_FILE_OPEN: + // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); + WebServer->sendContent(supla_webpage_other(2)); + break; + } +} + +String SuplaWebPageSensor::supla_impulse_counter_set(int save) { + String readUrl, nr, key; + uint8_t place, selected, suported; + + String path = PATH_START; + path += PATH_IMPULSE_COUNTER_SET; + readUrl = WebServer->httpServer.uri(); + + place = readUrl.indexOf(path); + nr = readUrl.substring(place + path.length(), place + path.length() + 3); + + String page = ""; + page += WebServer->SuplaSaveResult(save); + page += WebServer->SuplaJavaScript(PATH_OTHER); + uint8_t relays = ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt(); + if (nr.toInt() <= relays && ConfigESP->getGpio(nr.toInt(), FUNCTION_IMPULSE_COUNTER) != OFF_GPIO) { + page += F("

"); + page += S_IMPULSE_COUNTER_SETTINGS_NR; + page += F(" "); + page += nr; + page += F("

"); + page += F(""); + page += F(""); + addNumberBox(page, INPUT_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, S_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, KEY_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, 99999999); + page += F(""); + + page += F("
"); + } + else { + page += F("

"); + page += S_NO_IMPULSE_COUNTER_NR; + page += F(" "); + page += nr; + page += F("

"); + } + page += F("
"); + page += F("
"); + + return page; +} #endif \ No newline at end of file diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 94238687..8bbbd8dc 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -4,17 +4,18 @@ #include "SuplaDeviceGUI.h" #include "SuplaWebServer.h" -#define PATH_MULTI_DS "multids" -#define PATH_SAVE_MULTI_DS "savemultids" -#define PATH_1WIRE "1wire" -#define PATH_SAVE_1WIRE "save1wire" -#define PATH_I2C "i2c" -#define PATH_SAVE_I2C "savei2c" -#define PATH_SPI "spi" -#define PATH_SAVE_SPI "savespi" -#define PATH_OTHER "other" -#define PATH_SAVE_OTHER "saveother" -#define PATH_IMPULSE_COUNTER_SET "setimpulsecounter" +#define PATH_MULTI_DS "multids" +#define PATH_SAVE_MULTI_DS "savemultids" +#define PATH_1WIRE "1wire" +#define PATH_SAVE_1WIRE "save1wire" +#define PATH_I2C "i2c" +#define PATH_SAVE_I2C "savei2c" +#define PATH_SPI "spi" +#define PATH_SAVE_SPI "savespi" +#define PATH_OTHER "other" +#define PATH_SAVE_OTHER "saveother" +#define PATH_IMPULSE_COUNTER_SET "setimpulsecounter" +#define PATH_SAVE_IMPULSE_COUNTER_SET "savesetimpulsecounter" #define INPUT_MULTI_DS_GPIO "mdsg" #define INPUT_DHT11_GPIO "dht11" @@ -105,6 +106,10 @@ class SuplaWebPageSensor { #if defined(SUPLA_HC_SR04) || defined(SUPLA_IMPULSE_COUNTER) void handleOther(); void handleOtherSave(); + + void handleImpulseCounterSet(); + void handleImpulseCounterSaveSet(); + String supla_impulse_counter_set(int save); #endif private: diff --git a/src/language/en.h b/src/language/en.h index cf6ff1a1..b03974f9 100644 --- a/src/language/en.h +++ b/src/language/en.h @@ -95,10 +95,12 @@ #define S_ABSENT "ABSENT" //#### SuplaWebPageSensor.cpp #### -#define S_IMPULSE_COUNTER "Impulse counter" -#define S_DEBOUNCE_TIMEOUT "Debounce timeout" -#define S_RAISING_EDGE "Raising edge" -#define S_PULL_UP "Pull up" -#define S_CHANGE_VALUE "Change value" +#define S_IMPULSE_COUNTER "Impulse counter" +#define S_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT "Debounce timeout" +#define S_IMPULSE_COUNTER_RAISING_EDGE "Raising edge" +#define S_IMPULSE_COUNTER_PULL_UP "Pull up" +#define S_IMPULSE_COUNTER_CHANGE_VALUE "Change value" +#define S_IMPULSE_COUNTER_SETTINGS_NR "Settings IC nr." +#define S_NO_IMPULSE_COUNTER_NR "No IC nr." #endif // _LANGUAGE_EN_S_H_ diff --git a/src/language/pl.h b/src/language/pl.h index 1502974d..9da87466 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -95,10 +95,12 @@ #define S_ABSENT "BRAK" //#### SuplaWebPageSensor.cpp #### -#define S_IMPULSE_COUNTER "Licznik impulsów" -#define S_DEBOUNCE_TIMEOUT "Limit czasu" -#define S_RAISING_EDGE "Zbocze rosnące" -#define S_PULL_UP "Podciąganie do VCC" -#define S_CHANGE_VALUE "Zmień wartość" +#define S_IMPULSE_COUNTER "Licznik impulsów" +#define S_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT "Limit czasu" +#define S_IMPULSE_COUNTER_RAISING_EDGE "Zbocze rosnące" +#define S_IMPULSE_COUNTER_PULL_UP "Podciąganie do VCC" +#define S_IMPULSE_COUNTER_CHANGE_VALUE "Zmień wartość" +#define S_IMPULSE_COUNTER_SETTINGS_NR "Ustawienia IC nr." +#define S_NO_IMPULSE_COUNTER_NR "Brak IC nr." #endif // _LANGUAGE_PL_S_H_ From da220f2c91b5aec3ccb266f3a99cb4fde40b8dd5 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 28 Nov 2020 17:02:03 +0100 Subject: [PATCH 059/233] aktualizacja OTA w 2 krokach --- platformio.ini | 3 +- src/SuplaHTTPUpdateServer.cpp | 177 ++++++++++++++++++++++++++++++++++ src/SuplaHTTPUpdateServer.h | 52 ++++++++++ src/SuplaWebServer.cpp | 51 ++-------- src/SuplaWebServer.h | 10 +- 5 files changed, 240 insertions(+), 53 deletions(-) create mode 100644 src/SuplaHTTPUpdateServer.cpp create mode 100644 src/SuplaHTTPUpdateServer.h diff --git a/platformio.ini b/platformio.ini index 81807fd9..9ea7a14c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,8 +37,9 @@ lib_deps = closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 -build_flags = -D BUILD_VERSION='"GUI 1.0.0"' +build_flags = -D BUILD_VERSION='"GUI 1.0.1"' -w + -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC -D SUPLA_OTA -D SUPLA_RELAY diff --git a/src/SuplaHTTPUpdateServer.cpp b/src/SuplaHTTPUpdateServer.cpp new file mode 100644 index 00000000..5376a5ae --- /dev/null +++ b/src/SuplaHTTPUpdateServer.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include +#include "StreamString.h" +#include "SuplaHTTPUpdateServer.h" + +#include "SuplaWebServer.h" +#include "SuplaDeviceGUI.h" +#include "GUIGenericCommon.h" + +static const char serverIndex[] PROGMEM = + R"( + + + + + + +
Flash Size: {f}kB
+
Sketch Max Size: {s}kB
+
+ +
+ +
+ + )"; +static const char successResponse[] PROGMEM = "Update Success! Rebooting..."; +static const char twoStepResponse[] PROGMEM = "WARNING only use 2-step OTA update. Use GUI-GenericUpdater.bin"; + +ESP8266HTTPUpdateServer::ESP8266HTTPUpdateServer(bool serial_debug) { + _serial_output = serial_debug; + _server = NULL; + _username = emptyString; + _password = emptyString; + _authenticated = false; +} + +void ESP8266HTTPUpdateServer::setup(ESP8266WebServer* server, const String& path, const String& username, const String& password) { + _server = server; + _username = username; + _password = password; + + _server->on(PATH_UPDATE_HENDLE, std::bind(&ESP8266HTTPUpdateServer::handleFirmwareUp, this)); + // handler for the /update form page + _server->on(path.c_str(), HTTP_GET, [&]() { + if (_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str())) + return _server->requestAuthentication(); + + String index = FPSTR(serverIndex); + index.replace("{f}", String(ESP.getFlashChipRealSize() / 1024)); + index.replace("{s}", String(ESP.getFreeSketchSpace() / 1024)); + _server->send(200, PSTR("text/html"), index); + }); + + // handler for the /update form POST (once file upload finishes) + _server->on( + path.c_str(), HTTP_POST, + [&]() { + if (!_authenticated) + return _server->requestAuthentication(); + if (Update.hasError()) { + _server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError); + } + else { + _server->client().setNoDelay(true); + _server->send_P(200, PSTR("text/html"), successResponse); + delay(100); + _server->client().stop(); + ESP.restart(); + } + }, + [&]() { + // handler for the file upload, get's the sketch bytes, and writes + // them through the Update object + HTTPUpload& upload = _server->upload(); + + if (upload.status == UPLOAD_FILE_START) { + _updaterError = String(); + if (_serial_output) + Serial.setDebugOutput(true); + + _authenticated = (_username == emptyString || _password == emptyString || _server->authenticate(_username.c_str(), _password.c_str())); + if (!_authenticated) { + if (_serial_output) + Serial.printf("Unauthenticated Update\n"); + return; + } + + WiFiUDP::stopAll(); + if (_serial_output) + Serial.printf("Update: %s\n", upload.filename.c_str()); + if (upload.name == "filesystem") { + size_t fsSize = ((size_t)&_FS_end - (size_t)&_FS_start); + close_all_fs(); + if (!Update.begin(fsSize, U_FS)) { // start with max available size + if (_serial_output) + Update.printError(Serial); + } + } + else { + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace, U_FLASH)) { // start with max available size + _setUpdaterError(); + } + } + } + else if (_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()) { + if (_serial_output) + Serial.printf("."); + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + _server->send_P(200, PSTR("text/html"), twoStepResponse); + + //_setUpdaterError(); + } + } + else if (_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()) { + if (Update.end(true)) { // true to set the size to the current progress + if (_serial_output) + Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); + } + else { + _setUpdaterError(); + } + if (_serial_output) + Serial.setDebugOutput(false); + } + else if (_authenticated && upload.status == UPLOAD_FILE_ABORTED) { + Update.end(); + if (_serial_output) + Serial.println("Update was aborted"); + } + delay(0); + }); +} + +void ESP8266HTTPUpdateServer::handleFirmwareUp() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + WebServer->sendContent(suplaWebPageUpddate()); +} + +String ESP8266HTTPUpdateServer::suplaWebPageUpddate() { + String content = ""; + content += WebServer->SuplaJavaScript(); + content += F("
"); + content += F("

"); + content += S_SOFTWARE_UPDATE; + content += F("

"); + content += F("
"); + content += F("
"); + content += F(""); + content += F("
"); + content += F("
"); + content += F(""); + + return content; +} + +void ESP8266HTTPUpdateServer::_setUpdaterError() { + if (_serial_output) + Update.printError(Serial); + StreamString str; + Update.printError(str); + Serial.println(str.c_str()); + _updaterError = str.c_str(); +} diff --git a/src/SuplaHTTPUpdateServer.h b/src/SuplaHTTPUpdateServer.h new file mode 100644 index 00000000..ca61d4ea --- /dev/null +++ b/src/SuplaHTTPUpdateServer.h @@ -0,0 +1,52 @@ +#ifndef __HTTP_UPDATE_SERVER_H +#define __HTTP_UPDATE_SERVER_H + +#include + +#define PATH_UPDATE_HENDLE "/update" +#define PATH_UPDATE "/updateOTA" + +class ESP8266HTTPUpdateServer +{ + public: + ESP8266HTTPUpdateServer(bool serial_debug=false); + + void setup(ESP8266WebServer *server) + { + setup(server, emptyString, emptyString); + } + + void setup(ESP8266WebServer *server, const String& path) + { + setup(server, path, emptyString, emptyString); + } + + void setup(ESP8266WebServer *server, const String& username, const String& password) + { + setup(server, PATH_UPDATE, username, password); + } + + void setup(ESP8266WebServer *server, const String& path, const String& username, const String& password); + + void updateCredentials(const String& username, const String& password) + { + _username = username; + _password = password; + } + + protected: + void _setUpdaterError(); + + private: + bool _serial_output; + ESP8266WebServer *_server; + String _username; + String _password; + bool _authenticated; + String _updaterError; + + void handleFirmwareUp(); + String suplaWebPageUpddate(); +}; + +#endif diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index e9875e9f..228377bf 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -36,8 +36,6 @@ void SuplaWebServer::begin() { strcpy(this->www_password, ConfigManager->get(KEY_LOGIN_PASS)->getValue()); #ifdef SUPLA_OTA - httpUpdater.setup(&httpServer, UPDATE_PATH, www_username, www_password); -#endif httpServer.begin(); } @@ -50,11 +48,6 @@ void SuplaWebServer::createWebServer() { httpServer.on(path, HTTP_GET, std::bind(&SuplaWebServer::handle, this)); path = PATH_START; httpServer.on(path, std::bind(&SuplaWebServer::handleSave, this)); -#ifdef SUPLA_OTA - path = PATH_START; - path += PATH_UPDATE; - httpServer.on(path, std::bind(&SuplaWebServer::handleFirmwareUp, this)); -#endif path = PATH_START; path += PATH_REBOT; httpServer.on(path, std::bind(&SuplaWebServer::supla_webpage_reboot, this)); @@ -78,6 +71,9 @@ void SuplaWebServer::createWebServer() { #ifdef SUPLA_CONFIG WebPageConfig->createWebPageConfig(); #endif +#ifdef SUPLA_OTA + httpUpdater.setup(&httpServer, this->www_username, this->www_password); +#endif } void SuplaWebServer::handle() { @@ -133,15 +129,6 @@ void SuplaWebServer::handleSave() { break; } } -#ifdef SUPLA_OTA -void SuplaWebServer::handleFirmwareUp() { - if (ConfigESP->configModeESP == NORMAL_MODE) { - if (!httpServer.authenticate(www_username, www_password)) - return httpServer.requestAuthentication(); - } - this->sendContent(supla_webpage_upddate()); -} -#endif void SuplaWebServer::handleDeviceSettings() { if (ConfigESP->configModeESP == NORMAL_MODE) { @@ -210,8 +197,7 @@ String SuplaWebServer::supla_webpage_start(int save) { content += F("

"); #ifdef SUPLA_OTA content += F(""); @@ -226,30 +212,6 @@ String SuplaWebServer::supla_webpage_start(int save) { return content; } -#ifdef SUPLA_OTA -String SuplaWebServer::supla_webpage_upddate() { - String content = ""; - content += F("
"); - content += F("

"); - content += S_SOFTWARE_UPDATE; - content += F("

"); - content += F("
"); - content += F("
"); - content += F(""); - content += F("
"); - content += F("
"); - content += F(""); - - return content; -} -#endif - void SuplaWebServer::supla_webpage_reboot() { if (ConfigESP->configModeESP == NORMAL_MODE) { if (!httpServer.authenticate(www_username, www_password)) @@ -520,10 +482,9 @@ void SuplaWebServer::sendContent(const String content) { httpServer.chunkedResponseFinalize(); } -void SuplaWebServer::redirectToIndex() { +void SuplaWebServer::handleNotFound() { httpServer.sendHeader("Location", "/", true); - httpServer.send(302, "text/plain", ""); - httpServer.client().stop(); + httpServer.send(302, "text/plane", ""); } bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr, const String& input_max) { diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 8b0689dd..22dbf895 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -19,7 +19,7 @@ #include "GUI-Generic_Config.h" #ifdef SUPLA_OTA -#include +#include "SuplaHTTPUpdateServer.h" #endif #include #include @@ -37,8 +37,6 @@ #define PATH_START "/" #define PATH_SAVE_LOGIN "savelogin" -#define UPDATE_PATH "/firmware" -#define PATH_UPDATE "update" #define PATH_REBOT "rbt" #define PATH_DEVICE_SETTINGS "devicesettings" #define PATH_DEFAULT_SETTINGS "defaultsettings" @@ -62,7 +60,6 @@ class SuplaWebServer : public Supla::Element { char www_username[MAX_MLOGIN]; char www_password[MAX_MPASSWORD]; - char* update_path = (char*)UPDATE_PATH; const String SuplaFavicon(); const String SuplaIconEdit(); @@ -72,10 +69,9 @@ class SuplaWebServer : public Supla::Element { void sendContent(const String content); ESP8266WebServer httpServer = {80}; + #ifdef SUPLA_OTA - ESP8266HTTPUpdateServer httpUpdater; - void handleFirmwareUp(); - String supla_webpage_upddate(); + ESP8266HTTPUpdateServer httpUpdater; #endif bool saveGPIO(const String& input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); From 14018d4f974cb53cad84fa33cc3b9e99ea51a2b1 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 28 Nov 2020 17:02:25 +0100 Subject: [PATCH 060/233] handleNotFound --- src/SuplaWebServer.cpp | 2 +- src/SuplaWebServer.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 228377bf..7dd25519 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -35,7 +35,7 @@ void SuplaWebServer::begin() { strcpy(this->www_username, ConfigManager->get(KEY_LOGIN)->getValue()); strcpy(this->www_password, ConfigManager->get(KEY_LOGIN_PASS)->getValue()); -#ifdef SUPLA_OTA + httpServer.onNotFound(std::bind(&SuplaWebServer::handleNotFound, this)); httpServer.begin(); } diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 22dbf895..50e5a11d 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -92,7 +92,7 @@ class SuplaWebServer : public Supla::Element { String deviceSettings(int save); String loginSettings(); - void redirectToIndex(); + void handleNotFound(); }; #endif // SuplaWebServer_h From 90f56c96ffc03bdf3acf94995de2040d89dbe8c1 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 28 Nov 2020 19:15:22 +0100 Subject: [PATCH 061/233] poprawne MAX_DS18B20 dla addNumberBox --- src/SuplaWebPageSensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 935b21b9..7fe08b19 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -361,7 +361,7 @@ String SuplaWebPageSensor::supla_webpage_1wire(int save) { #ifdef SUPLA_DS18B20 addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " Multi DS18B20"); - addNumberBox(page, INPUT_MAX_DS18B20, S_QUANTITY, KEY_MULTI_MAX_DS18B20, ConfigESP->countFreeGpio(FUNCTION_DS18B20)); + addNumberBox(page, INPUT_MAX_DS18B20, S_QUANTITY, KEY_MULTI_MAX_DS18B20, MAX_DS18B20); if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { addListGPIOLinkBox(page, INPUT_MULTI_DS_GPIO, "MULTI DS18B20", FUNCTION_DS18B20, PATH_MULTI_DS); } From 1553bfeac1f1c4fd6b5b4f479e0170db04d0ca6b Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 28 Nov 2020 19:23:55 +0100 Subject: [PATCH 062/233] =?UTF-8?q?usuni=C4=99cie=20Serial.println?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Markup.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index 9312fb34..cd91f446 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -138,8 +138,6 @@ void addListBox(String& html, const String& input_id, const String& name, const html += input_id; html += F("'>"); - Serial.println(name); - Serial.println(sizeof(array_P)); for (uint8_t suported = 0; suported < size; suported++) { html += F("
"); html += F("

"); @@ -68,8 +76,7 @@ void addTextBox( return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, readonly, false); } -void addTextBoxPassword( - String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required) { +void addTextBoxPassword(String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required) { return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, false, true); } @@ -85,15 +92,6 @@ void addNumberBox(String& html, const String& input_id, const String& name, uint html += F("'>"); } -void addButton(String& html, const String& name, const String& url) { - html += F(""); - html += F("

"); -} - void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { html += F(""); } +void addButton(String& html, const String& name, const String& url) { + html += F(""); + html += F("

"); +} + +void addButtonSubmit(String& html, const String& name) { + html += F(""); + html += F("

"); +} + String addListGPIOSelect(const char* input, uint8_t function, uint8_t nr) { String page = ""; page += F(""); - selected = ConfigESP->getLevel(FUNCTION_CFG_LED); - for (suported = 0; suported < 2; suported++) { - page += F("
"); - } - page += F(""); - page += addListGPIOSelect(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON); - page += F(""); - - page += F(""); - - page += F("

"); - page += F("
"); - page += F(""); + addListBox(page, INPUT_CFG_MODE, S_CFG_MODE, CFG_MODE_P, 2, selected); + + addFormHeaderEnd(page); + addButtonSubmit(page, S_SAVE); + addFormEnd(page); + + addButton(page, S_RETURN, PATH_DEVICE_SETTINGS); + return page; } diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 37b8095b..104a6a15 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -144,8 +144,8 @@ String SuplaWebServer::supla_webpage_start(int save) { String content = F(""); content += SuplaSaveResult(save); content += SuplaJavaScript(); - content += F("
"); + addForm(content, F("post")); addFormHeader(content, S_SETTING_WIFI_SSID); addTextBox(content, INPUT_WIFI_SSID, S_WIFI_SSID, KEY_WIFI_SSID, 0, MAX_SSID, true); addTextBoxPassword(content, INPUT_WIFI_PASS, S_WIFI_PASS, KEY_WIFI_PASS, MIN_PASSWORD, MAX_PASSWORD, true); @@ -165,20 +165,9 @@ String SuplaWebServer::supla_webpage_start(int save) { #ifdef SUPLA_ROLLERSHUTTER uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); if (maxrollershutter >= 2) { - content += F("
"); - content += F("

"); - content += S_ROLLERSHUTTERS; - content += F("

"); - content += F(""); - content += F("
"); + addFormHeader(content, S_ROLLERSHUTTERS); + addNumberBox(content, INPUT_ROLLERSHUTTER, S_QUANTITY, KEY_MAX_ROLLERSHUTTER, (maxrollershutter / 2)); + addFormHeaderEnd(content); } #endif @@ -186,20 +175,13 @@ String SuplaWebServer::supla_webpage_start(int save) { WebPageSensor->showDS18B20(content, true); #endif - content += F("
"); - content += F("
"); + addButtonSubmit(content, S_SAVE); + addFormEnd(content); addButton(content, S_DEVICE_SETTINGS, PATH_DEVICE_SETTINGS); - addButton(content, "Tools", PATH_TOOLS); + addButton(content, F("Tools"), PATH_TOOLS); + addButton(content, S_RESTART, PATH_REBOT); - content += F("
"); - content += F("
"); return content; } @@ -217,7 +199,7 @@ String SuplaWebServer::deviceSettings(int save) { content += WebServer->SuplaSaveResult(save); content += WebServer->SuplaJavaScript(PATH_DEVICE_SETTINGS); - + content += F("
"); From a12875e281b9fc6e55c3049ef76eb951510ddc54 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 19 Dec 2020 18:27:03 +0100 Subject: [PATCH 084/233] wsparcie dla OLEDa --- platformio.ini | 6 +- src/GUI-Generic.ino | 14 +- src/GUI-Generic_Config.h | 1 + src/Markup.cpp | 8 +- src/Markup.h | 2 +- src/SuplaConfigESP.cpp | 10 +- src/SuplaConfigESP.h | 1 + src/SuplaDeviceGUI.cpp | 7 +- src/SuplaDeviceGUI.h | 11 ++ src/SuplaOled.cpp | 290 +++++++++++++++++++++++++++++++++++++ src/SuplaOled.h | 67 +++++++++ src/SuplaWebPageSensor.cpp | 55 ++++--- src/SuplaWebPageSensor.h | 4 +- src/language/pl.h | 2 +- 14 files changed, 435 insertions(+), 43 deletions(-) create mode 100644 src/SuplaOled.cpp create mode 100644 src/SuplaOled.h diff --git a/platformio.ini b/platformio.ini index 5fb67a51..63ceb725 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.0.4"' +build_flags = -D BUILD_VERSION='"GUI 1.0.6"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -41,7 +41,8 @@ build_flags = -D BUILD_VERSION='"GUI 1.0.4"' -D SUPLA_SI7021 -D SUPLA_MAX6675 -D SUPLA_HC_SR04 - -D SUPLA_IMPULSE_COUNTER + -D SUPLA_IMPULSE_COUNTER + -D SUPLA_OLED [env] framework = arduino @@ -58,6 +59,7 @@ lib_deps = datacute/DoubleResetDetector@^1.0.3 closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 + thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.1.0 extra_scripts = tools/copy_files.py [env:GUI_Generic_1M] diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 7466b190..dacd6309 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -149,18 +149,18 @@ void setup() { defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); - #ifdef SUPLA_BME280 + switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt()) { case BME280_ADDRESS_0X76: - new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); break; case BME280_ADDRESS_0X77: - new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); break; case BME280_ADDRESS_0X76_AND_0X77: - new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); - new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); + Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); break; } #endif @@ -207,6 +207,10 @@ void setup() { #endif +#ifdef SUPLA_OLED + new SuplaOled(); +#endif + Supla::GUI::begin(); } diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index fb01e453..537d5328 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -33,6 +33,7 @@ #define SUPLA_BME280 #define SUPLA_SHT3x #define SUPLA_SI7021 +#define SUPLA_OLED // #define SUPLA_HTU21D // 0x40 NOT SUPPORTED // #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED // #define SUPLA_BH1750 // 0x23 AND 0x5C NOT SUPPORTED diff --git a/src/Markup.cpp b/src/Markup.cpp index 51689b05..18e63b89 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -11,9 +11,11 @@ void addFormEnd(String& html) { void addFormHeader(String& html, const String& name) { html += F("
"); - html += F("

"); - html += name; - html += F("

"); + if (name != "\n") { + html += F("

"); + html += name; + html += F("

"); + } } void addFormHeaderEnd(String& html) { diff --git a/src/Markup.h b/src/Markup.h index 9cfe2771..f794c7b8 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -6,7 +6,7 @@ void addForm(String& html, const String& method, const String& action = "/"); void addFormEnd(String& html); -void addFormHeader(String& html, const String& name); +void addFormHeader(String& html, const String& name = "\n"); void addFormHeaderEnd(String& html); void addTextBox(String& html, diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 36e09b12..08bfc445 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -88,7 +88,7 @@ void SuplaConfigESP::runAction(int event, int action) { } if (configModeESP == CONFIG_MODE) { - if (event == Supla::ON_CLICK_1) { + if (event == Supla::ON_CLICK_1) { rebootESP(); } } @@ -113,11 +113,13 @@ void SuplaConfigESP::configModeInit() { void SuplaConfigESP::iterateAlways() { if (configModeESP == CONFIG_MODE) { - String CONFIG_WIFI_NAME = "SUPLA-ESP8266-" + getMacAddress(false); - WiFi.softAP(CONFIG_WIFI_NAME, ""); + WiFi.softAP(getConfigNameAP(), ""); } } +String SuplaConfigESP::getConfigNameAP() { + return "SUPLA-ESP8266-" + getMacAddress(false); +} const char *SuplaConfigESP::getLastStatusSupla() { return supla_status.msg; } @@ -414,6 +416,6 @@ void SuplaConfigESP::factoryReset(bool forceReset) { ConfigManager->save(); - // rebootESP(); + // rebootESP(); } } diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index 969a3783..1ed1e307 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -80,6 +80,7 @@ class SuplaConfigESP : public Supla::Triggerable, public Supla::Element { int getAction(int nr, int function); void factoryReset(bool forceReset = false); + String getConfigNameAP(); private: void configModeInit(); diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index 5f85fbf2..6a25ba62 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -76,9 +76,8 @@ void addRelayButton(int pinRelay, int pinButton, bool highIsOn) { relay[size]->keepTurnOnDuration(); eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); - button.push_back(new Supla::Control::Button(pinButton, true)); if (pinButton != OFF_GPIO) { - + button.push_back(new Supla::Control::Button(pinButton, true)); button[size]->addAction(ConfigESP->getAction(size + 1, FUNCTION_BUTTON), *relay[size], ConfigESP->getLevel(size + 1, FUNCTION_BUTTON)); button[size]->setSwNoiseFilterDelay(50); } @@ -163,7 +162,9 @@ std::vector button; #ifdef SUPLA_DS18B20 std::vector sensorDS; #endif - +#ifdef SUPLA_BME280 +std::vector sensorBme280; +#endif } // namespace GUI } // namespace Supla diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index 7c1142e8..c2c250a7 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -44,6 +44,13 @@ #include +#ifdef SUPLA_BME280 +#include +#include "SuplaWebPageSensor.h" +#endif + +#include "SuplaOled.h" + namespace Supla { namespace GUI { @@ -78,8 +85,12 @@ extern std::vector RollerShutterButtonClose; #ifdef SUPLA_IMPULSE_COUNTER extern std::vector impulseCounter; void addImpulseCounter(int pin, bool lowToHigh, bool inputPullup, unsigned int debounceDelay); +#endif +#ifdef SUPLA_BME280 +extern std::vector sensorBme280; #endif + }; // namespace GUI }; // namespace Supla diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp new file mode 100644 index 00000000..e4359fcc --- /dev/null +++ b/src/SuplaOled.cpp @@ -0,0 +1,290 @@ +#include "SuplaOled.h" +#include "SuplaDeviceGUI.h" + +#ifdef SUPLA_OLED +uint8_t* framesCountSensor; + +String getTempString(double temperature) { + if (temperature == -275) { + return F("error"); + } + else { + return String(temperature, 1); + } +} + +String getHumidityString(double humidity) { + if (humidity == -1) { + return F("error"); + } + else { + return String(humidity, 1); + } +} + +String getPressureString(double pressure) { + if (pressure == -1) { + return F("error"); + } + else { + return String(floor(pressure), 0); + } +} + +uint8_t getFramesCountSensor(OLEDDisplayUiState* state) { + if (state->frameState) { + return framesCountSensor[state->currentFrame]; + } + return 0; +} + +int32_t readRssi(void) { + int32_t rssi = WiFi.RSSI(); + if (WiFi.status() != WL_CONNECTED) + return -1; + if (rssi <= -100) + return 0; + if (rssi >= -50) + return 100; + return (2 * (rssi + 100)); +} + +void displaySignal(OLEDDisplay* display) { + int x = display->getWidth() - 17; + int y = 0; + int value = readRssi(); + // clear area only + display->setColor(BLACK); + display->fillRect(x, y, x + 46, 16); + display->setColor(WHITE); + if (value == -1) { + display->setFont(ArialMT_Plain_10); + display->drawString(x + 1, y, "x"); + } + else { + if (value > 0) + display->fillRect(x, y + 6, 3, 4); + else + display->drawRect(x, y + 6, 3, 4); + + if (value >= 25) + display->fillRect(x + 4, y + 4, 3, 6); + else + display->drawRect(x + 4, y + 4, 3, 6); + + if (value >= 50) + display->fillRect(x + 8, y + 2, 3, 8); + else + display->drawRect(x + 8, y + 2, 3, 8); + + if (value >= 75) + display->fillRect(x + 12, y, 3, 10); + else + display->drawRect(x + 12, y, 3, 10); + } +} + +void displayRelayState(OLEDDisplay* display) { + int y = 0; + int x = 0; + + display->setFont(ArialMT_Plain_10); + display->setTextAlignment(TEXT_ALIGN_LEFT); + for (int i = 0; i < Supla::GUI::relay.size(); i++) { + if (Supla::GUI::relay[i]->isOn()) { + display->setColor(WHITE); + display->fillRect(x, y + 1, 10, 10); + display->setColor(BLACK); + display->drawString(x + 2, y, String(i + 1)); + } + else { + display->setColor(WHITE); + display->drawString(x + 2, y, String(i + 1)); + } + x += 15; + } + display->setColor(WHITE); + display->drawHorizontalLine(0, 14, display->getWidth()); +} + +void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state) { + displaySignal(display); + if (Supla::GUI::relay.size()) { + displayRelayState(display); + } +} + +void displaySuplaStatus(OLEDDisplay* display) { + int x = 0; + int y = display->getHeight() / 3; + display->clear(); + + displaySignal(display); + + display->setFont(ArialMT_Plain_10); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setColor(WHITE); + display->drawStringMaxWidth(x, y, display->getWidth(), ConfigESP->supla_status.msg); + display->display(); +} + +void displayConfigMode(OLEDDisplay* display) { + display->clear(); + display->setFont(ArialMT_Plain_10); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setColor(WHITE); + display->drawString(0, 15, F("Tryb konfiguracyjny")); + display->drawString(0, 28, F("AP name:")); + display->drawString(0, 41, ConfigESP->getConfigNameAP()); + display->drawString(0, 54, F("IP: 192.168.4.1")); + display->display(); +} + +void displayBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + // display->drawXbm(10, 17, supla_logo_width, supla_logo_height, supla_logo_bits); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(ArialMT_Plain_16); + display->drawString(10, display->getHeight() / 2, F("SUPLA")); +} + +void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name = "\n") { + int drawHeightIcon = display->getHeight() / 2 - 10; + int drawStringIcon = display->getHeight() / 2 - 5; + + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawXbm(x + 0, y + drawHeightIcon, temp_width, temp_height, temp_bits); + display->setFont(ArialMT_Plain_10); + if (name != NULL) { + display->drawString(x + temp_width + 20, y + display->getHeight() / 2 - 15, name); + } + display->setFont(ArialMT_Plain_24); + display->drawString(x + temp_width + 10, y + drawStringIcon, getTempString(temp)); + display->setFont(ArialMT_Plain_16); + display->drawString(x + temp_width + 10 + (getTempString(temp).length() * 12), y + drawStringIcon, "ºC"); +} + +void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { + int drawHeightIcon = display->getHeight() / 2 - 10; + int drawStringIcon = display->getHeight() / 2 - 5; + + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawXbm(x + 0, y + drawHeightIcon, humidity_width, humidity_height, humidity_bits); + display->setFont(ArialMT_Plain_24); + display->drawString(x + humidity_width + 20, y + drawStringIcon, getHumidityString(humidity)); + display->setFont(ArialMT_Plain_16); + display->drawString(x + humidity_width + 20 + (getHumidityString(humidity).length() * 12), y + drawStringIcon, "%"); +} + +void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { + int drawHeightIcon = display->getHeight() / 2 - 10; + int drawStringIcon = display->getHeight() / 2 - 5; + + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawXbm(x + 0, y + drawHeightIcon, pressure_width, pressure_height, pressure_bits); + display->setFont(ArialMT_Plain_24); + display->drawString(x + pressure_width + 15, y + drawStringIcon, getPressureString(pressure)); + display->setFont(ArialMT_Plain_10); + display->drawString(x + pressure_width + 15 + (getPressureString(pressure).length() * 14), y + drawStringIcon, "hPa"); +} + +void displayDs18b20(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayTemp(display, state, x, y, Supla::GUI::sensorDS[getFramesCountSensor(state)]->getValue(), + String(ConfigManager->get(KEY_DS_NAME + getFramesCountSensor(state))->getValue())); +} + +void displayBme280Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayTemp(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getTemp()); +} + +void displayBme280Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displaHumidity(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getHumi()); +} + +void displayBme280Pressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayPressure(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getPressure()); +} + +SuplaOled::SuplaOled() { + if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { + display = new SH1106Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + } + else { + display = new SSD1306Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + } + + ui = new OLEDDisplayUi(display); + + overlays[0] = {msOverlay}; + + int maxFrame = Supla::GUI::sensorDS.size() + (Supla::GUI::sensorBme280.size() * 3); + if (maxFrame == 0) + maxFrame = 1; + + frames = (FrameCallback*)malloc(sizeof(FrameCallback) * maxFrame); + framesCountSensor = (uint8_t*)malloc(sizeof(uint8_t) * maxFrame); + + for (int i = 0; i < Supla::GUI::sensorDS.size(); i++) { + frames[frameCount] = {displayDs18b20}; + framesCountSensor[frameCount] = i; + frameCount += 1; + } + + for (int i = 0; i < Supla::GUI::sensorBme280.size(); i++) { + frames[frameCount] = {displayBme280Temp}; + framesCountSensor[frameCount] = i; + frameCount += 1; + frames[frameCount] = {displayBme280Humidity}; + framesCountSensor[frameCount] = i; + frameCount += 1; + frames[frameCount] = {displayBme280Pressure}; + framesCountSensor[frameCount] = i; + frameCount += 1; + } + + if (frameCount == 0) { + frames[frameCount] = {displayBlank}; + frameCount += 1; + } + + if (frameCount == 1) { + ui->disableAllIndicators(); + ui->disableAutoTransition(); + } + else { + ui->setIndicatorPosition(BOTTOM); + ui->setIndicatorDirection(LEFT_RIGHT); + ui->setFrameAnimation(SLIDE_LEFT); + } + + ui->setTargetFPS(60); + ui->setFrames(frames, frameCount); + ui->setOverlays(overlays, overlaysCount); + ui->init(); + display->flipScreenVertically(); + } +} + +void SuplaOled::iterateAlways() { + if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { + + if (ConfigESP->supla_status.status != STATUS_REGISTERED_AND_READY) { + displaySuplaStatus(display); + return; + } + + if (ConfigESP->configModeESP == NORMAL_MODE) { + int remainingTimeBudget = ui->update(); + + if (remainingTimeBudget > 0) + delay(remainingTimeBudget); + } + else { + displayConfigMode(display); + } + } +} +#endif \ No newline at end of file diff --git a/src/SuplaOled.h b/src/SuplaOled.h new file mode 100644 index 00000000..283e76b0 --- /dev/null +++ b/src/SuplaOled.h @@ -0,0 +1,67 @@ +#ifndef SuplaOled_H +#define SuplaOled_H + +#ifdef SUPLA_OLED + +#include "GUI-Generic_Config.h" +#include +#include +#include // Only needed for Arduino 1.6.5 and earlier +#include "SSD1306Wire.h" //OLED 0,96" +#include "SH1106Wire.h" //OLED 1.3" +#include "OLEDDisplayUi.h" + +const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; +const char SH1106[] PROGMEM = "SH1106 - 1,3''"; +const char* const OLED_P[] PROGMEM = {SSD1306, SH1106}; + +class SuplaOled : public Supla::Element { + public: + SuplaOled(); + + private: + void iterateAlways(); + OLEDDisplay *display; + OLEDDisplayUi *ui; + + FrameCallback *frames; + int frameCount = 0; + OverlayCallback overlays[1]; + int overlaysCount = 1; + + int count = 0; +}; + +// https://www.online-utility.org/image/convert/to/XBM +#define temp_width 32 +#define temp_height 32 +const uint8_t temp_bits[] PROGMEM = {0x00, 0x3C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x81, 0xF8, 0x03, 0x00, 0x99, 0x00, + 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0xF8, 0x03, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, + 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0xF8, 0x03, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, + 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, + 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x80, 0x99, 0x01, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x3C, 0x06, + 0x00, 0x20, 0x7E, 0x04, 0x00, 0x20, 0x7E, 0x04, 0x00, 0x20, 0x7E, 0x04, 0x00, 0x60, 0x3C, 0x06, 0x00, 0xC0, 0x18, + 0x03, 0x00, 0x80, 0x81, 0x01, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00}; + +#define humidity_width 32 +#define humidity_height 32 +const uint8_t humidity_bits[] PROGMEM = { + 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x18, + 0x18, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x80, 0x01, 0x80, 0x00, + 0x80, 0x01, 0x80, 0x01, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0x60, 0x30, 0x08, 0x06, 0x30, 0x48, 0x0C, 0x0C, 0x30, 0x48, + 0x06, 0x0C, 0x30, 0x30, 0x03, 0x0C, 0x30, 0x80, 0x01, 0x0C, 0x30, 0xC0, 0x00, 0x0C, 0x30, 0x60, 0x06, 0x0C, 0x30, 0x30, 0x09, 0x0C, + 0x30, 0x10, 0x09, 0x0C, 0x30, 0x00, 0x06, 0x0C, 0x20, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x03, 0x80, 0x01, + 0x80, 0x01, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0E, 0x70, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF0, 0x0F, 0x00}; + +#define pressure_width 32 +#define pressure_height 32 +const uint8_t pressure_bits[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, + 0x0F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x80, 0x81, 0x81, 0x01, 0xC0, 0x80, 0x01, 0x03, + 0x60, 0x80, 0x01, 0x06, 0x30, 0x00, 0xC0, 0x0C, 0x30, 0x03, 0xE0, 0x18, 0x18, 0x07, 0x40, 0x10, 0x0C, 0x82, 0x01, 0x30, 0x0C, 0x80, + 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x81, 0x81, 0x30, 0x8C, 0x83, 0xC1, 0x31, + 0x8C, 0x81, 0x81, 0x31, 0x08, 0xC0, 0x03, 0x10, 0x08, 0xE0, 0x07, 0x10, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +#endif +#endif // SuplaOled_H \ No newline at end of file diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 6cd19426..f0751b0f 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -5,6 +5,7 @@ #include "SuplaCommonPROGMEM.h" #include "GUIGenericCommon.h" #include "Markup.h" +#include "SuplaOled.h" SuplaWebPageSensor *WebPageSensor = new SuplaWebPageSensor(); @@ -447,6 +448,14 @@ void SuplaWebPageSensor::handlei2cSave() { } #endif +#ifdef SUPLA_OLED + key = KEY_ACTIVE_SENSOR; + input = INPUT_OLED; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_OLED, WebServer->httpServer.arg(input).toInt()); + } +#endif + switch (ConfigManager->save()) { case E_CONFIG_OK: WebServer->sendContent(supla_webpage_i2c(1)); @@ -463,52 +472,52 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { String page, key; page += WebServer->SuplaSaveResult(save); page += WebServer->SuplaJavaScript(PATH_I2C); - page += F(""); -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) + addForm(page, F("post"), PATH_SAVE_I2C); +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_OLED) addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " i2c"); addListGPIOBox(page, INPUT_SDA_GPIO, "SDA", FUNCTION_SDA); addListGPIOBox(page, INPUT_SCL_GPIO, "SCL", FUNCTION_SCL); + addFormHeaderEnd(page); if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { #ifdef SUPLA_BME280 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt(); + addFormHeader(page); addListBox(page, INPUT_BME280, "BME280 adres", BME280_P, 4, selected); addNumberBox(page, INPUT_ALTITUDE_BME280, S_ALTITUDE_ABOVE_SEA_LEVEL, KEY_ALTITUDE_BME280, 1500); + addFormHeaderEnd(page); #endif #ifdef SUPLA_SHT3x selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT3x).toInt(); + addFormHeader(page); addListBox(page, INPUT_SHT3x, "SHT3x", SHT3x_P, 4, selected); + addFormHeaderEnd(page); #endif #ifdef SUPLA_SI7021 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt(); - addListBox(page, INPUT_SI7021, "Si7021", STATE_P, 2, selected); + addFormHeader(page); + addListBox(page, INPUT_SI7021, "SI7021", STATE_P, 2, selected); + addFormHeaderEnd(page); +#endif + +#ifdef SUPLA_OLED + selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); + addFormHeader(page); + addListBox(page, INPUT_OLED, "OLED", OLED_P, 2, selected); + addFormHeaderEnd(page); #endif } - addFormHeaderEnd(page); #endif - page += F(""); - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); - page += F("
"); + addButtonSubmit(page, S_SAVE); + addFormEnd(page); + + addButton(page, S_RETURN, PATH_DEVICE_SETTINGS); + addButton(page, S_RESTART, PATH_REBOT); + return page; } #endif diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 76c6e7c2..f9cbdd5e 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -26,6 +26,7 @@ #define INPUT_ALTITUDE_BME280 "abme280" #define INPUT_SHT3x "sht30" #define INPUT_SI7021 "si7021" +#define INPUT_OLED "oled" #define INPUT_SI7021_SONOFF "si7021sonoff" #define INPUT_TRIG_GPIO "trig" #define INPUT_ECHO_GPIO "echo" @@ -49,7 +50,8 @@ enum _sensor SENSOR_BME280, SENSOR_SHT3x, SENSOR_SI7021, - SENSOR_MAX6675 + SENSOR_MAX6675, + SENSOR_OLED }; #endif diff --git a/src/language/pl.h b/src/language/pl.h index ae898809..043a25ef 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -23,7 +23,7 @@ #define S_RELAYS "PRZEKAŹNIKI" #define S_BUTTONS "PRZYCISKI" #define S_SENSORS_1WIRE "SENSORY 1Wire" -#define S_SENSORS_I2C "SENSORY i2c" +#define S_SENSORS_I2C "i2c" #define S_SENSORS_SPI "SENSORY SPI" #define S_SENSORS_OTHER "SENSORY INNE" #define S_LED_BUTTON_CFG "LED, BUTTON CONFIG" From b060e7680a1ecd292ed61b4347fdcd26f4d52ad2 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 19 Dec 2020 21:29:02 +0100 Subject: [PATCH 085/233] =?UTF-8?q?aktualizacja=20czujnik=C3=B3w=20SHT3x,?= =?UTF-8?q?=20Si7021,=20Si7021=20for=20Sonoff,=20MAX6675=20K?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/supla/sensor/MAX6675_K.cpp | 74 ++++++++ lib/SuplaDevice/src/supla/sensor/MAX6675_K.h | 92 +++------- lib/SuplaDevice/src/supla/sensor/SHT3x.h | 65 ++++--- lib/SuplaDevice/src/supla/sensor/Si7021.h | 15 +- .../src/supla/sensor/Si7021_sonoff.cpp | 118 ++++++++++++ .../src/supla/sensor/Si7021_sonoff.h | 168 ++++-------------- 6 files changed, 298 insertions(+), 234 deletions(-) create mode 100644 lib/SuplaDevice/src/supla/sensor/MAX6675_K.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.cpp diff --git a/lib/SuplaDevice/src/supla/sensor/MAX6675_K.cpp b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.cpp new file mode 100644 index 00000000..4f610197 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "MAX6675_K.h" + +namespace Supla { +namespace Sensor { +MAX6675_K::MAX6675_K(uint8_t pin_CLK, uint8_t pin_CS, uint8_t pin_DO) + : pin_CLK(pin_CLK), pin_CS(pin_CS), pin_DO(pin_DO) { +} + +double MAX6675_K::getValue() { + uint16_t value; + + digitalWrite(pin_CS, LOW); + delay(1); + + value = spiRead(); + value <<= 8; + value |= spiRead(); + + digitalWrite(pin_CS, HIGH); + + if (value & 0x4) { // this means there is no probe connected to Max6675 + Serial.print(F("no probe connected to Max6675")); + return TEMPERATURE_NOT_AVAILABLE; + } + value >>= 3; + + return value * 0.25; +} + +void MAX6675_K::onInit() { + digitalWrite(pin_CS, HIGH); + + pinMode(pin_CS, OUTPUT); + pinMode(pin_CLK, OUTPUT); + pinMode(pin_DO, INPUT); + + channel.setNewValue(getValue()); +} + +byte MAX6675_K::spiRead() { + int i; + byte d = 0; + + for (i = 7; i >= 0; i--) { + digitalWrite(pin_CLK, LOW); + delay(1); + if (digitalRead(pin_DO)) { + d |= (1 << i); + } + + digitalWrite(pin_CLK, HIGH); + delay(1); + } + return d; +} + +}; // namespace Sensor +}; // namespace Supla \ No newline at end of file diff --git a/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h index e0f52438..868a9bff 100644 --- a/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h +++ b/lib/SuplaDevice/src/supla/sensor/MAX6675_K.h @@ -1,80 +1,42 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + #ifndef _max6675_k_h #define _max6675_k_h -#if defined(ESP8266) -#include -#endif -#ifdef avr -#include -#endif - -#include -#include "supla/channel.h" -#include "supla/sensor/thermometer.h" +#include +#include namespace Supla { namespace Sensor { class MAX6675_K : public Thermometer { public: - MAX6675_K(uint8_t pin_CLK, uint8_t pin_CS, uint8_t pin_DO) { - _pin_CLK = pin_CLK; - _pin_CS = pin_CS; - _pin_DO = pin_DO; - } - - double getValue() { - uint16_t value; - - digitalWrite(_pin_CS, LOW); - delay(1); - - value = spi_read(); - value <<= 8; - value |= spi_read(); - - digitalWrite(_pin_CS, HIGH); - - if (value & 0x4) { - return -275; - } - value >>= 3; - - return value * 0.25; - } + MAX6675_K(uint8_t pin_CLK, uint8_t pin_CS, uint8_t pin_DO); + double getValue(); - void onInit() { - pinMode(_pin_CS, OUTPUT); - pinMode(_pin_CLK, OUTPUT); - pinMode(_pin_DO, INPUT); - - digitalWrite(_pin_CS, HIGH); - - channel.setNewValue(getValue()); - } - - byte spi_read(void) { - int i; - byte d = 0; - - for (i = 7; i >= 0; i--) { - digitalWrite(_pin_CLK, LOW); - delay(1); - if (digitalRead(_pin_DO)) { - d |= (1 << i); - } - - digitalWrite(_pin_CLK, HIGH); - delay(1); - } - - return d; - } + private: + void onInit(); + byte spiRead(); protected: - int8_t _pin_CLK; - int8_t _pin_CS; - int8_t _pin_DO; + int8_t pin_CLK; + int8_t pin_CS; + int8_t pin_DO; }; + }; // namespace Sensor }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/sensor/SHT3x.h b/lib/SuplaDevice/src/supla/sensor/SHT3x.h index 9f25c919..3406ac14 100644 --- a/lib/SuplaDevice/src/supla/sensor/SHT3x.h +++ b/lib/SuplaDevice/src/supla/sensor/SHT3x.h @@ -17,58 +17,73 @@ #ifndef _sht3x_h #define _sht3x_h -// Dependency: Risele SHT3x library - use library manager to install it +// Dependency: ClosedCube SHT3x library - use library manager to install it // https://github.com/closedcube/ClosedCube_SHT31D_Arduino -#include "ClosedCube_SHT31D.h" +#include #include "therm_hygro_meter.h" + namespace Supla { namespace Sensor { class SHT3x : public ThermHygroMeter { public: - SHT3x(int8_t address = 0x44) : address(address) { + SHT3x(int8_t address = 0x44) + : temperature(TEMPERATURE_NOT_AVAILABLE), + humidity(HUMIDITY_NOT_AVAILABLE), + address(address), + retryCount(0) { } double getTemp() { - float value = TEMPERATURE_NOT_AVAILABLE; + return temperature; + } - SHT31D result = sht.readTempAndHumidity( - SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50); + double getHumi() { + return humidity; + } - if (result.error == SHT3XD_NO_ERROR) { - value = result.t; - } else { - Serial.print("SHT [ERROR] Code #"); - Serial.println(result.error); + private: + void iterateAlways() { + if (millis() - lastReadTime > 10000) { + lastReadTime = millis(); + readValuesFromDevice(); + channel.setNewValue(getTemp(), getHumi()); } - return value; } - double getHumi() { - float value = HUMIDITY_NOT_AVAILABLE; + void onInit() { + sht.begin(address); + readValuesFromDevice(); + channel.setNewValue(getTemp(), getHumi()); + } + void readValuesFromDevice() { SHT31D result = sht.readTempAndHumidity( SHT3XD_REPEATABILITY_LOW, SHT3XD_MODE_CLOCK_STRETCH, 50); - if (result.error == SHT3XD_NO_ERROR) { - value = result.rh; - } else { - Serial.print("SHT [ERROR] Code #"); + if (result.error != SHT3XD_NO_ERROR) { + Serial.print(F("SHT [ERROR] Code #")); Serial.println(result.error); + retryCount++; + if (retryCount > 3) { + retryCount = 0; + temperature = TEMPERATURE_NOT_AVAILABLE; + humidity = HUMIDITY_NOT_AVAILABLE; + } + } else { + retryCount = 0; + temperature = result.t; + humidity = result.rh; } - return value; - } - - void onInit() { - sht.begin(address); - - channel.setNewValue(getTemp(), getHumi()); } protected: int8_t address; + double temperature; + double humidity; + int8_t retryCount; ::ClosedCube_SHT31D sht; // I2C }; diff --git a/lib/SuplaDevice/src/supla/sensor/Si7021.h b/lib/SuplaDevice/src/supla/sensor/Si7021.h index 0b6b70f6..055e3f6f 100644 --- a/lib/SuplaDevice/src/supla/sensor/Si7021.h +++ b/lib/SuplaDevice/src/supla/sensor/Si7021.h @@ -20,7 +20,7 @@ // Dependency: Adafruid Si7021 library - use library manager to install it // https://github.com/adafruit/Adafruit_Si7021 -#include "Adafruit_Si7021.h" +#include #include "therm_hygro_meter.h" namespace Supla { @@ -53,26 +53,25 @@ class Si7021 : public ThermHygroMeter { } void onInit() { - sensor = Adafruit_Si7021(); sensor.begin(); - Serial.print("Found model "); + Serial.print(F("Found model ")); switch (sensor.getModel()) { case SI_Engineering_Samples: - Serial.print("SI engineering samples"); + Serial.print(F("SI engineering samples")); break; case SI_7013: - Serial.print("Si7013"); + Serial.print(F("Si7013")); break; case SI_7020: - Serial.print("Si7020"); + Serial.print(F("Si7020")); break; case SI_7021: - Serial.print("Si7021"); + Serial.print(F("Si7021")); break; case SI_UNKNOWN: default: - Serial.print("Unknown"); + Serial.print(F("Unknown")); } channel.setNewValue(getTemp(), getHumi()); diff --git a/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.cpp b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.cpp new file mode 100644 index 00000000..c3391e25 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "Si7021_sonoff.h" + +namespace Supla { +namespace Sensor { +Si7021Sonoff::Si7021Sonoff(int pin) + : temperature(TEMPERATURE_NOT_AVAILABLE), + humidity(HUMIDITY_NOT_AVAILABLE), + pin(pin), + retryCount(0) { +} + +double Si7021Sonoff::getTemp() { + return temperature; +} + +double Si7021Sonoff::getHumi() { + return humidity; +} + +void Si7021Sonoff::iterateAlways() { + if (millis() - lastReadTime > 10000) { + lastReadTime = millis(); + read(); + channel.setNewValue(getTemp(), getHumi()); + } +} + +void Si7021Sonoff::onInit() { + pinMode(pin, INPUT); + + delay(100); + read(); + channel.setNewValue(getTemp(), getHumi()); +} + +double Si7021Sonoff::readTemp(uint8_t* data) { + double temp = (((data[2] & 0x7F) << 8) | data[3]) * 0.1; + if (data[2] & 0x80) { + temp *= -1; + } + return temp; +} + +double Si7021Sonoff::readHumi(uint8_t* data) { + double humi = ((data[0] << 8) | data[1]) * 0.1; + return humi; +} + +bool Si7021Sonoff::read() { + uint8_t data[5] = {0}; + + yield(); + + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + delayMicroseconds(500); + digitalWrite(pin, HIGH); + delayMicroseconds(20); + pinMode(pin, INPUT); + + uint32_t i = 0; + if (waitState(0) && waitState(1) && waitState(0)) { + for (i = 0; i < 40; i++) { + if (!waitState(1)) { + break; + } + delayMicroseconds(35); + if (digitalRead(pin) == HIGH) { + data[i / 8] |= (1 << (7 - i % 8)); + } + if (!waitState(0)) { + break; + } + } + } + + uint8_t checksum = (data[0] + data[1] + data[2] + data[3]) & 0xFF; + if (i < 40 || data[4] != checksum) { + retryCount++; + if (retryCount > 3) { + retryCount = 0; + temperature = TEMPERATURE_NOT_AVAILABLE; + humidity = HUMIDITY_NOT_AVAILABLE; + } + } else { + retryCount = 0; + temperature = readTemp(data); + humidity = readHumi(data); + } +} + +bool Si7021Sonoff::waitState(bool state) { + unsigned long timeout = micros(); + while (micros() - timeout < 100) { + if (digitalRead(pin) == state) return true; + delayMicroseconds(1); + } + return false; +} + +}; // namespace Sensor +}; // namespace Supla \ No newline at end of file diff --git a/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h index b07ec66d..bac9667e 100644 --- a/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h +++ b/lib/SuplaDevice/src/supla/sensor/Si7021_sonoff.h @@ -1,154 +1,50 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + #ifndef _si7021_sonoff_h #define _si7021_sonoff_h +#include #include + namespace Supla { namespace Sensor { class Si7021Sonoff : public ThermHygroMeter { public: - Si7021Sonoff(int pin) { - _pin = pin; - pinMode(_pin, INPUT); - delay(100); - retryCountTemp = 0; - retryCountHumi = 0; - lastValidTemp = TEMPERATURE_NOT_AVAILABLE; - lastValidHumi = HUMIDITY_NOT_AVAILABLE; - } - - double getTemp() { - double value = TEMPERATURE_NOT_AVAILABLE; - ReadTemp(); - value = _temp; - if (isnan(value)) { - value = TEMPERATURE_NOT_AVAILABLE; - } - - if (value == TEMPERATURE_NOT_AVAILABLE) { - retryCountTemp++; - if (retryCountTemp > 3) { - retryCountTemp = 0; - } - else { - value = lastValidTemp; - } - } - else { - retryCountTemp = 0; - } - lastValidTemp = value; - - return value; - } - - double getHumi() { - double value = HUMIDITY_NOT_AVAILABLE; - ReadTemp(); - value = _humidity; - if (isnan(value)) { - value = HUMIDITY_NOT_AVAILABLE; - } - - if (value == HUMIDITY_NOT_AVAILABLE) { - retryCountHumi++; - if (retryCountHumi > 3) { - retryCountHumi = 0; - } - else { - value = lastValidHumi; - } - } - else { - retryCountHumi = 0; - } - lastValidHumi = value; - - return value; - } - - void iterateAlways() { - if (lastReadTime + 10000 < millis()) { - lastReadTime = millis(); - channel.setNewValue(getTemp(), getHumi()); - } - } - - void onInit() { - channel.setNewValue(getTemp(), getHumi()); - } + Si7021Sonoff(int pin); + double getTemp(); + double getHumi(); private: - bool ReadTemp() { - _temp = NAN; - _humidity = NAN; - - uint8_t d[5]; - d[0] = d[1] = d[2] = d[3] = d[4] = 0; - - pinMode(_pin, OUTPUT); - digitalWrite(_pin, LOW); - delayMicroseconds(500); - digitalWrite(_pin, HIGH); - delayMicroseconds(20); - pinMode(_pin, INPUT); - - uint32_t i = 0; - if (WaitState(0) and WaitState(1) and WaitState(0)) { - for (i = 0; i < 40; i++) { - if (!WaitState(1)) { - break; - } - delayMicroseconds(35); - if (digitalRead(_pin) == HIGH) { - d[i / 8] |= (1 << (7 - i % 8)); - } - if (!WaitState(0)) { - break; - } - } - } - - if (i < 40) { - return false; - } - - uint8_t checksum = (d[0] + d[1] + d[2] + d[3]) & 0xFF; - if (d[4] == checksum) { - _temp = (((d[2] & 0x7F) << 8) | d[3]) * 0.1; - _humidity = ((d[0] << 8) | d[1]) * 0.1; - if (d[2] & 0x80) { - _temp *= -1; - } - } - - if (isnan(_temp) || isnan(_humidity)) { - Serial.println(F("Invalid NAN reading")); - return false; - } - return true; - } - - bool WaitState(bool state) { - unsigned long timeout = micros(); - while (micros() - timeout < 100) { - if (digitalRead(_pin) == state) - return true; - delayMicroseconds(1); - } - return false; - } + void iterateAlways(); + void onInit(); + double readTemp(uint8_t* data); + double readHumi(uint8_t* data); + bool read(); + bool waitState(bool state); protected: - int8_t _pin; - double _temp = NAN, _humidity = NAN; - double lastValidTemp; - double lastValidHumi; - int8_t retryCountTemp; - int8_t retryCountHumi; + int8_t pin; + double temperature; + double humidity; + int8_t retryCount; }; }; // namespace Sensor }; // namespace Supla -#endif +#endif \ No newline at end of file From 7037e3654fa0bfd2f5cdcfba03dd27258367732b Mon Sep 17 00:00:00 2001 From: krycha88 Date: Mon, 21 Dec 2020 18:11:01 +0100 Subject: [PATCH 086/233] =?UTF-8?q?wsparcie=20kolejnych=20czujnik=C3=B3w?= =?UTF-8?q?=20dla=20OLED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformio.ini | 2 +- src/GUI-Generic.ino | 43 +++-------------------- src/SuplaConfigESP.h | 1 + src/SuplaDeviceGUI.cpp | 13 +++++++ src/SuplaDeviceGUI.h | 58 ++++++++++++++++++++++++------- src/SuplaOled.cpp | 72 +++++++++++++++++++++++++++++++++++++-- src/SuplaOled.h | 20 +++++++++-- src/SuplaWebPageRelay.cpp | 1 - 8 files changed, 153 insertions(+), 57 deletions(-) diff --git a/platformio.ini b/platformio.ini index 63ceb725..dee6b5fe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.0.6"' +build_flags = -D BUILD_VERSION='"GUI 1.0.7"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index dacd6309..6498e0ba 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -13,40 +13,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "GUI-Generic_Config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef SUPLA_BME280 -#include -#include "SuplaWebPageSensor.h" -#endif -#ifdef SUPLA_SHT3x -#include -#endif -#ifdef SUPLA_SI7021 -#include -#endif -#ifdef SUPLA_SI7021_SONOFF -#include -#endif -#ifdef SUPLA_MAX6675 -#include -#endif -#ifdef SUPLA_IMPULSE_COUNTER -#include -#endif #include "SuplaDeviceGUI.h" -#include "SuplaWebServer.h" #define DRD_TIMEOUT 5 // Number of seconds after reset during which a subseqent reset will be considered a double reset. #define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use @@ -61,7 +28,6 @@ void setup() { } uint8_t nr, gpio; - String key; #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) uint8_t rollershutters = ConfigManager->get(KEY_MAX_ROLLERSHUTTER)->getValueInt(); @@ -122,7 +88,7 @@ void setup() { #ifdef SUPLA_DHT22 if (ConfigESP->getGpio(FUNCTION_DHT22) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT22)->getValueInt() > 0) { for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { - new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22); + Supla::GUI::sensorDHT22.push_back(new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22)); } } #endif @@ -135,7 +101,7 @@ void setup() { #ifdef SUPLA_SI7021_SONOFF if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != OFF_GPIO) { - new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); + Supla::GUI::sensorSi7021Sonoff.push_back(new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF))); } #endif @@ -190,7 +156,7 @@ void setup() { #ifdef SUPLA_MAX6675 if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { - new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0)); + Supla::GUI::sensorMAX6675_K.push_back(new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0))); } #endif @@ -208,7 +174,8 @@ void setup() { #endif #ifdef SUPLA_OLED - new SuplaOled(); + oled = new SuplaOled(); + oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); #endif Supla::GUI::begin(); diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index 1ed1e307..b96df291 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -17,6 +17,7 @@ #ifndef SuplaConfigESP_h #define SuplaConfigESP_h +#include "Arduino.h" #include #include #include "GUI-Generic_Config.h" diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index 6a25ba62..64c418be 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -162,10 +162,23 @@ std::vector button; #ifdef SUPLA_DS18B20 std::vector sensorDS; #endif + #ifdef SUPLA_BME280 std::vector sensorBme280; #endif +#ifdef SUPLA_SI7021_SONOFF +std::vector sensorSi7021Sonoff; +#endif + +#ifdef SUPLA_DHT22 +std::vector sensorDHT22; +#endif + +#ifdef SUPLA_MAX6675 +std::vector sensorMAX6675_K; +#endif + } // namespace GUI } // namespace Supla diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index c2c250a7..ed5bb1f5 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -19,17 +19,10 @@ #include "GUI-Generic_Config.h" -#include - -#include -#include -#include -#include "SuplaSensorDS18B20.h" -#include +#include +#include -#ifdef DEBUG_MODE -#include -#endif +#include #include "SuplaConfigESP.h" #include "SuplaConfigManager.h" @@ -41,15 +34,44 @@ #include "SuplaWebPageTools.h" #include "GUIGenericCommon.h" #include "Markup.h" +#include "SuplaOled.h" #include +#include +#include +#include + +#include "SuplaSensorDS18B20.h" +#include +#include +#include #ifdef SUPLA_BME280 #include #include "SuplaWebPageSensor.h" #endif - -#include "SuplaOled.h" +#ifdef SUPLA_SI7021_SONOFF +#include +#endif +#ifdef SUPLA_BME280 +#include +#include "SuplaWebPageSensor.h" +#endif +#ifdef SUPLA_SHT3x +#include +#endif +#ifdef SUPLA_SI7021 +#include +#endif +#ifdef SUPLA_MAX6675 +#include +#endif +#ifdef SUPLA_IMPULSE_COUNTER +#include +#endif +#ifdef DEBUG_MODE +#include +#endif namespace Supla { namespace GUI { @@ -91,6 +113,18 @@ void addImpulseCounter(int pin, bool lowToHigh, bool inputPullup, unsigned int d extern std::vector sensorBme280; #endif +#ifdef SUPLA_SI7021_SONOFF +extern std::vector sensorSi7021Sonoff; +#endif + +#ifdef SUPLA_DHT22 +extern std::vector sensorDHT22; +#endif + +#ifdef SUPLA_MAX6675 +extern std::vector sensorMAX6675_K; +#endif + }; // namespace GUI }; // namespace Supla diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index e4359fcc..e4bba5bd 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -207,6 +207,26 @@ void displayBme280Pressure(OLEDDisplay* display, OLEDDisplayUiState* state, int1 displayPressure(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getPressure()); } +void displaySi7021SonoffTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayTemp(display, state, x, y, Supla::GUI::sensorSi7021Sonoff[getFramesCountSensor(state)]->getTemp()); +} + +void displaySi7021SonoffHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displaHumidity(display, state, x, y, Supla::GUI::sensorSi7021Sonoff[getFramesCountSensor(state)]->getHumi()); +} + +void displayDHT22Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayTemp(display, state, x, y, Supla::GUI::sensorDHT22[getFramesCountSensor(state)]->getTemp()); +} + +void displayDHT22Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displaHumidity(display, state, x, y, Supla::GUI::sensorDHT22[getFramesCountSensor(state)]->getHumi()); +} + +void displayMAX6675Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + displayTemp(display, state, x, y, Supla::GUI::sensorMAX6675_K[getFramesCountSensor(state)]->getValue()); +} + SuplaOled::SuplaOled() { if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { @@ -220,7 +240,9 @@ SuplaOled::SuplaOled() { overlays[0] = {msOverlay}; - int maxFrame = Supla::GUI::sensorDS.size() + (Supla::GUI::sensorBme280.size() * 3); + int maxFrame = Supla::GUI::sensorDS.size() + (Supla::GUI::sensorBme280.size() * 3) + Supla::GUI::sensorSi7021Sonoff.size() + + (Supla::GUI::sensorDHT22.size() * 2) + Supla::GUI::sensorMAX6675_K.size(); + if (maxFrame == 0) maxFrame = 1; @@ -245,6 +267,30 @@ SuplaOled::SuplaOled() { frameCount += 1; } + for (int i = 0; i < Supla::GUI::sensorSi7021Sonoff.size(); i++) { + frames[frameCount] = {displaySi7021SonoffTemp}; + framesCountSensor[frameCount] = i; + frameCount += 1; + frames[frameCount] = {displaySi7021SonoffHumidity}; + framesCountSensor[frameCount] = i; + frameCount += 1; + } + + for (int i = 0; i < Supla::GUI::sensorDHT22.size(); i++) { + frames[frameCount] = {displayDHT22Temp}; + framesCountSensor[frameCount] = i; + frameCount += 1; + frames[frameCount] = {displayDHT22Humidity}; + framesCountSensor[frameCount] = i; + frameCount += 1; + } + + for (int i = 0; i < Supla::GUI::sensorMAX6675_K.size(); i++) { + frames[frameCount] = {displayMAX6675Temp}; + framesCountSensor[frameCount] = i; + frameCount += 1; + } + if (frameCount == 0) { frames[frameCount] = {displayBlank}; frameCount += 1; @@ -270,12 +316,17 @@ SuplaOled::SuplaOled() { void SuplaOled::iterateAlways() { if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { - if (ConfigESP->supla_status.status != STATUS_REGISTERED_AND_READY) { displaySuplaStatus(display); return; } + if (millis() - timeLastChangeOled > 30000 && oledON) { + display->setBrightness(50); + oledON = false; + // display.displayOff(); + } + if (ConfigESP->configModeESP == NORMAL_MODE) { int remainingTimeBudget = ui->update(); @@ -287,4 +338,21 @@ void SuplaOled::iterateAlways() { } } } + +void SuplaOled::addButtonOled(int pin) { + if (pin != OFF_GPIO) { + Supla::Control::Button* button = new Supla::Control::Button(pin, true, true); + button->addAction(TURN_ON_OLED, oled, Supla::ON_PRESS); + } +} + +void SuplaOled::runAction(int event, int action) { + if (action == TURN_ON_OLED) { + display->setBrightness(255); + timeLastChangeOled = millis(); + oledON = true; + } +} + +SuplaOled* oled; #endif \ No newline at end of file diff --git a/src/SuplaOled.h b/src/SuplaOled.h index 283e76b0..9457c9ba 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -5,22 +5,31 @@ #include "GUI-Generic_Config.h" #include +#include #include #include // Only needed for Arduino 1.6.5 and earlier #include "SSD1306Wire.h" //OLED 0,96" #include "SH1106Wire.h" //OLED 1.3" #include "OLEDDisplayUi.h" +enum customActions +{ + TURN_ON_OLED +}; + const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; const char SH1106[] PROGMEM = "SH1106 - 1,3''"; -const char* const OLED_P[] PROGMEM = {SSD1306, SH1106}; +const char *const OLED_P[] PROGMEM = {SSD1306, SH1106}; -class SuplaOled : public Supla::Element { +class SuplaOled : public Supla::Triggerable, public Supla::Element { public: SuplaOled(); + void addButtonOled(int pin); private: void iterateAlways(); + void runAction(int event, int action); + OLEDDisplay *display; OLEDDisplayUi *ui; @@ -30,8 +39,13 @@ class SuplaOled : public Supla::Element { int overlaysCount = 1; int count = 0; + + unsigned long timeLastChangeOled = millis(); + bool oledON = true; }; +extern SuplaOled *oled; + // https://www.online-utility.org/image/convert/to/XBM #define temp_width 32 #define temp_height 32 @@ -62,6 +76,6 @@ const uint8_t pressure_bits[] PROGMEM = { 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x80, 0x01, 0x30, 0x0C, 0x81, 0x81, 0x30, 0x8C, 0x83, 0xC1, 0x31, 0x8C, 0x81, 0x81, 0x31, 0x08, 0xC0, 0x03, 0x10, 0x08, 0xE0, 0x07, 0x10, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - + #endif #endif // SuplaOled_H \ No newline at end of file diff --git a/src/SuplaWebPageRelay.cpp b/src/SuplaWebPageRelay.cpp index 86e24207..75c9dd43 100644 --- a/src/SuplaWebPageRelay.cpp +++ b/src/SuplaWebPageRelay.cpp @@ -76,7 +76,6 @@ void SuplaWebPageRelay::handleRelaySave() { } String SuplaWebPageRelay::supla_webpage_relay(int save) { - String key; uint8_t selected, suported, nr; String pagerelay = ""; From 44e132b0070926a0580156ad957047de9746c09a Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 22 Dec 2020 12:53:32 +0100 Subject: [PATCH 087/233] fixed OLED --- platformio.ini | 3 ++- src/GUI-Generic.ino | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index dee6b5fe..725e3cd1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.0.7"' +build_flags = -D BUILD_VERSION='"GUI 1.0.8"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -60,6 +60,7 @@ lib_deps = closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.1.0 + xoseperez/HLW8012 @ ^1.1.1 extra_scripts = tools/copy_files.py [env:GUI_Generic_1M] diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 6498e0ba..e477a76e 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -150,6 +150,12 @@ void setup() { if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt()) { new Supla::Sensor::Si7021(); } +#endif +#ifdef SUPLA_OLED + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { + oled = new SuplaOled(); + oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); + } #endif } #endif @@ -173,11 +179,6 @@ void setup() { #endif -#ifdef SUPLA_OLED - oled = new SuplaOled(); - oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); -#endif - Supla::GUI::begin(); } From c66a93571e5b6469abbc3996810588203e7c1d81 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 24 Dec 2020 14:22:11 +0100 Subject: [PATCH 088/233] =?UTF-8?q?dodanie=20mo=C5=BCliwo=C5=9Bci=20wy?= =?UTF-8?q?=C5=82=C4=85czenia=20OLEDa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaCommonPROGMEM.h | 3 +++ src/SuplaOled.h | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 01aabf4e..e9ee41d8 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -54,6 +54,9 @@ const char HTTP_COPYRIGHT[] PROGMEM = "https://forum.supla.org/\n"; +const char HTTP_RBT[] PROGMEM = + "
"; + const char GPIO0[] PROGMEM = "GPIO0-D3"; const char GPIO1[] PROGMEM = "GPIO1-TX"; const char GPIO2[] PROGMEM = "GPIO2-D4"; diff --git a/src/SuplaOled.h b/src/SuplaOled.h index 9457c9ba..1a864cc9 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -17,10 +17,6 @@ enum customActions TURN_ON_OLED }; -const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; -const char SH1106[] PROGMEM = "SH1106 - 1,3''"; -const char *const OLED_P[] PROGMEM = {SSD1306, SH1106}; - class SuplaOled : public Supla::Triggerable, public Supla::Element { public: SuplaOled(); From a31a2e4694882408e34d7ddb4aa54902de6a9f11 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 24 Dec 2020 14:23:11 +0100 Subject: [PATCH 089/233] Update SuplaCommonPROGMEM.h --- src/SuplaCommonPROGMEM.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index e9ee41d8..0e0b6990 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -108,6 +108,12 @@ const char CFG_10_PRESSES[] PROGMEM = S_CFG_10_PRESSES; const char CFG_5SEK_HOLD[] PROGMEM = S_5SEK_HOLD; const char* const CFG_MODE_P[] PROGMEM = {CFG_10_PRESSES, CFG_5SEK_HOLD}; +#ifdef SUPLA_OLED +const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; +const char SH1106[] PROGMEM = "SH1106 - 1,3''"; +const char *const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106}; +#endif + String StateString(uint8_t adr); String LevelString(uint8_t nr); String MemoryString(uint8_t nr); From f1b7294e6b4af4ff7ceb3028fc559259d7f4c672 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 24 Dec 2020 14:24:31 +0100 Subject: [PATCH 090/233] =?UTF-8?q?Dodanie=20Restartu=20urz=C4=85dzenia=20?= =?UTF-8?q?na=20ka=C5=BCdej=20stronie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Markup.cpp | 45 +++++++++++++++++++++ src/Markup.h | 17 ++++---- src/SuplaDeviceGUI.h | 1 + src/SuplaHTTPUpdateServer.cpp | 4 +- src/SuplaWebPageConfig.cpp | 6 +-- src/SuplaWebPageControl.cpp | 16 +++----- src/SuplaWebPageRelay.cpp | 12 +++--- src/SuplaWebPageSensor.cpp | 73 ++++++++++------------------------- src/SuplaWebPageUpload.cpp | 6 +-- src/SuplaWebServer.cpp | 59 ++-------------------------- src/SuplaWebServer.h | 2 - 11 files changed, 97 insertions(+), 144 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index 18e63b89..b1cba4b8 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -210,4 +210,49 @@ String addListGPIOSelect(const char* input, uint8_t function, uint8_t nr) { String getURL(const String& url) { return String(F(PATH_START)) + url; +} + +const String SuplaJavaScript(const String& java_return) { + String java_script = + F("\n"); +#ifdef SUPLA_OTA + java_script += + F("\n"); +#endif + return java_script; +} + +const String SuplaSaveResult(int save) { + if (save == 0) + return F(""); + String saveresult = ""; + saveresult += F("
"); + if (save == 1) { + saveresult += S_DATA_SAVED; + } + else if (save == 2) { + saveresult += S_RESTART_MODULE; + } + else if (save == 3) { + saveresult += S_DATA_ERASED_RESTART_DEVICE; + } + else if (save == 4) { + saveresult += S_WRITE_ERROR_UNABLE_TO_READ_FILE_FS_PARTITION_MISSING; + } + else if (save == 5) { + saveresult += S_DATA_SAVED_RESTART_MODULE; + } + else if (save == 6) { + saveresult += S_WRITE_ERROR_BAD_DATA; + } + else if (save == 7) { + saveresult += F("data saved"); + } + saveresult += F("
"); + return saveresult; } \ No newline at end of file diff --git a/src/Markup.h b/src/Markup.h index f794c7b8..1a348def 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -19,16 +19,9 @@ void addTextBox(String& html, bool required, bool readonly = false, bool password = false); -void addTextBox(String& html, - const String& input_id, - const String& name, - uint8_t value_key, - int minlength, - int maxlength, - bool required, - bool readonly = false); -void addTextBoxPassword( - String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required); +void addTextBox( + String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required, bool readonly = false); +void addTextBoxPassword(String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required); void addNumberBox(String& html, const String& input_id, const String& name, uint8_t value_key, uint16_t max); @@ -45,4 +38,8 @@ void addButtonSubmit(String& html, const String& name); String addListGPIOSelect(const char* input, uint8_t function, uint8_t nr = 0); String getURL(const String& url); + +const String SuplaJavaScript(const String& java_return = PATH_START); + +const String SuplaSaveResult(int save); #endif // Markup_h diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index ed5bb1f5..7619e957 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -33,6 +33,7 @@ #include "SuplaWebPageUpload.h" #include "SuplaWebPageTools.h" #include "GUIGenericCommon.h" +#include "SuplaCommonPROGMEM.h" #include "Markup.h" #include "SuplaOled.h" diff --git a/src/SuplaHTTPUpdateServer.cpp b/src/SuplaHTTPUpdateServer.cpp index 3c93fb1a..7a68aaf8 100644 --- a/src/SuplaHTTPUpdateServer.cpp +++ b/src/SuplaHTTPUpdateServer.cpp @@ -149,7 +149,7 @@ void ESP8266HTTPUpdateServer::handleFirmwareUp() { String ESP8266HTTPUpdateServer::suplaWebPageUpddate() { String content = ""; - content += WebServer->SuplaJavaScript(); + content += SuplaJavaScript(); content += F("
"); content += F("

"); content += S_SOFTWARE_UPDATE; @@ -165,7 +165,7 @@ String ESP8266HTTPUpdateServer::suplaWebPageUpddate() { content += PATH_TOOLS; content += F("'>

"); + content += F("

"); return content; } diff --git a/src/SuplaWebPageConfig.cpp b/src/SuplaWebPageConfig.cpp index 14265f7a..1a28163b 100644 --- a/src/SuplaWebPageConfig.cpp +++ b/src/SuplaWebPageConfig.cpp @@ -68,8 +68,8 @@ String SuplaWebPageConfig::supla_webpage_config(int save) { uint8_t selected, suported; String page = ""; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_CONFIG); + page += SuplaSaveResult(save); + page += SuplaJavaScript(PATH_CONFIG); addForm(page, F("post"), PATH_SAVE_CONFIG); addFormHeader(page, S_GPIO_SETTINGS_FOR_CONFIG); @@ -81,7 +81,7 @@ String SuplaWebPageConfig::supla_webpage_config(int save) { selected = ConfigManager->get(KEY_CFG_MODE)->getValueInt(); addListBox(page, INPUT_CFG_MODE, S_CFG_MODE, CFG_MODE_P, 2, selected); - + addFormHeaderEnd(page); addButtonSubmit(page, S_SAVE); addFormEnd(page); diff --git a/src/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp index 5e2bfaed..9033f567 100644 --- a/src/SuplaWebPageControl.cpp +++ b/src/SuplaWebPageControl.cpp @@ -1,7 +1,6 @@ #include "SuplaWebPageControl.h" #include "SuplaDeviceGUI.h" #include "SuplaWebServer.h" -#include "SuplaCommonPROGMEM.h" #include "GUIGenericCommon.h" #include "Markup.h" @@ -92,8 +91,8 @@ String SuplaWebPageControl::supla_webpage_control(int save) { uint8_t nr, suported, selected; String pagebutton, key; - pagebutton += WebServer->SuplaSaveResult(save); - pagebutton += WebServer->SuplaJavaScript(PATH_CONTROL); + pagebutton += SuplaSaveResult(save); + pagebutton += SuplaJavaScript(PATH_CONTROL); pagebutton += F("
"); @@ -125,7 +124,7 @@ String SuplaWebPageControl::supla_webpage_control(int save) { pagebutton += PATH_DEVICE_SETTINGS; pagebutton += F("'>"); + pagebutton += F("

"); return pagebutton; } @@ -189,8 +188,8 @@ String SuplaWebPageControl::supla_webpage_button_set(int save) { nr_button = readUrl.substring(place + path.length(), place + path.length() + 3); String page = ""; - page += WebServer->SuplaSaveResult(save); - page += WebServer->SuplaJavaScript(PATH_CONTROL); + page += SuplaSaveResult(save); + page += SuplaJavaScript(PATH_CONTROL); uint8_t buttons = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); if (nr_button.toInt() <= buttons && ConfigESP->getGpio(nr_button.toInt(), FUNCTION_BUTTON) != OFF_GPIO) { page += F(""); - page += F(" + + + + + First char code: + + + Width: + + + Height: + + + + + + + + + + + + + + +
+ +
+
+

+ +
+
+ +
+
+
+
+ + + diff --git a/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.png b/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.png new file mode 100644 index 0000000000000000000000000000000000000000..800efc56f4bd50ba347eab57f3205ec8d0c0dd31 GIT binary patch literal 68751 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 M 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℞9m-8fv#DxWbiNUN1>5} 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 V=l%V&;(J^#ke61GDt>7i_#ZiQOU?iQ literal 0 HcmV?d00001 diff --git a/lib/esp8266-oled-ssd1306-master/resources/xbmPreview.png b/lib/esp8266-oled-ssd1306-master/resources/xbmPreview.png new file mode 100644 index 0000000000000000000000000000000000000000..70ea3a53a843f220f18d2ef7cc0698516a990ba6 GIT binary patch literal 41692 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 literal 0 HcmV?d00001 diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp new file mode 100644 index 00000000..bf4a5ee0 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp @@ -0,0 +1,1043 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + + /* + * TODO Helmut + * - test/finish dislplay.printf() on mbed-os + * - Finish _putc with drawLogBuffer when running display + */ + +#include "OLEDDisplay.h" + +OLEDDisplay::OLEDDisplay() { + + displayWidth = 128; + displayHeight = 64; + displayBufferSize = displayWidth * displayHeight / 8; + color = WHITE; + geometry = GEOMETRY_128_64; + textAlignment = TEXT_ALIGN_LEFT; + fontData = ArialMT_Plain_10; + fontTableLookupFunction = DefaultFontTableLookup; + buffer = NULL; +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + buffer_back = NULL; +#endif +} + +OLEDDisplay::~OLEDDisplay() { + end(); +} + +bool OLEDDisplay::allocateBuffer() { + + logBufferSize = 0; + logBufferFilled = 0; + logBufferLine = 0; + logBufferMaxLines = 0; + logBuffer = NULL; + + if (!this->connect()) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); + return false; + } + + if(this->buffer==NULL) { + this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer += getBufferOffset(); + + if(!this->buffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); + return false; + } + } + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if(this->buffer_back==NULL) { + this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer_back += getBufferOffset(); + + if(!this->buffer_back) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); + free(this->buffer - getBufferOffset()); + return false; + } + } + #endif + + return true; +} + +bool OLEDDisplay::init() { + + if(!allocateBuffer()) { + return false; + } + + sendInitCommands(); + resetDisplay(); + + return true; +} + +void OLEDDisplay::end() { + if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } + #endif + if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } +} + +void OLEDDisplay::resetDisplay(void) { + clear(); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + memset(buffer_back, 1, displayBufferSize); + #endif + display(); +} + +void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { + this->color = color; +} + +OLEDDISPLAY_COLOR OLEDDisplay::getColor() { + return this->color; +} + +void OLEDDisplay::setPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::clearPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; + case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + + +// Bresenham's algorithm - thx wikipedia and Adafruit_GFX +void OLEDDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { + int16_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0<=x1; x0++) { + if (steep) { + setPixel(y0, x0); + } else { + setPixel(x0, y0); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +void OLEDDisplay::drawRect(int16_t x, int16_t y, int16_t width, int16_t height) { + drawHorizontalLine(x, y, width); + drawVerticalLine(x, y, height); + drawVerticalLine(x + width - 1, y, height); + drawHorizontalLine(x, y + height - 1, width); +} + +void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { + for (int16_t x = xMove; x < xMove + width; x++) { + drawVerticalLine(x, yMove, height); + } +} + +void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + setPixel(x0 + x, y0 + y); //For the 8 octants + setPixel(x0 - x, y0 + y); + setPixel(x0 + x, y0 - y); + setPixel(x0 - x, y0 - y); + setPixel(x0 + y, y0 + x); + setPixel(x0 - y, y0 + x); + setPixel(x0 + y, y0 - x); + setPixel(x0 - y, y0 - x); + + } while (x < y); + + setPixel(x0 + radius, y0); + setPixel(x0, y0 + radius); + setPixel(x0 - radius, y0); + setPixel(x0, y0 - radius); +} + +void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + if (quads & 0x1) { + setPixel(x0 + x, y0 - y); + setPixel(x0 + y, y0 - x); + } + if (quads & 0x2) { + setPixel(x0 - y, y0 - x); + setPixel(x0 - x, y0 - y); + } + if (quads & 0x4) { + setPixel(x0 - y, y0 + x); + setPixel(x0 - x, y0 + y); + } + if (quads & 0x8) { + setPixel(x0 + x, y0 + y); + setPixel(x0 + y, y0 + x); + } + } + if (quads & 0x1 && quads & 0x8) { + setPixel(x0 + radius, y0); + } + if (quads & 0x4 && quads & 0x8) { + setPixel(x0, y0 + radius); + } + if (quads & 0x2 && quads & 0x4) { + setPixel(x0 - radius, y0); + } + if (quads & 0x1 && quads & 0x2) { + setPixel(x0, y0 - radius); + } +} + + +void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + drawHorizontalLine(x0 - x, y0 - y, 2*x); + drawHorizontalLine(x0 - x, y0 + y, 2*x); + drawHorizontalLine(x0 - y, y0 - x, 2*y); + drawHorizontalLine(x0 - y, y0 + x, 2*y); + + + } while (x < y); + drawHorizontalLine(x0 - radius, y0, 2 * radius); + +} + +void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { + if (y < 0 || y >= this->height()) { return; } + + if (x < 0) { + length += x; + x = 0; + } + + if ( (x + length) > this->width()) { + length = (this->width() - x); + } + + if (length <= 0) { return; } + + uint8_t * bufferPtr = buffer; + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + uint8_t drawBit = 1 << (y & 7); + + switch (color) { + case WHITE: while (length--) { + *bufferPtr++ |= drawBit; + }; break; + case BLACK: drawBit = ~drawBit; while (length--) { + *bufferPtr++ &= drawBit; + }; break; + case INVERSE: while (length--) { + *bufferPtr++ ^= drawBit; + }; break; + } +} + +void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { + if (x < 0 || x >= this->width()) return; + + if (y < 0) { + length += y; + y = 0; + } + + if ( (y + length) > this->height()) { + length = (this->height() - y); + } + + if (length <= 0) return; + + + uint8_t yOffset = y & 7; + uint8_t drawBit; + uint8_t *bufferPtr = buffer; + + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + if (yOffset) { + yOffset = 8 - yOffset; + drawBit = ~(0xFF >> (yOffset)); + + if (length < yOffset) { + drawBit &= (0xFF >> (yOffset - length)); + } + + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + + if (length < yOffset) return; + + length -= yOffset; + bufferPtr += this->width(); + } + + if (length >= 8) { + switch (color) { + case WHITE: + case BLACK: + drawBit = (color == WHITE) ? 0xFF : 0x00; + do { + *bufferPtr = drawBit; + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + case INVERSE: + do { + *bufferPtr = ~(*bufferPtr); + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + } + } + + if (length > 0) { + drawBit = (1 << (length & 7)) - 1; + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + } +} + +void OLEDDisplay::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress) { + uint16_t radius = height / 2; + uint16_t xRadius = x + radius; + uint16_t yRadius = y + radius; + uint16_t doubleRadius = 2 * radius; + uint16_t innerRadius = radius - 2; + + setColor(WHITE); + drawCircleQuads(xRadius, yRadius, radius, 0b00000110); + drawHorizontalLine(xRadius, y, width - doubleRadius + 1); + drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); + drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); + + uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; + + fillCircle(xRadius, yRadius, innerRadius); + fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); + fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); +} + +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { + drawInternal(xMove, yMove, width, height, image, 0, 0); +} + +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { + int16_t widthInXbm = (width + 7) / 8; + uint8_t data = 0; + + for(int16_t y = 0; y < height; y++) { + for(int16_t x = 0; x < width; x++ ) { + if (x & 7) { + data >>= 1; // Move a bit + } else { // Read new data every 8 bit + data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); + } + // if there is a bit draw it + if (data & 0x01) { + setPixel(xMove + x, yMove + y); + } + } + } +} + +void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { + uint16_t data; + + for(int16_t y = 0; y < 16; y++) { + data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); + for(int16_t x = 0; x < 16; x++ ) { + if ((data & 0x01) ^ inverse) { + setPixelColor(xMove + x, yMove + y, WHITE); + } else { + setPixelColor(xMove + x, yMove + y, BLACK); + } + data >>= 1; // Move a bit + } + } +} + +void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS) * JUMPTABLE_BYTES; + + uint16_t cursorX = 0; + uint16_t cursorY = 0; + + switch (textAlignment) { + case TEXT_ALIGN_CENTER_BOTH: + yMove -= textHeight >> 1; + // Fallthrough + case TEXT_ALIGN_CENTER: + xMove -= textWidth >> 1; // divide by 2 + break; + case TEXT_ALIGN_RIGHT: + xMove -= textWidth; + break; + case TEXT_ALIGN_LEFT: + break; + } + + // Don't draw anything if it is not on the screen. + if (xMove + textWidth < 0 || xMove > this->width() ) {return;} + if (yMove + textHeight < 0 || yMove > this->width() ) {return;} + + for (uint16_t j = 0; j < textLength; j++) { + int16_t xPos = xMove + cursorX; + int16_t yPos = yMove + cursorY; + + uint8_t code = text[j]; + if (code >= firstChar) { + uint8_t charCode = code - firstChar; + + // 4 Bytes per char code + uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress + uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / + uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size + uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); + drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + } + + cursorX += currentCharWidth; + } + } +} + + +void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, String strUser) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + + // char* text must be freed! + char* text = utf8ascii(strUser); + + uint16_t yOffset = 0; + // If the string should be centered vertically too + // we need to now how heigh the string is. + if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { + uint16_t lb = 0; + // Find number of linebreaks in text + for (uint16_t i=0;text[i] != 0; i++) { + lb += (text[i] == 10); + } + // Calculate center + yOffset = (lb * lineHeight) / 2; + } + + uint16_t line = 0; + char* textPart = strtok(text,"\n"); + while (textPart != NULL) { + uint16_t length = strlen(textPart); + drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); + textPart = strtok(NULL, "\n"); + } + free(text); +} + +void OLEDDisplay::drawStringf( int16_t x, int16_t y, char* buffer, String format, ... ) +{ + va_list myargs; + va_start(myargs, format); + vsprintf(buffer, format.c_str(), myargs); + va_end(myargs); + drawString( x, y, buffer ); +} + +void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, String strUser) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + + char* text = utf8ascii(strUser); + + uint16_t length = strlen(text); + uint16_t lastDrawnPos = 0; + uint16_t lineNumber = 0; + uint16_t strWidth = 0; + + uint16_t preferredBreakpoint = 0; + uint16_t widthAtBreakpoint = 0; + + for (uint16_t i = 0; i < length; i++) { + strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + + // Always try to break on a space or dash + if (text[i] == ' ' || text[i]== '-') { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + + if (strWidth >= maxLineWidth) { + if (preferredBreakpoint == 0) { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint); + lastDrawnPos = preferredBreakpoint + 1; + // It is possible that we did not draw all letters to i so we need + // to account for the width of the chars from `i - preferredBreakpoint` + // by calculating the width we did not draw yet. + strWidth = strWidth - widthAtBreakpoint; + preferredBreakpoint = 0; + } + } + + // Draw last part if needed + if (lastDrawnPos < length) { + drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos)); + } + + free(text); +} + +uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + + uint16_t stringWidth = 0; + uint16_t maxWidth = 0; + + while (length--) { + stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + if (text[length] == 10) { + maxWidth = max(maxWidth, stringWidth); + stringWidth = 0; + } + } + + return max(maxWidth, stringWidth); +} + +uint16_t OLEDDisplay::getStringWidth(String strUser) { + char* text = utf8ascii(strUser); + uint16_t length = strlen(text); + uint16_t width = getStringWidth(text, length); + free(text); + return width; +} + +void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { + this->textAlignment = textAlignment; +} + +void OLEDDisplay::setFont(const uint8_t *fontData) { + this->fontData = fontData; +} + +void OLEDDisplay::displayOn(void) { + sendCommand(DISPLAYON); +} + +void OLEDDisplay::displayOff(void) { + sendCommand(DISPLAYOFF); +} + +void OLEDDisplay::invertDisplay(void) { + sendCommand(INVERTDISPLAY); +} + +void OLEDDisplay::normalDisplay(void) { + sendCommand(NORMALDISPLAY); +} + +void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { + sendCommand(SETPRECHARGE); //0xD9 + sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F + sendCommand(SETCONTRAST); + sendCommand(contrast); // 0-255 + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(DISPLAYON); +} + +void OLEDDisplay::setBrightness(uint8_t brightness) { + uint8_t contrast = brightness; + if (brightness < 128) { + // Magic values to get a smooth/ step-free transition + contrast = brightness * 1.171; + } else { + contrast = brightness * 1.171 - 43; + } + + uint8_t precharge = 241; + if (brightness == 0) { + precharge = 0; + } + uint8_t comdetect = brightness / 8; + + setContrast(contrast, precharge, comdetect); +} + +void OLEDDisplay::resetOrientation() { + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); //Reset screen rotation or mirroring +} + +void OLEDDisplay::flipScreenVertically() { + sendCommand(SEGREMAP | 0x01); + sendCommand(COMSCANDEC); //Rotate screen 180 Deg +} + +void OLEDDisplay::mirrorScreen() { + sendCommand(SEGREMAP); + sendCommand(COMSCANDEC); //Mirror screen +} + +void OLEDDisplay::clear(void) { + memset(buffer, 0, displayBufferSize); +} + +void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + // Always align left + setTextAlignment(TEXT_ALIGN_LEFT); + + // State values + uint16_t length = 0; + uint16_t line = 0; + uint16_t lastPos = 0; + + for (uint16_t i=0;ilogBufferFilled;i++){ + // Everytime we have a \n print + if (this->logBuffer[i] == 10) { + length++; + // Draw string on line `line` from lastPos to length + // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT + drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0); + // Remember last pos + lastPos = i; + // Reset length + length = 0; + } else { + // Count chars until next linebreak + length++; + } + } + // Draw the remaining string + if (length > 0) { + drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0); + } +} + +uint16_t OLEDDisplay::getWidth(void) { + return displayWidth; +} + +uint16_t OLEDDisplay::getHeight(void) { + return displayHeight; +} + +bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ + if (logBuffer != NULL) free(logBuffer); + uint16_t size = lines * chars; + if (size > 0) { + this->logBufferLine = 0; // Lines printed + this->logBufferFilled = 0; // Nothing stored yet + this->logBufferMaxLines = lines; // Lines max printable + this->logBufferSize = size; // Total number of characters the buffer can hold + this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); + if(!this->logBuffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); + return false; + } + } + return true; +} + +size_t OLEDDisplay::write(uint8_t c) { + if (this->logBufferSize > 0) { + // Don't waste space on \r\n line endings, dropping \r + if (c == 13) return 1; + + // convert UTF-8 character to font table index + c = (this->fontTableLookupFunction)(c); + // drop unknown character + if (c == 0) return 1; + + bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; + bool bufferNotFull = this->logBufferFilled < this->logBufferSize; + + // Can we write to the buffer? + if (bufferNotFull && maxLineNotReached) { + this->logBuffer[logBufferFilled] = c; + this->logBufferFilled++; + // Keep track of lines written + if (c == 10) this->logBufferLine++; + } else { + // Max line number is reached + if (!maxLineNotReached) this->logBufferLine--; + + // Find the end of the first line + uint16_t firstLineEnd = 0; + for (uint16_t i=0;ilogBufferFilled;i++) { + if (this->logBuffer[i] == 10){ + // Include last char too + firstLineEnd = i + 1; + break; + } + } + // If there was a line ending + if (firstLineEnd > 0) { + // Calculate the new logBufferFilled value + this->logBufferFilled = logBufferFilled - firstLineEnd; + // Now we move the lines infront of the buffer + memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); + } else { + // Let's reuse the buffer if it was full + if (!bufferNotFull) { + this->logBufferFilled = 0; + }// else { + // Nothing to do here + //} + } + write(c); + } + } + // We are always writing all uint8_t to the buffer + return 1; +} + +size_t OLEDDisplay::write(const char* str) { + if (str == NULL) return 0; + size_t length = strlen(str); + for (size_t i = 0; i < length; i++) { + write(str[i]); + } + return length; +} + +#ifdef __MBED__ +int OLEDDisplay::_putc(int c) { + + if (!fontData) + return 1; + if (!logBufferSize) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint16_t lines = this->displayHeight / textHeight; + uint16_t chars = 2 * (this->displayWidth / textHeight); + + if (this->displayHeight % textHeight) + lines++; + if (this->displayWidth % textHeight) + chars++; + setLogBuffer(lines, chars); + } + + return this->write((uint8_t)c); +} +#endif + +// Private functions +void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + this->displayBufferSize = displayWidth * displayHeight / 8; +} + +void OLEDDisplay::sendInitCommands(void) { + if (geometry == GEOMETRY_RAWMODE) + return; + sendCommand(DISPLAYOFF); + sendCommand(SETDISPLAYCLOCKDIV); + sendCommand(0xF0); // Increase speed of the display max ~96Hz + sendCommand(SETMULTIPLEX); + sendCommand(this->height() - 1); + sendCommand(SETDISPLAYOFFSET); + sendCommand(0x00); + if(geometry == GEOMETRY_64_32) + sendCommand(0x00); + else + sendCommand(SETSTARTLINE); + sendCommand(CHARGEPUMP); + sendCommand(0x14); + sendCommand(MEMORYMODE); + sendCommand(0x00); + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); + sendCommand(SETCOMPINS); + + if (geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) { + sendCommand(0x12); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x02); + } + + sendCommand(SETCONTRAST); + + if (geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) { + sendCommand(0xCF); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x8F); + } + + sendCommand(SETPRECHARGE); + sendCommand(0xF1); + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(0x40); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(0x2e); // stop scroll + sendCommand(DISPLAYON); +} + +void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) { + if (width < 0 || height < 0) return; + if (yMove + height < 0 || yMove > this->height()) return; + if (xMove + width < 0 || xMove > this->width()) return; + + uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) + int8_t yOffset = yMove & 7; + + bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; + + int16_t initYMove = yMove; + int8_t initYOffset = yOffset; + + + for (uint16_t i = 0; i < bytesInData; i++) { + + // Reset if next horizontal drawing phase is started. + if ( i % rasterHeight == 0) { + yMove = initYMove; + yOffset = initYOffset; + } + + uint8_t currentByte = pgm_read_byte(data + offset + i); + + int16_t xPos = xMove + (i / rasterHeight); + int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); + +// int16_t yScreenPos = yMove + yOffset; + int16_t dataPos = xPos + yPos; + + if (dataPos >= 0 && dataPos < displayBufferSize && + xPos >= 0 && xPos < this->width() ) { + + if (yOffset >= 0) { + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte << yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; + } + + if (dataPos < (displayBufferSize - this->width())) { + switch (this->color) { + case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; + case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; + case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; + } + } + } else { + // Make new offset position + yOffset = -yOffset; + + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; + } + + // Prepare for next iteration by moving one block up + yMove -= 8; + + // and setting the new yOffset + yOffset = 8 - yOffset; + } +#ifndef __MBED__ + yield(); +#endif + } + } +} + +// You need to free the char! +char* OLEDDisplay::utf8ascii(String str) { + uint16_t k = 0; + uint16_t length = str.length() + 1; + + // Copy the string into a char array + char* s = (char*) malloc(length * sizeof(char)); + if(!s) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); + return (char*) str.c_str(); + } + str.toCharArray(s, length); + + length--; + + for (uint16_t i=0; i < length; i++) { + char c = (this->fontTableLookupFunction)(s[i]); + if (c!=0) { + s[k++]=c; + } + } + + s[k]=0; + + // This will leak 's' be sure to free it in the calling function. + return s; +} + +void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { + this->fontTableLookupFunction = function; +} + + +char DefaultFontTableLookup(const uint8_t ch) { + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + static uint8_t LASTCHAR; + + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } + + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; + + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; + case 0xC3: return (uint8_t) (ch | 0xC0); + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } + + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored +} diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h new file mode 100644 index 00000000..8500b761 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h @@ -0,0 +1,383 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef OLEDDISPLAY_h +#define OLEDDISPLAY_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) + +#include +#define delay(x) wait_ms(x) +#define yield() void() + +/* + * This is a little Arduino String emulation to keep the OLEDDisplay + * library code in common between Arduino and mbed-os + */ +class String { +public: + String(const char *s) { _str = s; }; + int length() { return strlen(_str); }; + const char *c_str() { return _str; }; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { + memcpy(buf, _str + index, std::min(bufsize, strlen(_str))); + }; +private: + const char *_str; +}; + +#else +#error "Unkown operating system" +#endif + +#include "OLEDDisplayFonts.h" + +//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ ) +//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ ) + +#ifndef DEBUG_OLEDDISPLAY +#define DEBUG_OLEDDISPLAY(...) +#endif + +// Use DOUBLE BUFFERING by default +#ifndef OLEDDISPLAY_REDUCE_MEMORY +#define OLEDDISPLAY_DOUBLE_BUFFER +#endif + +// Header Values +#define JUMPTABLE_BYTES 4 + +#define JUMPTABLE_LSB 1 +#define JUMPTABLE_SIZE 2 +#define JUMPTABLE_WIDTH 3 +#define JUMPTABLE_START 4 + +#define WIDTH_POS 0 +#define HEIGHT_POS 1 +#define FIRST_CHAR_POS 2 +#define CHAR_NUM_POS 3 + + +// Display commands +#define CHARGEPUMP 0x8D +#define COLUMNADDR 0x21 +#define COMSCANDEC 0xC8 +#define COMSCANINC 0xC0 +#define DISPLAYALLON 0xA5 +#define DISPLAYALLON_RESUME 0xA4 +#define DISPLAYOFF 0xAE +#define DISPLAYON 0xAF +#define EXTERNALVCC 0x1 +#define INVERTDISPLAY 0xA7 +#define MEMORYMODE 0x20 +#define NORMALDISPLAY 0xA6 +#define PAGEADDR 0x22 +#define SEGREMAP 0xA0 +#define SETCOMPINS 0xDA +#define SETCONTRAST 0x81 +#define SETDISPLAYCLOCKDIV 0xD5 +#define SETDISPLAYOFFSET 0xD3 +#define SETHIGHCOLUMN 0x10 +#define SETLOWCOLUMN 0x00 +#define SETMULTIPLEX 0xA8 +#define SETPRECHARGE 0xD9 +#define SETSEGMENTREMAP 0xA1 +#define SETSTARTLINE 0x40 +#define SETVCOMDETECT 0xDB +#define SWITCHCAPVCC 0x2 + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } +#endif + +enum OLEDDISPLAY_COLOR { + BLACK = 0, + WHITE = 1, + INVERSE = 2 +}; + +enum OLEDDISPLAY_TEXT_ALIGNMENT { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_RIGHT = 1, + TEXT_ALIGN_CENTER = 2, + TEXT_ALIGN_CENTER_BOTH = 3 +}; + + +enum OLEDDISPLAY_GEOMETRY { + GEOMETRY_128_64 = 0, + GEOMETRY_128_32 = 1, + GEOMETRY_64_48 = 2, + GEOMETRY_64_32 = 3, + GEOMETRY_RAWMODE = 4 +}; + +enum HW_I2C { + I2C_ONE, + I2C_TWO +}; + +typedef char (*FontTableLookupFunction)(const uint8_t ch); +char DefaultFontTableLookup(const uint8_t ch); + + +#ifdef ARDUINO +class OLEDDisplay : public Print { +#elif __MBED__ +class OLEDDisplay : public Stream { +#else +#error "Unkown operating system" +#endif + + public: + OLEDDisplay(); + virtual ~OLEDDisplay(); + + uint16_t width(void) const { return displayWidth; }; + uint16_t height(void) const { return displayHeight; }; + + // Use this to resume after a deep sleep without resetting the display (what init() would do). + // Returns true if connection to the display was established and the buffer allocated, false otherwise. + bool allocateBuffer(); + + // Allocates the buffer and initializes the driver & display. Resets the display! + // Returns false if buffer allocation failed, true otherwise. + bool init(); + + // Free the memory used by the display + void end(); + + // Cycle through the initialization + void resetDisplay(void); + + /* Drawing functions */ + // Sets the color of all pixel operations + void setColor(OLEDDISPLAY_COLOR color); + + // Returns the current color. + OLEDDISPLAY_COLOR getColor(); + + // Draw a pixel at given position + void setPixel(int16_t x, int16_t y); + + // Draw a pixel at given position and color + void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color); + + // Clear a pixel at given position FIXME: INVERSE is untested with this function + void clearPixel(int16_t x, int16_t y); + + // Draw a line from position 0 to position 1 + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); + + // Draw the border of a rectangle at the given location + void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Fill the rectangle + void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Draw the border of a circle + void drawCircle(int16_t x, int16_t y, int16_t radius); + + // Draw all Quadrants specified in the quads bit mask + void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads); + + // Fill circle + void fillCircle(int16_t x, int16_t y, int16_t radius); + + // Draw a line horizontally + void drawHorizontalLine(int16_t x, int16_t y, int16_t length); + + // Draw a line vertically + void drawVerticalLine(int16_t x, int16_t y, int16_t length); + + // Draws a rounded progress bar with the outer dimensions given by width and height. Progress is + // a unsigned byte value between 0 and 100 + void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); + + // Draw a bitmap in the internal image format + void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image); + + // Draw a XBM + void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); + + // Draw icon 16x16 xbm format + void drawIco16x16(int16_t x, int16_t y, const char *ico, bool inverse = false); + + /* Text functions */ + + // Draws a string at the given location + void drawString(int16_t x, int16_t y, String text); + + // Draws a formatted string (like printf) at the given location + void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... ); + + // Draws a String with a maximum width at the given location. + // If the given String is wider than the specified width + // The text will be wrapped to the next line at a space or dash + void drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, String text); + + // Returns the width of the const char* with the current + // font settings + uint16_t getStringWidth(const char* text, uint16_t length); + + // Convencience method for the const char version + uint16_t getStringWidth(String text); + + // Specifies relative to which anchor point + // the text is rendered. Available constants: + // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH + void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment); + + // Sets the current font. Available default fonts + // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 + void setFont(const uint8_t *fontData); + + // Set the function that will convert utf-8 to font table index + void setFontTableLookupFunction(FontTableLookupFunction function); + + /* Display functions */ + + // Turn the display on + void displayOn(void); + + // Turn the display offs + void displayOff(void); + + // Inverted display mode + void invertDisplay(void); + + // Normal display mode + void normalDisplay(void); + + // Set display contrast + // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 + // normal brightness & contrast: contrast = 100 + void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); + + // Convenience method to access + void setBrightness(uint8_t); + + // Reset display rotation or mirroring + void resetOrientation(); + + // Turn the display upside down + void flipScreenVertically(); + + // Mirror the display (to be used in a mirror or as a projector) + void mirrorScreen(); + + // Write the buffer to the display memory + virtual void display(void) = 0; + + // Clear the local pixel buffer + void clear(void); + + // Log buffer implementation + + // This will define the lines and characters you can + // print to the screen. When you exeed the buffer size (lines * chars) + // the output may be truncated due to the size constraint. + bool setLogBuffer(uint16_t lines, uint16_t chars); + + // Draw the log buffer at position (x, y) + void drawLogBuffer(uint16_t x, uint16_t y); + + // Get screen geometry + uint16_t getWidth(void); + uint16_t getHeight(void); + + // Implement needed function to be compatible with Print class + size_t write(uint8_t c); + size_t write(const char* s); + + // Implement needed function to be compatible with Stream class +#ifdef __MBED__ + int _putc(int c); + int _getc() { return -1; }; +#endif + + + uint8_t *buffer; + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t *buffer_back; + #endif + + protected: + + OLEDDISPLAY_GEOMETRY geometry; + + uint16_t displayWidth; + uint16_t displayHeight; + uint16_t displayBufferSize; + + // Set the correct height, width and buffer for the geometry + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0); + + OLEDDISPLAY_TEXT_ALIGNMENT textAlignment; + OLEDDISPLAY_COLOR color; + + const uint8_t *fontData; + + // State values for logBuffer + uint16_t logBufferSize; + uint16_t logBufferFilled; + uint16_t logBufferLine; + uint16_t logBufferMaxLines; + char *logBuffer; + + + // the header size of the buffer used, e.g. for the SPI command header + virtual int getBufferOffset(void) = 0; + + // Send a command to the display (low level function) + virtual void sendCommand(uint8_t com) {(void)com;}; + + // Connect to the display + virtual bool connect() { return false; }; + + // Send all the init commands + void sendInitCommands(); + + // converts utf8 characters to extended ascii + char* utf8ascii(String s); + + void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); + + void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth); + + FontTableLookupFunction fontTableLookupFunction; +}; + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayFonts.h b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayFonts.h new file mode 100644 index 00000000..abc61ba1 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayFonts.h @@ -0,0 +1,1278 @@ +#ifndef OLEDDISPLAYFONTS_h +#define OLEDDISPLAYFONTS_h + +#ifdef __MBED__ +#define PROGMEM +#endif + +const uint8_t ArialMT_Plain_10[] PROGMEM = { + 0x0A, // Width: 10 + 0x0D, // Height: 13 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x03, // 32:65535 + 0x00, 0x00, 0x04, 0x03, // 33:0 + 0x00, 0x04, 0x05, 0x04, // 34:4 + 0x00, 0x09, 0x09, 0x06, // 35:9 + 0x00, 0x12, 0x0A, 0x06, // 36:18 + 0x00, 0x1C, 0x10, 0x09, // 37:28 + 0x00, 0x2C, 0x0E, 0x07, // 38:44 + 0x00, 0x3A, 0x01, 0x02, // 39:58 + 0x00, 0x3B, 0x06, 0x03, // 40:59 + 0x00, 0x41, 0x06, 0x03, // 41:65 + 0x00, 0x47, 0x05, 0x04, // 42:71 + 0x00, 0x4C, 0x09, 0x06, // 43:76 + 0x00, 0x55, 0x04, 0x03, // 44:85 + 0x00, 0x59, 0x03, 0x03, // 45:89 + 0x00, 0x5C, 0x04, 0x03, // 46:92 + 0x00, 0x60, 0x05, 0x03, // 47:96 + 0x00, 0x65, 0x0A, 0x06, // 48:101 + 0x00, 0x6F, 0x08, 0x06, // 49:111 + 0x00, 0x77, 0x0A, 0x06, // 50:119 + 0x00, 0x81, 0x0A, 0x06, // 51:129 + 0x00, 0x8B, 0x0B, 0x06, // 52:139 + 0x00, 0x96, 0x0A, 0x06, // 53:150 + 0x00, 0xA0, 0x0A, 0x06, // 54:160 + 0x00, 0xAA, 0x09, 0x06, // 55:170 + 0x00, 0xB3, 0x0A, 0x06, // 56:179 + 0x00, 0xBD, 0x0A, 0x06, // 57:189 + 0x00, 0xC7, 0x04, 0x03, // 58:199 + 0x00, 0xCB, 0x04, 0x03, // 59:203 + 0x00, 0xCF, 0x0A, 0x06, // 60:207 + 0x00, 0xD9, 0x09, 0x06, // 61:217 + 0x00, 0xE2, 0x09, 0x06, // 62:226 + 0x00, 0xEB, 0x0B, 0x06, // 63:235 + 0x00, 0xF6, 0x14, 0x0A, // 64:246 + 0x01, 0x0A, 0x0E, 0x07, // 65:266 + 0x01, 0x18, 0x0C, 0x07, // 66:280 + 0x01, 0x24, 0x0C, 0x07, // 67:292 + 0x01, 0x30, 0x0B, 0x07, // 68:304 + 0x01, 0x3B, 0x0C, 0x07, // 69:315 + 0x01, 0x47, 0x09, 0x06, // 70:327 + 0x01, 0x50, 0x0D, 0x08, // 71:336 + 0x01, 0x5D, 0x0C, 0x07, // 72:349 + 0x01, 0x69, 0x04, 0x03, // 73:361 + 0x01, 0x6D, 0x08, 0x05, // 74:365 + 0x01, 0x75, 0x0E, 0x07, // 75:373 + 0x01, 0x83, 0x0C, 0x06, // 76:387 + 0x01, 0x8F, 0x10, 0x08, // 77:399 + 0x01, 0x9F, 0x0C, 0x07, // 78:415 + 0x01, 0xAB, 0x0E, 0x08, // 79:427 + 0x01, 0xB9, 0x0B, 0x07, // 80:441 + 0x01, 0xC4, 0x0E, 0x08, // 81:452 + 0x01, 0xD2, 0x0C, 0x07, // 82:466 + 0x01, 0xDE, 0x0C, 0x07, // 83:478 + 0x01, 0xEA, 0x0B, 0x06, // 84:490 + 0x01, 0xF5, 0x0C, 0x07, // 85:501 + 0x02, 0x01, 0x0D, 0x07, // 86:513 + 0x02, 0x0E, 0x11, 0x09, // 87:526 + 0x02, 0x1F, 0x0E, 0x07, // 88:543 + 0x02, 0x2D, 0x0D, 0x07, // 89:557 + 0x02, 0x3A, 0x0C, 0x06, // 90:570 + 0x02, 0x46, 0x06, 0x03, // 91:582 + 0x02, 0x4C, 0x06, 0x03, // 92:588 + 0x02, 0x52, 0x04, 0x03, // 93:594 + 0x02, 0x56, 0x09, 0x05, // 94:598 + 0x02, 0x5F, 0x0C, 0x06, // 95:607 + 0x02, 0x6B, 0x03, 0x03, // 96:619 + 0x02, 0x6E, 0x0A, 0x06, // 97:622 + 0x02, 0x78, 0x0A, 0x06, // 98:632 + 0x02, 0x82, 0x0A, 0x05, // 99:642 + 0x02, 0x8C, 0x0A, 0x06, // 100:652 + 0x02, 0x96, 0x0A, 0x06, // 101:662 + 0x02, 0xA0, 0x05, 0x03, // 102:672 + 0x02, 0xA5, 0x0A, 0x06, // 103:677 + 0x02, 0xAF, 0x0A, 0x06, // 104:687 + 0x02, 0xB9, 0x04, 0x02, // 105:697 + 0x02, 0xBD, 0x04, 0x02, // 106:701 + 0x02, 0xC1, 0x08, 0x05, // 107:705 + 0x02, 0xC9, 0x04, 0x02, // 108:713 + 0x02, 0xCD, 0x10, 0x08, // 109:717 + 0x02, 0xDD, 0x0A, 0x06, // 110:733 + 0x02, 0xE7, 0x0A, 0x06, // 111:743 + 0x02, 0xF1, 0x0A, 0x06, // 112:753 + 0x02, 0xFB, 0x0A, 0x06, // 113:763 + 0x03, 0x05, 0x05, 0x03, // 114:773 + 0x03, 0x0A, 0x08, 0x05, // 115:778 + 0x03, 0x12, 0x06, 0x03, // 116:786 + 0x03, 0x18, 0x0A, 0x06, // 117:792 + 0x03, 0x22, 0x09, 0x05, // 118:802 + 0x03, 0x2B, 0x0E, 0x07, // 119:811 + 0x03, 0x39, 0x0A, 0x05, // 120:825 + 0x03, 0x43, 0x09, 0x05, // 121:835 + 0x03, 0x4C, 0x0A, 0x05, // 122:844 + 0x03, 0x56, 0x06, 0x03, // 123:854 + 0x03, 0x5C, 0x04, 0x03, // 124:860 + 0x03, 0x60, 0x05, 0x03, // 125:864 + 0x03, 0x65, 0x09, 0x06, // 126:869 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 128:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 129:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 130:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 131:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 132:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 133:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 134:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 135:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 136:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 137:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 138:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 139:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 140:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 141:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 142:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 143:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 144:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 145:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 146:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 147:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 148:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 149:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 150:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 151:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 152:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 153:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 154:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 155:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 156:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 157:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 158:65535 + 0xFF, 0xFF, 0x00, 0x0A, // 159:65535 + 0xFF, 0xFF, 0x00, 0x03, // 160:65535 + 0x03, 0x6E, 0x04, 0x03, // 161:878 + 0x03, 0x72, 0x0A, 0x06, // 162:882 + 0x03, 0x7C, 0x0C, 0x06, // 163:892 + 0x03, 0x88, 0x0A, 0x06, // 164:904 + 0x03, 0x92, 0x0A, 0x06, // 165:914 + 0x03, 0x9C, 0x04, 0x03, // 166:924 + 0x03, 0xA0, 0x0A, 0x06, // 167:928 + 0x03, 0xAA, 0x05, 0x03, // 168:938 + 0x03, 0xAF, 0x0D, 0x07, // 169:943 + 0x03, 0xBC, 0x07, 0x04, // 170:956 + 0x03, 0xC3, 0x0A, 0x06, // 171:963 + 0x03, 0xCD, 0x09, 0x06, // 172:973 + 0x03, 0xD6, 0x03, 0x03, // 173:982 + 0x03, 0xD9, 0x0D, 0x07, // 174:985 + 0x03, 0xE6, 0x0B, 0x06, // 175:998 + 0x03, 0xF1, 0x07, 0x04, // 176:1009 + 0x03, 0xF8, 0x0A, 0x05, // 177:1016 + 0x04, 0x02, 0x05, 0x03, // 178:1026 + 0x04, 0x07, 0x05, 0x03, // 179:1031 + 0x04, 0x0C, 0x05, 0x03, // 180:1036 + 0x04, 0x11, 0x0A, 0x06, // 181:1041 + 0x04, 0x1B, 0x09, 0x05, // 182:1051 + 0x04, 0x24, 0x03, 0x03, // 183:1060 + 0x04, 0x27, 0x06, 0x03, // 184:1063 + 0x04, 0x2D, 0x05, 0x03, // 185:1069 + 0x04, 0x32, 0x07, 0x04, // 186:1074 + 0x04, 0x39, 0x0A, 0x06, // 187:1081 + 0x04, 0x43, 0x10, 0x08, // 188:1091 + 0x04, 0x53, 0x10, 0x08, // 189:1107 + 0x04, 0x63, 0x10, 0x08, // 190:1123 + 0x04, 0x73, 0x0A, 0x06, // 191:1139 + 0x04, 0x7D, 0x0E, 0x07, // 192:1149 + 0x04, 0x8B, 0x0E, 0x07, // 193:1163 + 0x04, 0x99, 0x0E, 0x07, // 194:1177 + 0x04, 0xA7, 0x0E, 0x07, // 195:1191 + 0x04, 0xB5, 0x0E, 0x07, // 196:1205 + 0x04, 0xC3, 0x0E, 0x07, // 197:1219 + 0x04, 0xD1, 0x12, 0x0A, // 198:1233 + 0x04, 0xE3, 0x0C, 0x07, // 199:1251 + 0x04, 0xEF, 0x0C, 0x07, // 200:1263 + 0x04, 0xFB, 0x0C, 0x07, // 201:1275 + 0x05, 0x07, 0x0C, 0x07, // 202:1287 + 0x05, 0x13, 0x0C, 0x07, // 203:1299 + 0x05, 0x1F, 0x05, 0x03, // 204:1311 + 0x05, 0x24, 0x04, 0x03, // 205:1316 + 0x05, 0x28, 0x04, 0x03, // 206:1320 + 0x05, 0x2C, 0x05, 0x03, // 207:1324 + 0x05, 0x31, 0x0B, 0x07, // 208:1329 + 0x05, 0x3C, 0x0C, 0x07, // 209:1340 + 0x05, 0x48, 0x0E, 0x08, // 210:1352 + 0x05, 0x56, 0x0E, 0x08, // 211:1366 + 0x05, 0x64, 0x0E, 0x08, // 212:1380 + 0x05, 0x72, 0x0E, 0x08, // 213:1394 + 0x05, 0x80, 0x0E, 0x08, // 214:1408 + 0x05, 0x8E, 0x0A, 0x06, // 215:1422 + 0x05, 0x98, 0x0D, 0x08, // 216:1432 + 0x05, 0xA5, 0x0C, 0x07, // 217:1445 + 0x05, 0xB1, 0x0C, 0x07, // 218:1457 + 0x05, 0xBD, 0x0C, 0x07, // 219:1469 + 0x05, 0xC9, 0x0C, 0x07, // 220:1481 + 0x05, 0xD5, 0x0D, 0x07, // 221:1493 + 0x05, 0xE2, 0x0B, 0x07, // 222:1506 + 0x05, 0xED, 0x0C, 0x06, // 223:1517 + 0x05, 0xF9, 0x0A, 0x06, // 224:1529 + 0x06, 0x03, 0x0A, 0x06, // 225:1539 + 0x06, 0x0D, 0x0A, 0x06, // 226:1549 + 0x06, 0x17, 0x0A, 0x06, // 227:1559 + 0x06, 0x21, 0x0A, 0x06, // 228:1569 + 0x06, 0x2B, 0x0A, 0x06, // 229:1579 + 0x06, 0x35, 0x10, 0x09, // 230:1589 + 0x06, 0x45, 0x0A, 0x05, // 231:1605 + 0x06, 0x4F, 0x0A, 0x06, // 232:1615 + 0x06, 0x59, 0x0A, 0x06, // 233:1625 + 0x06, 0x63, 0x0A, 0x06, // 234:1635 + 0x06, 0x6D, 0x0A, 0x06, // 235:1645 + 0x06, 0x77, 0x05, 0x03, // 236:1655 + 0x06, 0x7C, 0x04, 0x03, // 237:1660 + 0x06, 0x80, 0x05, 0x03, // 238:1664 + 0x06, 0x85, 0x05, 0x03, // 239:1669 + 0x06, 0x8A, 0x0A, 0x06, // 240:1674 + 0x06, 0x94, 0x0A, 0x06, // 241:1684 + 0x06, 0x9E, 0x0A, 0x06, // 242:1694 + 0x06, 0xA8, 0x0A, 0x06, // 243:1704 + 0x06, 0xB2, 0x0A, 0x06, // 244:1714 + 0x06, 0xBC, 0x0A, 0x06, // 245:1724 + 0x06, 0xC6, 0x0A, 0x06, // 246:1734 + 0x06, 0xD0, 0x09, 0x05, // 247:1744 + 0x06, 0xD9, 0x0A, 0x06, // 248:1753 + 0x06, 0xE3, 0x0A, 0x06, // 249:1763 + 0x06, 0xED, 0x0A, 0x06, // 250:1773 + 0x06, 0xF7, 0x0A, 0x06, // 251:1783 + 0x07, 0x01, 0x0A, 0x06, // 252:1793 + 0x07, 0x0B, 0x09, 0x05, // 253:1803 + 0x07, 0x14, 0x0A, 0x06, // 254:1812 + 0x07, 0x1E, 0x09, 0x05, // 255:1822 + + // Font Data: + 0x00,0x00,0xF8,0x02, // 33 + 0x38,0x00,0x00,0x00,0x38, // 34 + 0xA0,0x03,0xE0,0x00,0xB8,0x03,0xE0,0x00,0xB8, // 35 + 0x30,0x01,0x28,0x02,0xF8,0x07,0x48,0x02,0x90,0x01, // 36 + 0x00,0x00,0x30,0x00,0x48,0x00,0x30,0x03,0xC0,0x00,0xB0,0x01,0x48,0x02,0x80,0x01, // 37 + 0x80,0x01,0x50,0x02,0x68,0x02,0xA8,0x02,0x18,0x01,0x80,0x03,0x80,0x02, // 38 + 0x38, // 39 + 0xE0,0x03,0x10,0x04,0x08,0x08, // 40 + 0x08,0x08,0x10,0x04,0xE0,0x03, // 41 + 0x28,0x00,0x18,0x00,0x28, // 42 + 0x40,0x00,0x40,0x00,0xF0,0x01,0x40,0x00,0x40, // 43 + 0x00,0x00,0x00,0x06, // 44 + 0x80,0x00,0x80, // 45 + 0x00,0x00,0x00,0x02, // 46 + 0x00,0x03,0xE0,0x00,0x18, // 47 + 0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 48 + 0x00,0x00,0x20,0x00,0x10,0x00,0xF8,0x03, // 49 + 0x10,0x02,0x08,0x03,0x88,0x02,0x48,0x02,0x30,0x02, // 50 + 0x10,0x01,0x08,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 51 + 0xC0,0x00,0xA0,0x00,0x90,0x00,0x88,0x00,0xF8,0x03,0x80, // 52 + 0x60,0x01,0x38,0x02,0x28,0x02,0x28,0x02,0xC8,0x01, // 53 + 0xF0,0x01,0x28,0x02,0x28,0x02,0x28,0x02,0xD0,0x01, // 54 + 0x08,0x00,0x08,0x03,0xC8,0x00,0x38,0x00,0x08, // 55 + 0xB0,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 56 + 0x70,0x01,0x88,0x02,0x88,0x02,0x88,0x02,0xF0,0x01, // 57 + 0x00,0x00,0x20,0x02, // 58 + 0x00,0x00,0x20,0x06, // 59 + 0x00,0x00,0x40,0x00,0xA0,0x00,0xA0,0x00,0x10,0x01, // 60 + 0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0, // 61 + 0x00,0x00,0x10,0x01,0xA0,0x00,0xA0,0x00,0x40, // 62 + 0x10,0x00,0x08,0x00,0x08,0x00,0xC8,0x02,0x48,0x00,0x30, // 63 + 0x00,0x00,0xC0,0x03,0x30,0x04,0xD0,0x09,0x28,0x0A,0x28,0x0A,0xC8,0x0B,0x68,0x0A,0x10,0x05,0xE0,0x04, // 64 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x88,0x00,0xB0,0x00,0xC0,0x01,0x00,0x02, // 65 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0xF0,0x01, // 66 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x10,0x01, // 67 + 0x00,0x00,0xF8,0x03,0x08,0x02,0x08,0x02,0x10,0x01,0xE0, // 68 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0x48,0x02, // 69 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x08, // 70 + 0x00,0x00,0xE0,0x00,0x10,0x01,0x08,0x02,0x48,0x02,0x50,0x01,0xC0, // 71 + 0x00,0x00,0xF8,0x03,0x40,0x00,0x40,0x00,0x40,0x00,0xF8,0x03, // 72 + 0x00,0x00,0xF8,0x03, // 73 + 0x00,0x03,0x00,0x02,0x00,0x02,0xF8,0x01, // 74 + 0x00,0x00,0xF8,0x03,0x80,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 75 + 0x00,0x00,0xF8,0x03,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 76 + 0x00,0x00,0xF8,0x03,0x30,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x30,0x00,0xF8,0x03, // 77 + 0x00,0x00,0xF8,0x03,0x30,0x00,0x40,0x00,0x80,0x01,0xF8,0x03, // 78 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 79 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x48,0x00,0x30, // 80 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x03,0x08,0x03,0xF0,0x02, // 81 + 0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0xC8,0x00,0x30,0x03, // 82 + 0x00,0x00,0x30,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0x90,0x01, // 83 + 0x00,0x00,0x08,0x00,0x08,0x00,0xF8,0x03,0x08,0x00,0x08, // 84 + 0x00,0x00,0xF8,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0xF8,0x01, // 85 + 0x08,0x00,0x70,0x00,0x80,0x01,0x00,0x02,0x80,0x01,0x70,0x00,0x08, // 86 + 0x18,0x00,0xE0,0x01,0x00,0x02,0xF0,0x01,0x08,0x00,0xF0,0x01,0x00,0x02,0xE0,0x01,0x18, // 87 + 0x00,0x02,0x08,0x01,0x90,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 88 + 0x08,0x00,0x10,0x00,0x20,0x00,0xC0,0x03,0x20,0x00,0x10,0x00,0x08, // 89 + 0x08,0x03,0x88,0x02,0xC8,0x02,0x68,0x02,0x38,0x02,0x18,0x02, // 90 + 0x00,0x00,0xF8,0x0F,0x08,0x08, // 91 + 0x18,0x00,0xE0,0x00,0x00,0x03, // 92 + 0x08,0x08,0xF8,0x0F, // 93 + 0x40,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x40, // 94 + 0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08, // 95 + 0x08,0x00,0x10, // 96 + 0x00,0x00,0x00,0x03,0xA0,0x02,0xA0,0x02,0xE0,0x03, // 97 + 0x00,0x00,0xF8,0x03,0x20,0x02,0x20,0x02,0xC0,0x01, // 98 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0x40,0x01, // 99 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xF8,0x03, // 100 + 0x00,0x00,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 101 + 0x20,0x00,0xF0,0x03,0x28, // 102 + 0x00,0x00,0xC0,0x05,0x20,0x0A,0x20,0x0A,0xE0,0x07, // 103 + 0x00,0x00,0xF8,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 104 + 0x00,0x00,0xE8,0x03, // 105 + 0x00,0x08,0xE8,0x07, // 106 + 0xF8,0x03,0x80,0x00,0xC0,0x01,0x20,0x02, // 107 + 0x00,0x00,0xF8,0x03, // 108 + 0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 109 + 0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 110 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xC0,0x01, // 111 + 0x00,0x00,0xE0,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 112 + 0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xE0,0x0F, // 113 + 0x00,0x00,0xE0,0x03,0x20, // 114 + 0x40,0x02,0xA0,0x02,0xA0,0x02,0x20,0x01, // 115 + 0x20,0x00,0xF8,0x03,0x20,0x02, // 116 + 0x00,0x00,0xE0,0x01,0x00,0x02,0x00,0x02,0xE0,0x03, // 117 + 0x20,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x20, // 118 + 0xE0,0x01,0x00,0x02,0xC0,0x01,0x20,0x00,0xC0,0x01,0x00,0x02,0xE0,0x01, // 119 + 0x20,0x02,0x40,0x01,0x80,0x00,0x40,0x01,0x20,0x02, // 120 + 0x20,0x00,0xC0,0x09,0x00,0x06,0xC0,0x01,0x20, // 121 + 0x20,0x02,0x20,0x03,0xA0,0x02,0x60,0x02,0x20,0x02, // 122 + 0x80,0x00,0x78,0x0F,0x08,0x08, // 123 + 0x00,0x00,0xF8,0x0F, // 124 + 0x08,0x08,0x78,0x0F,0x80, // 125 + 0xC0,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0xC0, // 126 + 0x00,0x00,0xA0,0x0F, // 161 + 0x00,0x00,0xC0,0x01,0xA0,0x0F,0x78,0x02,0x40,0x01, // 162 + 0x40,0x02,0x70,0x03,0xC8,0x02,0x48,0x02,0x08,0x02,0x10,0x02, // 163 + 0x00,0x00,0xE0,0x01,0x20,0x01,0x20,0x01,0xE0,0x01, // 164 + 0x48,0x01,0x70,0x01,0xC0,0x03,0x70,0x01,0x48,0x01, // 165 + 0x00,0x00,0x38,0x0F, // 166 + 0xD0,0x04,0x28,0x09,0x48,0x09,0x48,0x0A,0x90,0x05, // 167 + 0x08,0x00,0x00,0x00,0x08, // 168 + 0xE0,0x00,0x10,0x01,0x48,0x02,0xA8,0x02,0xA8,0x02,0x10,0x01,0xE0, // 169 + 0x68,0x00,0x68,0x00,0x68,0x00,0x78, // 170 + 0x00,0x00,0x80,0x01,0x40,0x02,0x80,0x01,0x40,0x02, // 171 + 0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0, // 172 + 0x80,0x00,0x80, // 173 + 0xE0,0x00,0x10,0x01,0xE8,0x02,0x68,0x02,0xC8,0x02,0x10,0x01,0xE0, // 174 + 0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 175 + 0x00,0x00,0x38,0x00,0x28,0x00,0x38, // 176 + 0x40,0x02,0x40,0x02,0xF0,0x03,0x40,0x02,0x40,0x02, // 177 + 0x48,0x00,0x68,0x00,0x58, // 178 + 0x48,0x00,0x58,0x00,0x68, // 179 + 0x00,0x00,0x10,0x00,0x08, // 180 + 0x00,0x00,0xE0,0x0F,0x00,0x02,0x00,0x02,0xE0,0x03, // 181 + 0x70,0x00,0xF8,0x0F,0x08,0x00,0xF8,0x0F,0x08, // 182 + 0x00,0x00,0x40, // 183 + 0x00,0x00,0x00,0x14,0x00,0x18, // 184 + 0x00,0x00,0x10,0x00,0x78, // 185 + 0x30,0x00,0x48,0x00,0x48,0x00,0x30, // 186 + 0x00,0x00,0x40,0x02,0x80,0x01,0x40,0x02,0x80,0x01, // 187 + 0x00,0x00,0x10,0x02,0x78,0x01,0xC0,0x00,0x20,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 188 + 0x00,0x00,0x10,0x02,0x78,0x01,0x80,0x00,0x60,0x00,0x50,0x02,0x48,0x03,0xC0,0x02, // 189 + 0x48,0x00,0x58,0x00,0x68,0x03,0x80,0x00,0x60,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 190 + 0x00,0x00,0x00,0x06,0x00,0x09,0xA0,0x09,0x00,0x04, // 191 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 192 + 0x00,0x02,0xC0,0x01,0xB0,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 193 + 0x00,0x02,0xC0,0x01,0xB2,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 194 + 0x00,0x02,0xC2,0x01,0xB1,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 195 + 0x00,0x02,0xC0,0x01,0xB2,0x00,0x88,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 196 + 0x00,0x02,0xC0,0x01,0xBE,0x00,0x8A,0x00,0xBE,0x00,0xC0,0x01,0x00,0x02, // 197 + 0x00,0x03,0xC0,0x00,0xE0,0x00,0x98,0x00,0x88,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02, // 198 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x16,0x08,0x1A,0x10,0x01, // 199 + 0x00,0x00,0xF8,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 200 + 0x00,0x00,0xF8,0x03,0x48,0x02,0x4A,0x02,0x49,0x02,0x48,0x02, // 201 + 0x00,0x00,0xFA,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 202 + 0x00,0x00,0xF8,0x03,0x4A,0x02,0x48,0x02,0x4A,0x02,0x48,0x02, // 203 + 0x00,0x00,0xF9,0x03,0x02, // 204 + 0x02,0x00,0xF9,0x03, // 205 + 0x01,0x00,0xFA,0x03, // 206 + 0x02,0x00,0xF8,0x03,0x02, // 207 + 0x40,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x10,0x01,0xE0, // 208 + 0x00,0x00,0xFA,0x03,0x31,0x00,0x42,0x00,0x81,0x01,0xF8,0x03, // 209 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x09,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 210 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x08,0x02,0xF0,0x01, // 211 + 0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x0A,0x02,0xF0,0x01, // 212 + 0x00,0x00,0xF0,0x01,0x0A,0x02,0x09,0x02,0x0A,0x02,0x09,0x02,0xF0,0x01, // 213 + 0x00,0x00,0xF0,0x01,0x0A,0x02,0x08,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 214 + 0x10,0x01,0xA0,0x00,0xE0,0x00,0xA0,0x00,0x10,0x01, // 215 + 0x00,0x00,0xF0,0x02,0x08,0x03,0xC8,0x02,0x28,0x02,0x18,0x03,0xE8, // 216 + 0x00,0x00,0xF8,0x01,0x01,0x02,0x02,0x02,0x00,0x02,0xF8,0x01, // 217 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x00,0x02,0xF8,0x01, // 218 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x02,0x02,0xF8,0x01, // 219 + 0x00,0x00,0xF8,0x01,0x02,0x02,0x00,0x02,0x02,0x02,0xF8,0x01, // 220 + 0x08,0x00,0x10,0x00,0x20,0x00,0xC2,0x03,0x21,0x00,0x10,0x00,0x08, // 221 + 0x00,0x00,0xF8,0x03,0x10,0x01,0x10,0x01,0x10,0x01,0xE0, // 222 + 0x00,0x00,0xF0,0x03,0x08,0x01,0x48,0x02,0xB0,0x02,0x80,0x01, // 223 + 0x00,0x00,0x00,0x03,0xA4,0x02,0xA8,0x02,0xE0,0x03, // 224 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE0,0x03, // 225 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE8,0x03, // 226 + 0x00,0x00,0x08,0x03,0xA4,0x02,0xA8,0x02,0xE4,0x03, // 227 + 0x00,0x00,0x00,0x03,0xA8,0x02,0xA0,0x02,0xE8,0x03, // 228 + 0x00,0x00,0x00,0x03,0xAE,0x02,0xAA,0x02,0xEE,0x03, // 229 + 0x00,0x00,0x40,0x03,0xA0,0x02,0xA0,0x02,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 230 + 0x00,0x00,0xC0,0x01,0x20,0x16,0x20,0x1A,0x40,0x01, // 231 + 0x00,0x00,0xC0,0x01,0xA4,0x02,0xA8,0x02,0xC0,0x02, // 232 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC0,0x02, // 233 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC8,0x02, // 234 + 0x00,0x00,0xC0,0x01,0xA8,0x02,0xA0,0x02,0xC8,0x02, // 235 + 0x00,0x00,0xE4,0x03,0x08, // 236 + 0x08,0x00,0xE4,0x03, // 237 + 0x08,0x00,0xE4,0x03,0x08, // 238 + 0x08,0x00,0xE0,0x03,0x08, // 239 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x38,0x02,0xE0,0x01, // 240 + 0x00,0x00,0xE8,0x03,0x24,0x00,0x28,0x00,0xC4,0x03, // 241 + 0x00,0x00,0xC0,0x01,0x24,0x02,0x28,0x02,0xC0,0x01, // 242 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC0,0x01, // 243 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC8,0x01, // 244 + 0x00,0x00,0xC8,0x01,0x24,0x02,0x28,0x02,0xC4,0x01, // 245 + 0x00,0x00,0xC0,0x01,0x28,0x02,0x20,0x02,0xC8,0x01, // 246 + 0x40,0x00,0x40,0x00,0x50,0x01,0x40,0x00,0x40, // 247 + 0x00,0x00,0xC0,0x02,0xA0,0x03,0x60,0x02,0xA0,0x01, // 248 + 0x00,0x00,0xE0,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 249 + 0x00,0x00,0xE0,0x01,0x08,0x02,0x04,0x02,0xE0,0x03, // 250 + 0x00,0x00,0xE8,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 251 + 0x00,0x00,0xE0,0x01,0x08,0x02,0x00,0x02,0xE8,0x03, // 252 + 0x20,0x00,0xC0,0x09,0x08,0x06,0xC4,0x01,0x20, // 253 + 0x00,0x00,0xF8,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 254 + 0x20,0x00,0xC8,0x09,0x00,0x06,0xC8,0x01,0x20 // 255 +}; + +const uint8_t ArialMT_Plain_16[] PROGMEM = { + 0x10, // Width: 16 + 0x13, // Height: 19 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x04, // 32:65535 + 0x00, 0x00, 0x08, 0x04, // 33:0 + 0x00, 0x08, 0x0D, 0x06, // 34:8 + 0x00, 0x15, 0x1A, 0x09, // 35:21 + 0x00, 0x2F, 0x17, 0x09, // 36:47 + 0x00, 0x46, 0x26, 0x0E, // 37:70 + 0x00, 0x6C, 0x1D, 0x0B, // 38:108 + 0x00, 0x89, 0x04, 0x03, // 39:137 + 0x00, 0x8D, 0x0C, 0x05, // 40:141 + 0x00, 0x99, 0x0B, 0x05, // 41:153 + 0x00, 0xA4, 0x0D, 0x06, // 42:164 + 0x00, 0xB1, 0x17, 0x09, // 43:177 + 0x00, 0xC8, 0x09, 0x04, // 44:200 + 0x00, 0xD1, 0x0B, 0x05, // 45:209 + 0x00, 0xDC, 0x08, 0x04, // 46:220 + 0x00, 0xE4, 0x0A, 0x04, // 47:228 + 0x00, 0xEE, 0x17, 0x09, // 48:238 + 0x01, 0x05, 0x11, 0x09, // 49:261 + 0x01, 0x16, 0x17, 0x09, // 50:278 + 0x01, 0x2D, 0x17, 0x09, // 51:301 + 0x01, 0x44, 0x17, 0x09, // 52:324 + 0x01, 0x5B, 0x17, 0x09, // 53:347 + 0x01, 0x72, 0x17, 0x09, // 54:370 + 0x01, 0x89, 0x16, 0x09, // 55:393 + 0x01, 0x9F, 0x17, 0x09, // 56:415 + 0x01, 0xB6, 0x17, 0x09, // 57:438 + 0x01, 0xCD, 0x05, 0x04, // 58:461 + 0x01, 0xD2, 0x06, 0x04, // 59:466 + 0x01, 0xD8, 0x17, 0x09, // 60:472 + 0x01, 0xEF, 0x17, 0x09, // 61:495 + 0x02, 0x06, 0x17, 0x09, // 62:518 + 0x02, 0x1D, 0x16, 0x09, // 63:541 + 0x02, 0x33, 0x2F, 0x10, // 64:563 + 0x02, 0x62, 0x1D, 0x0B, // 65:610 + 0x02, 0x7F, 0x1D, 0x0B, // 66:639 + 0x02, 0x9C, 0x20, 0x0C, // 67:668 + 0x02, 0xBC, 0x20, 0x0C, // 68:700 + 0x02, 0xDC, 0x1D, 0x0B, // 69:732 + 0x02, 0xF9, 0x19, 0x0A, // 70:761 + 0x03, 0x12, 0x20, 0x0C, // 71:786 + 0x03, 0x32, 0x1D, 0x0C, // 72:818 + 0x03, 0x4F, 0x05, 0x04, // 73:847 + 0x03, 0x54, 0x14, 0x08, // 74:852 + 0x03, 0x68, 0x1D, 0x0B, // 75:872 + 0x03, 0x85, 0x17, 0x09, // 76:901 + 0x03, 0x9C, 0x23, 0x0D, // 77:924 + 0x03, 0xBF, 0x1D, 0x0C, // 78:959 + 0x03, 0xDC, 0x20, 0x0C, // 79:988 + 0x03, 0xFC, 0x1C, 0x0B, // 80:1020 + 0x04, 0x18, 0x20, 0x0C, // 81:1048 + 0x04, 0x38, 0x1D, 0x0C, // 82:1080 + 0x04, 0x55, 0x1D, 0x0B, // 83:1109 + 0x04, 0x72, 0x19, 0x0A, // 84:1138 + 0x04, 0x8B, 0x1D, 0x0C, // 85:1163 + 0x04, 0xA8, 0x1C, 0x0B, // 86:1192 + 0x04, 0xC4, 0x2B, 0x0F, // 87:1220 + 0x04, 0xEF, 0x20, 0x0B, // 88:1263 + 0x05, 0x0F, 0x19, 0x0B, // 89:1295 + 0x05, 0x28, 0x1A, 0x0A, // 90:1320 + 0x05, 0x42, 0x0C, 0x04, // 91:1346 + 0x05, 0x4E, 0x0B, 0x04, // 92:1358 + 0x05, 0x59, 0x09, 0x04, // 93:1369 + 0x05, 0x62, 0x14, 0x08, // 94:1378 + 0x05, 0x76, 0x1B, 0x09, // 95:1398 + 0x05, 0x91, 0x07, 0x05, // 96:1425 + 0x05, 0x98, 0x17, 0x09, // 97:1432 + 0x05, 0xAF, 0x17, 0x09, // 98:1455 + 0x05, 0xC6, 0x14, 0x08, // 99:1478 + 0x05, 0xDA, 0x17, 0x09, // 100:1498 + 0x05, 0xF1, 0x17, 0x09, // 101:1521 + 0x06, 0x08, 0x0A, 0x04, // 102:1544 + 0x06, 0x12, 0x17, 0x09, // 103:1554 + 0x06, 0x29, 0x14, 0x09, // 104:1577 + 0x06, 0x3D, 0x05, 0x04, // 105:1597 + 0x06, 0x42, 0x06, 0x04, // 106:1602 + 0x06, 0x48, 0x17, 0x08, // 107:1608 + 0x06, 0x5F, 0x05, 0x04, // 108:1631 + 0x06, 0x64, 0x23, 0x0D, // 109:1636 + 0x06, 0x87, 0x14, 0x09, // 110:1671 + 0x06, 0x9B, 0x17, 0x09, // 111:1691 + 0x06, 0xB2, 0x17, 0x09, // 112:1714 + 0x06, 0xC9, 0x18, 0x09, // 113:1737 + 0x06, 0xE1, 0x0D, 0x05, // 114:1761 + 0x06, 0xEE, 0x14, 0x08, // 115:1774 + 0x07, 0x02, 0x0B, 0x04, // 116:1794 + 0x07, 0x0D, 0x14, 0x09, // 117:1805 + 0x07, 0x21, 0x13, 0x08, // 118:1825 + 0x07, 0x34, 0x1F, 0x0C, // 119:1844 + 0x07, 0x53, 0x14, 0x08, // 120:1875 + 0x07, 0x67, 0x13, 0x08, // 121:1895 + 0x07, 0x7A, 0x14, 0x08, // 122:1914 + 0x07, 0x8E, 0x0F, 0x05, // 123:1934 + 0x07, 0x9D, 0x06, 0x04, // 124:1949 + 0x07, 0xA3, 0x0E, 0x05, // 125:1955 + 0x07, 0xB1, 0x17, 0x09, // 126:1969 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x10, // 128:65535 + 0xFF, 0xFF, 0x00, 0x10, // 129:65535 + 0xFF, 0xFF, 0x00, 0x10, // 130:65535 + 0xFF, 0xFF, 0x00, 0x10, // 131:65535 + 0xFF, 0xFF, 0x00, 0x10, // 132:65535 + 0xFF, 0xFF, 0x00, 0x10, // 133:65535 + 0xFF, 0xFF, 0x00, 0x10, // 134:65535 + 0xFF, 0xFF, 0x00, 0x10, // 135:65535 + 0xFF, 0xFF, 0x00, 0x10, // 136:65535 + 0xFF, 0xFF, 0x00, 0x10, // 137:65535 + 0xFF, 0xFF, 0x00, 0x10, // 138:65535 + 0xFF, 0xFF, 0x00, 0x10, // 139:65535 + 0xFF, 0xFF, 0x00, 0x10, // 140:65535 + 0xFF, 0xFF, 0x00, 0x10, // 141:65535 + 0xFF, 0xFF, 0x00, 0x10, // 142:65535 + 0xFF, 0xFF, 0x00, 0x10, // 143:65535 + 0xFF, 0xFF, 0x00, 0x10, // 144:65535 + 0xFF, 0xFF, 0x00, 0x10, // 145:65535 + 0xFF, 0xFF, 0x00, 0x10, // 146:65535 + 0xFF, 0xFF, 0x00, 0x10, // 147:65535 + 0xFF, 0xFF, 0x00, 0x10, // 148:65535 + 0xFF, 0xFF, 0x00, 0x10, // 149:65535 + 0xFF, 0xFF, 0x00, 0x10, // 150:65535 + 0xFF, 0xFF, 0x00, 0x10, // 151:65535 + 0xFF, 0xFF, 0x00, 0x10, // 152:65535 + 0xFF, 0xFF, 0x00, 0x10, // 153:65535 + 0xFF, 0xFF, 0x00, 0x10, // 154:65535 + 0xFF, 0xFF, 0x00, 0x10, // 155:65535 + 0xFF, 0xFF, 0x00, 0x10, // 156:65535 + 0xFF, 0xFF, 0x00, 0x10, // 157:65535 + 0xFF, 0xFF, 0x00, 0x10, // 158:65535 + 0xFF, 0xFF, 0x00, 0x10, // 159:65535 + 0xFF, 0xFF, 0x00, 0x04, // 160:65535 + 0x07, 0xC8, 0x09, 0x05, // 161:1992 + 0x07, 0xD1, 0x17, 0x09, // 162:2001 + 0x07, 0xE8, 0x17, 0x09, // 163:2024 + 0x07, 0xFF, 0x14, 0x09, // 164:2047 + 0x08, 0x13, 0x1A, 0x09, // 165:2067 + 0x08, 0x2D, 0x06, 0x04, // 166:2093 + 0x08, 0x33, 0x17, 0x09, // 167:2099 + 0x08, 0x4A, 0x07, 0x05, // 168:2122 + 0x08, 0x51, 0x23, 0x0C, // 169:2129 + 0x08, 0x74, 0x0E, 0x06, // 170:2164 + 0x08, 0x82, 0x14, 0x09, // 171:2178 + 0x08, 0x96, 0x17, 0x09, // 172:2198 + 0x08, 0xAD, 0x0B, 0x05, // 173:2221 + 0x08, 0xB8, 0x23, 0x0C, // 174:2232 + 0x08, 0xDB, 0x19, 0x09, // 175:2267 + 0x08, 0xF4, 0x0D, 0x06, // 176:2292 + 0x09, 0x01, 0x17, 0x09, // 177:2305 + 0x09, 0x18, 0x0E, 0x05, // 178:2328 + 0x09, 0x26, 0x0D, 0x05, // 179:2342 + 0x09, 0x33, 0x0A, 0x05, // 180:2355 + 0x09, 0x3D, 0x17, 0x09, // 181:2365 + 0x09, 0x54, 0x19, 0x09, // 182:2388 + 0x09, 0x6D, 0x08, 0x05, // 183:2413 + 0x09, 0x75, 0x0C, 0x05, // 184:2421 + 0x09, 0x81, 0x0B, 0x05, // 185:2433 + 0x09, 0x8C, 0x0D, 0x06, // 186:2444 + 0x09, 0x99, 0x17, 0x09, // 187:2457 + 0x09, 0xB0, 0x26, 0x0D, // 188:2480 + 0x09, 0xD6, 0x26, 0x0D, // 189:2518 + 0x09, 0xFC, 0x26, 0x0D, // 190:2556 + 0x0A, 0x22, 0x1A, 0x0A, // 191:2594 + 0x0A, 0x3C, 0x1D, 0x0B, // 192:2620 + 0x0A, 0x59, 0x1D, 0x0B, // 193:2649 + 0x0A, 0x76, 0x1D, 0x0B, // 194:2678 + 0x0A, 0x93, 0x1D, 0x0B, // 195:2707 + 0x0A, 0xB0, 0x1D, 0x0B, // 196:2736 + 0x0A, 0xCD, 0x1D, 0x0B, // 197:2765 + 0x0A, 0xEA, 0x2C, 0x10, // 198:2794 + 0x0B, 0x16, 0x20, 0x0C, // 199:2838 + 0x0B, 0x36, 0x1D, 0x0B, // 200:2870 + 0x0B, 0x53, 0x1D, 0x0B, // 201:2899 + 0x0B, 0x70, 0x1D, 0x0B, // 202:2928 + 0x0B, 0x8D, 0x1D, 0x0B, // 203:2957 + 0x0B, 0xAA, 0x05, 0x04, // 204:2986 + 0x0B, 0xAF, 0x07, 0x04, // 205:2991 + 0x0B, 0xB6, 0x0A, 0x04, // 206:2998 + 0x0B, 0xC0, 0x07, 0x04, // 207:3008 + 0x0B, 0xC7, 0x20, 0x0C, // 208:3015 + 0x0B, 0xE7, 0x1D, 0x0C, // 209:3047 + 0x0C, 0x04, 0x20, 0x0C, // 210:3076 + 0x0C, 0x24, 0x20, 0x0C, // 211:3108 + 0x0C, 0x44, 0x20, 0x0C, // 212:3140 + 0x0C, 0x64, 0x20, 0x0C, // 213:3172 + 0x0C, 0x84, 0x20, 0x0C, // 214:3204 + 0x0C, 0xA4, 0x17, 0x09, // 215:3236 + 0x0C, 0xBB, 0x20, 0x0C, // 216:3259 + 0x0C, 0xDB, 0x1D, 0x0C, // 217:3291 + 0x0C, 0xF8, 0x1D, 0x0C, // 218:3320 + 0x0D, 0x15, 0x1D, 0x0C, // 219:3349 + 0x0D, 0x32, 0x1D, 0x0C, // 220:3378 + 0x0D, 0x4F, 0x19, 0x0B, // 221:3407 + 0x0D, 0x68, 0x1D, 0x0B, // 222:3432 + 0x0D, 0x85, 0x17, 0x0A, // 223:3461 + 0x0D, 0x9C, 0x17, 0x09, // 224:3484 + 0x0D, 0xB3, 0x17, 0x09, // 225:3507 + 0x0D, 0xCA, 0x17, 0x09, // 226:3530 + 0x0D, 0xE1, 0x17, 0x09, // 227:3553 + 0x0D, 0xF8, 0x17, 0x09, // 228:3576 + 0x0E, 0x0F, 0x17, 0x09, // 229:3599 + 0x0E, 0x26, 0x29, 0x0E, // 230:3622 + 0x0E, 0x4F, 0x14, 0x08, // 231:3663 + 0x0E, 0x63, 0x17, 0x09, // 232:3683 + 0x0E, 0x7A, 0x17, 0x09, // 233:3706 + 0x0E, 0x91, 0x17, 0x09, // 234:3729 + 0x0E, 0xA8, 0x17, 0x09, // 235:3752 + 0x0E, 0xBF, 0x05, 0x04, // 236:3775 + 0x0E, 0xC4, 0x07, 0x04, // 237:3780 + 0x0E, 0xCB, 0x0A, 0x04, // 238:3787 + 0x0E, 0xD5, 0x07, 0x04, // 239:3797 + 0x0E, 0xDC, 0x17, 0x09, // 240:3804 + 0x0E, 0xF3, 0x14, 0x09, // 241:3827 + 0x0F, 0x07, 0x17, 0x09, // 242:3847 + 0x0F, 0x1E, 0x17, 0x09, // 243:3870 + 0x0F, 0x35, 0x17, 0x09, // 244:3893 + 0x0F, 0x4C, 0x17, 0x09, // 245:3916 + 0x0F, 0x63, 0x17, 0x09, // 246:3939 + 0x0F, 0x7A, 0x17, 0x09, // 247:3962 + 0x0F, 0x91, 0x17, 0x0A, // 248:3985 + 0x0F, 0xA8, 0x14, 0x09, // 249:4008 + 0x0F, 0xBC, 0x14, 0x09, // 250:4028 + 0x0F, 0xD0, 0x14, 0x09, // 251:4048 + 0x0F, 0xE4, 0x14, 0x09, // 252:4068 + 0x0F, 0xF8, 0x13, 0x08, // 253:4088 + 0x10, 0x0B, 0x17, 0x09, // 254:4107 + 0x10, 0x22, 0x13, 0x08, // 255:4130 + + // Font Data: + 0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x5F, // 33 + 0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78, // 34 + 0x80,0x08,0x00,0x80,0x78,0x00,0xC0,0x0F,0x00,0xB8,0x08,0x00,0x80,0x08,0x00,0x80,0x78,0x00,0xC0,0x0F,0x00,0xB8,0x08,0x00,0x80,0x08, // 35 + 0x00,0x00,0x00,0xE0,0x10,0x00,0x10,0x21,0x00,0x08,0x41,0x00,0xFC,0xFF,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x1C, // 36 + 0x00,0x00,0x00,0xF0,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x08,0x61,0x00,0xF0,0x18,0x00,0x00,0x06,0x00,0xC0,0x01,0x00,0x30,0x3C,0x00,0x08,0x42,0x00,0x00,0x42,0x00,0x00,0x42,0x00,0x00,0x3C, // 37 + 0x00,0x00,0x00,0x00,0x1C,0x00,0x70,0x22,0x00,0x88,0x41,0x00,0x08,0x43,0x00,0x88,0x44,0x00,0x70,0x28,0x00,0x00,0x10,0x00,0x00,0x28,0x00,0x00,0x44, // 38 + 0x00,0x00,0x00,0x78, // 39 + 0x00,0x00,0x00,0x80,0x3F,0x00,0x70,0xC0,0x01,0x08,0x00,0x02, // 40 + 0x00,0x00,0x00,0x08,0x00,0x02,0x70,0xC0,0x01,0x80,0x3F, // 41 + 0x10,0x00,0x00,0xD0,0x00,0x00,0x38,0x00,0x00,0xD0,0x00,0x00,0x10, // 42 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0xC0,0x1F,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 43 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x01, // 44 + 0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 45 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40, // 46 + 0x00,0x60,0x00,0x00,0x1E,0x00,0xE0,0x01,0x00,0x18, // 47 + 0x00,0x00,0x00,0xE0,0x1F,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0xE0,0x1F, // 48 + 0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0x00,0x10,0x00,0x00,0xF8,0x7F, // 49 + 0x00,0x00,0x00,0x20,0x40,0x00,0x10,0x60,0x00,0x08,0x50,0x00,0x08,0x48,0x00,0x08,0x44,0x00,0x10,0x43,0x00,0xE0,0x40, // 50 + 0x00,0x00,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x88,0x41,0x00,0xF0,0x22,0x00,0x00,0x1C, // 51 + 0x00,0x0C,0x00,0x00,0x0A,0x00,0x00,0x09,0x00,0xC0,0x08,0x00,0x20,0x08,0x00,0x10,0x08,0x00,0xF8,0x7F,0x00,0x00,0x08, // 52 + 0x00,0x00,0x00,0xC0,0x11,0x00,0xB8,0x20,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x08,0x21,0x00,0x08,0x1E, // 53 + 0x00,0x00,0x00,0xE0,0x1F,0x00,0x10,0x21,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x88,0x40,0x00,0x10,0x21,0x00,0x20,0x1E, // 54 + 0x00,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x78,0x00,0x08,0x07,0x00,0xC8,0x00,0x00,0x28,0x00,0x00,0x18, // 55 + 0x00,0x00,0x00,0x60,0x1C,0x00,0x90,0x22,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x90,0x22,0x00,0x60,0x1C, // 56 + 0x00,0x00,0x00,0xE0,0x11,0x00,0x10,0x22,0x00,0x08,0x44,0x00,0x08,0x44,0x00,0x08,0x44,0x00,0x10,0x22,0x00,0xE0,0x1F, // 57 + 0x00,0x00,0x00,0x40,0x40, // 58 + 0x00,0x00,0x00,0x40,0xC0,0x01, // 59 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x05,0x00,0x00,0x05,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x40,0x10, // 60 + 0x00,0x00,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08, // 61 + 0x00,0x00,0x00,0x40,0x10,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x80,0x08,0x00,0x00,0x05,0x00,0x00,0x05,0x00,0x00,0x02, // 62 + 0x00,0x00,0x00,0x60,0x00,0x00,0x10,0x00,0x00,0x08,0x00,0x00,0x08,0x5C,0x00,0x08,0x02,0x00,0x10,0x01,0x00,0xE0, // 63 + 0x00,0x00,0x00,0x00,0x3F,0x00,0xC0,0x40,0x00,0x20,0x80,0x00,0x10,0x1E,0x01,0x10,0x21,0x01,0x88,0x40,0x02,0x48,0x40,0x02,0x48,0x40,0x02,0x48,0x20,0x02,0x88,0x7C,0x02,0xC8,0x43,0x02,0x10,0x40,0x02,0x10,0x20,0x01,0x60,0x10,0x01,0x80,0x8F, // 64 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x70,0x04,0x00,0x08,0x04,0x00,0x70,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 65 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x90,0x22,0x00,0x60,0x1C, // 66 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10, // 67 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 68 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 69 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08, // 70 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x12,0x00,0x00,0x0E, // 71 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0xF8,0x7F, // 72 + 0x00,0x00,0x00,0xF8,0x7F, // 73 + 0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0xF8,0x3F, // 74 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x04,0x00,0x00,0x02,0x00,0x00,0x01,0x00,0x80,0x03,0x00,0x40,0x04,0x00,0x20,0x18,0x00,0x10,0x20,0x00,0x08,0x40, // 75 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40, // 76 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x30,0x00,0x00,0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0,0x00,0x00,0x30,0x00,0x00,0xF8,0x7F, // 77 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x10,0x00,0x00,0x60,0x00,0x00,0x80,0x00,0x00,0x00,0x03,0x00,0x00,0x04,0x00,0x00,0x18,0x00,0x00,0x20,0x00,0xF8,0x7F, // 78 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 79 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x10,0x01,0x00,0xE0, // 80 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x08,0x50,0x00,0x08,0x50,0x00,0x10,0x20,0x00,0x20,0x70,0x00,0xC0,0x4F, // 81 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x02,0x00,0x08,0x06,0x00,0x08,0x1A,0x00,0x10,0x21,0x00,0xE0,0x40, // 82 + 0x00,0x00,0x00,0x60,0x10,0x00,0x90,0x20,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x10,0x22,0x00,0x20,0x1C, // 83 + 0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0xF8,0x7F,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 84 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 85 + 0x00,0x00,0x00,0x18,0x00,0x00,0xE0,0x00,0x00,0x00,0x07,0x00,0x00,0x18,0x00,0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x07,0x00,0xE0,0x00,0x00,0x18, // 86 + 0x18,0x00,0x00,0xE0,0x01,0x00,0x00,0x1E,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x03,0x00,0x70,0x00,0x00,0x08,0x00,0x00,0x70,0x00,0x00,0x80,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1E,0x00,0xE0,0x01,0x00,0x18, // 87 + 0x00,0x40,0x00,0x08,0x20,0x00,0x10,0x10,0x00,0x60,0x0C,0x00,0x80,0x02,0x00,0x00,0x01,0x00,0x80,0x02,0x00,0x60,0x0C,0x00,0x10,0x10,0x00,0x08,0x20,0x00,0x00,0x40, // 88 + 0x08,0x00,0x00,0x30,0x00,0x00,0x40,0x00,0x00,0x80,0x01,0x00,0x00,0x7E,0x00,0x80,0x01,0x00,0x40,0x00,0x00,0x30,0x00,0x00,0x08, // 89 + 0x00,0x40,0x00,0x08,0x60,0x00,0x08,0x58,0x00,0x08,0x44,0x00,0x08,0x43,0x00,0x88,0x40,0x00,0x68,0x40,0x00,0x18,0x40,0x00,0x08,0x40, // 90 + 0x00,0x00,0x00,0xF8,0xFF,0x03,0x08,0x00,0x02,0x08,0x00,0x02, // 91 + 0x18,0x00,0x00,0xE0,0x01,0x00,0x00,0x1E,0x00,0x00,0x60, // 92 + 0x08,0x00,0x02,0x08,0x00,0x02,0xF8,0xFF,0x03, // 93 + 0x00,0x01,0x00,0xC0,0x00,0x00,0x30,0x00,0x00,0x08,0x00,0x00,0x30,0x00,0x00,0xC0,0x00,0x00,0x00,0x01, // 94 + 0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 95 + 0x00,0x00,0x00,0x08,0x00,0x00,0x10, // 96 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 97 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 98 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20, // 99 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0xF8,0x7F, // 100 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 101 + 0x40,0x00,0x00,0xF0,0x7F,0x00,0x48,0x00,0x00,0x48, // 102 + 0x00,0x00,0x00,0x00,0x1F,0x01,0x80,0x20,0x02,0x40,0x40,0x02,0x40,0x40,0x02,0x40,0x40,0x02,0x80,0x20,0x01,0xC0,0xFF, // 103 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 104 + 0x00,0x00,0x00,0xC8,0x7F, // 105 + 0x00,0x00,0x02,0xC8,0xFF,0x01, // 106 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x00,0x08,0x00,0x00,0x04,0x00,0x00,0x06,0x00,0x00,0x19,0x00,0x80,0x20,0x00,0x40,0x40, // 107 + 0x00,0x00,0x00,0xF8,0x7F, // 108 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 109 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x80,0x7F, // 110 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 111 + 0x00,0x00,0x00,0xC0,0xFF,0x03,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 112 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0xC0,0xFF,0x03, // 113 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x80,0x00,0x00,0x40,0x00,0x00,0x40, // 114 + 0x00,0x00,0x00,0x80,0x23,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x38, // 115 + 0x40,0x00,0x00,0xF0,0x7F,0x00,0x40,0x40,0x00,0x40,0x40, // 116 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 117 + 0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0, // 118 + 0xC0,0x00,0x00,0x00,0x1F,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x00,0x03,0x00,0xC0,0x00,0x00,0x00,0x03,0x00,0x00,0x1C,0x00,0x00,0x60,0x00,0x00,0x1F,0x00,0xC0, // 119 + 0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20,0x00,0x40,0x40, // 120 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x00,0x38,0x02,0x00,0xE0,0x01,0x00,0x38,0x00,0x00,0x07,0x00,0xC0, // 121 + 0x40,0x40,0x00,0x40,0x60,0x00,0x40,0x58,0x00,0x40,0x44,0x00,0x40,0x43,0x00,0xC0,0x40,0x00,0x40,0x40, // 122 + 0x00,0x04,0x00,0x00,0x04,0x00,0xF0,0xFB,0x01,0x08,0x00,0x02,0x08,0x00,0x02, // 123 + 0x00,0x00,0x00,0xF8,0xFF,0x03, // 124 + 0x08,0x00,0x02,0x08,0x00,0x02,0xF0,0xFB,0x01,0x00,0x04,0x00,0x00,0x04, // 125 + 0x00,0x02,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x01,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x01, // 126 + 0x00,0x00,0x00,0x00,0x00,0x00,0x40,0xFF,0x03, // 161 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x03,0x40,0xF0,0x00,0x40,0x4E,0x00,0xC0,0x41,0x00,0xB8,0x20,0x00,0x00,0x11, // 162 + 0x00,0x41,0x00,0xE0,0x31,0x00,0x10,0x2F,0x00,0x08,0x21,0x00,0x08,0x21,0x00,0x08,0x40,0x00,0x10,0x40,0x00,0x20,0x20, // 163 + 0x00,0x00,0x00,0x40,0x0B,0x00,0x80,0x04,0x00,0x40,0x08,0x00,0x40,0x08,0x00,0x80,0x04,0x00,0x40,0x0B, // 164 + 0x08,0x0A,0x00,0x10,0x0A,0x00,0x60,0x0A,0x00,0x80,0x0B,0x00,0x00,0x7E,0x00,0x80,0x0B,0x00,0x60,0x0A,0x00,0x10,0x0A,0x00,0x08,0x0A, // 165 + 0x00,0x00,0x00,0xF8,0xF1,0x03, // 166 + 0x00,0x86,0x00,0x70,0x09,0x01,0xC8,0x10,0x02,0x88,0x10,0x02,0x08,0x21,0x02,0x08,0x61,0x02,0x30,0xD2,0x01,0x00,0x0C, // 167 + 0x08,0x00,0x00,0x00,0x00,0x00,0x08, // 168 + 0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0xC8,0x47,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x28,0x48,0x00,0x48,0x44,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 169 + 0xD0,0x00,0x00,0x48,0x01,0x00,0x28,0x01,0x00,0x28,0x01,0x00,0xF0,0x01, // 170 + 0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20,0x00,0x00,0x04,0x00,0x00,0x1B,0x00,0x80,0x20, // 171 + 0x00,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x00,0x00,0x80,0x0F, // 172 + 0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00,0x08, // 173 + 0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0xE8,0x4F,0x00,0x28,0x41,0x00,0x28,0x41,0x00,0x28,0x43,0x00,0x28,0x45,0x00,0xC8,0x48,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 174 + 0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04,0x00,0x00,0x04, // 175 + 0x00,0x00,0x00,0x30,0x00,0x00,0x48,0x00,0x00,0x48,0x00,0x00,0x30, // 176 + 0x00,0x00,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0xE0,0x4F,0x00,0x00,0x41,0x00,0x00,0x41,0x00,0x00,0x41, // 177 + 0x10,0x01,0x00,0x88,0x01,0x00,0x48,0x01,0x00,0x48,0x01,0x00,0x30,0x01, // 178 + 0x90,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x28,0x01,0x00,0xD8, // 179 + 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x08, // 180 + 0x00,0x00,0x00,0xC0,0xFF,0x03,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 181 + 0xF0,0x00,0x00,0xF8,0x00,0x00,0xF8,0x01,0x00,0xF8,0x01,0x00,0xF8,0xFF,0x03,0x08,0x00,0x00,0x08,0x00,0x00,0xF8,0xFF,0x03,0x08, // 182 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02, // 183 + 0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x80,0x02,0x00,0x00,0x03, // 184 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x00,0x00,0xF8,0x01, // 185 + 0xF0,0x00,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0x08,0x01,0x00,0xF0, // 186 + 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04,0x00,0x80,0x20,0x00,0x00,0x1B,0x00,0x00,0x04, // 187 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x40,0x00,0xF8,0x21,0x00,0x00,0x10,0x00,0x00,0x0C,0x00,0x00,0x02,0x00,0x80,0x01,0x00,0x40,0x30,0x00,0x30,0x28,0x00,0x08,0x24,0x00,0x00,0x7E,0x00,0x00,0x20, // 188 + 0x00,0x00,0x00,0x10,0x00,0x00,0x08,0x40,0x00,0xF8,0x31,0x00,0x00,0x08,0x00,0x00,0x04,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x60,0x44,0x00,0x10,0x62,0x00,0x08,0x52,0x00,0x00,0x52,0x00,0x00,0x4C, // 189 + 0x90,0x00,0x00,0x08,0x01,0x00,0x08,0x41,0x00,0x28,0x21,0x00,0xD8,0x18,0x00,0x00,0x04,0x00,0x00,0x03,0x00,0x80,0x00,0x00,0x40,0x30,0x00,0x30,0x28,0x00,0x08,0x24,0x00,0x00,0x7E,0x00,0x00,0x20, // 190 + 0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x10,0x01,0x00,0x08,0x02,0x40,0x07,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x01,0x00,0xC0, // 191 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x71,0x04,0x00,0x0A,0x04,0x00,0x70,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 192 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x70,0x04,0x00,0x0A,0x04,0x00,0x71,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 193 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x09,0x04,0x00,0x71,0x04,0x00,0x82,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 194 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x09,0x04,0x00,0x72,0x04,0x00,0x81,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 195 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x72,0x04,0x00,0x08,0x04,0x00,0x72,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 196 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x1C,0x00,0x80,0x07,0x00,0x7E,0x04,0x00,0x0A,0x04,0x00,0x7E,0x04,0x00,0x80,0x07,0x00,0x00,0x1C,0x00,0x00,0x60, // 197 + 0x00,0x60,0x00,0x00,0x18,0x00,0x00,0x06,0x00,0x80,0x05,0x00,0x60,0x04,0x00,0x18,0x04,0x00,0x08,0x04,0x00,0x08,0x04,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41, // 198 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x08,0x40,0x02,0x08,0xC0,0x02,0x08,0x40,0x03,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10, // 199 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x09,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 200 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x09,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 201 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x09,0x41,0x00,0x09,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 202 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x0A,0x41,0x00,0x08,0x41,0x00,0x08,0x41,0x00,0x08,0x40, // 203 + 0x01,0x00,0x00,0xFA,0x7F, // 204 + 0x00,0x00,0x00,0xFA,0x7F,0x00,0x01, // 205 + 0x02,0x00,0x00,0xF9,0x7F,0x00,0x01,0x00,0x00,0x02, // 206 + 0x02,0x00,0x00,0xF8,0x7F,0x00,0x02, // 207 + 0x00,0x02,0x00,0xF8,0x7F,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x42,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 208 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x10,0x00,0x00,0x60,0x00,0x00,0x82,0x00,0x00,0x01,0x03,0x00,0x02,0x04,0x00,0x01,0x18,0x00,0x00,0x20,0x00,0xF8,0x7F, // 209 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 210 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 211 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 212 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x0A,0x40,0x00,0x09,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 213 + 0x00,0x00,0x00,0xC0,0x0F,0x00,0x20,0x10,0x00,0x10,0x20,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x08,0x40,0x00,0x0A,0x40,0x00,0x10,0x20,0x00,0x20,0x10,0x00,0xC0,0x0F, // 214 + 0x00,0x00,0x00,0x40,0x10,0x00,0x80,0x08,0x00,0x00,0x05,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x80,0x08,0x00,0x40,0x10, // 215 + 0x00,0x00,0x00,0xC0,0x4F,0x00,0x20,0x30,0x00,0x10,0x30,0x00,0x08,0x4C,0x00,0x08,0x42,0x00,0x08,0x41,0x00,0xC8,0x40,0x00,0x30,0x20,0x00,0x30,0x10,0x00,0xC8,0x0F, // 216 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x01,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 217 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x01,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 218 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x01,0x40,0x00,0x01,0x40,0x00,0x02,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 219 + 0x00,0x00,0x00,0xF8,0x1F,0x00,0x00,0x20,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x02,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xF8,0x1F, // 220 + 0x08,0x00,0x00,0x30,0x00,0x00,0x40,0x00,0x00,0x80,0x01,0x00,0x02,0x7E,0x00,0x81,0x01,0x00,0x40,0x00,0x00,0x30,0x00,0x00,0x08, // 221 + 0x00,0x00,0x00,0xF8,0x7F,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x20,0x10,0x00,0x40,0x08,0x00,0x80,0x07, // 222 + 0x00,0x00,0x00,0xE0,0x7F,0x00,0x10,0x00,0x00,0x08,0x20,0x00,0x88,0x43,0x00,0x70,0x42,0x00,0x00,0x44,0x00,0x00,0x38, // 223 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x48,0x44,0x00,0x50,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 224 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x48,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 225 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x48,0x42,0x00,0x50,0x22,0x00,0x80,0x7F, // 226 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x50,0x42,0x00,0x48,0x22,0x00,0x80,0x7F, // 227 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x50,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 228 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x5C,0x44,0x00,0x54,0x44,0x00,0x5C,0x42,0x00,0x40,0x22,0x00,0x80,0x7F, // 229 + 0x00,0x00,0x00,0x00,0x39,0x00,0x80,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x40,0x22,0x00,0x80,0x3F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 230 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x02,0x40,0xC0,0x02,0x40,0x40,0x03,0x80,0x20, // 231 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x48,0x44,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 232 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 233 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x50,0x44,0x00,0x48,0x44,0x00,0x48,0x44,0x00,0x90,0x24,0x00,0x00,0x17, // 234 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x24,0x00,0x50,0x44,0x00,0x40,0x44,0x00,0x50,0x44,0x00,0x80,0x24,0x00,0x00,0x17, // 235 + 0x08,0x00,0x00,0xD0,0x7F, // 236 + 0x00,0x00,0x00,0xD0,0x7F,0x00,0x08, // 237 + 0x10,0x00,0x00,0xC8,0x7F,0x00,0x08,0x00,0x00,0x10, // 238 + 0x10,0x00,0x00,0xC0,0x7F,0x00,0x10, // 239 + 0x00,0x00,0x00,0x00,0x1F,0x00,0xA0,0x20,0x00,0x68,0x40,0x00,0x58,0x40,0x00,0x70,0x40,0x00,0xE8,0x20,0x00,0x00,0x1F, // 240 + 0x00,0x00,0x00,0xC0,0x7F,0x00,0x90,0x00,0x00,0x48,0x00,0x00,0x50,0x00,0x00,0x48,0x00,0x00,0x80,0x7F, // 241 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x48,0x40,0x00,0x50,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 242 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x40,0x40,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 243 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x48,0x40,0x00,0x90,0x20,0x00,0x00,0x1F, // 244 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x48,0x40,0x00,0x50,0x40,0x00,0x88,0x20,0x00,0x00,0x1F, // 245 + 0x00,0x00,0x00,0x00,0x1F,0x00,0x80,0x20,0x00,0x50,0x40,0x00,0x40,0x40,0x00,0x50,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 246 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x80,0x0A,0x00,0x00,0x02,0x00,0x00,0x02,0x00,0x00,0x02, // 247 + 0x00,0x00,0x00,0x00,0x5F,0x00,0x80,0x30,0x00,0x40,0x48,0x00,0x40,0x44,0x00,0x40,0x42,0x00,0x80,0x21,0x00,0x40,0x1F, // 248 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x08,0x40,0x00,0x10,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 249 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x00,0x40,0x00,0x00,0x40,0x00,0x10,0x40,0x00,0x08,0x20,0x00,0xC0,0x7F, // 250 + 0x00,0x00,0x00,0xC0,0x3F,0x00,0x10,0x40,0x00,0x08,0x40,0x00,0x08,0x40,0x00,0x10,0x20,0x00,0xC0,0x7F, // 251 + 0x00,0x00,0x00,0xD0,0x3F,0x00,0x00,0x40,0x00,0x10,0x40,0x00,0x00,0x40,0x00,0x00,0x20,0x00,0xC0,0x7F, // 252 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x00,0x38,0x02,0x10,0xE0,0x01,0x08,0x38,0x00,0x00,0x07,0x00,0xC0, // 253 + 0x00,0x00,0x00,0xF8,0xFF,0x03,0x80,0x20,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x40,0x40,0x00,0x80,0x20,0x00,0x00,0x1F, // 254 + 0xC0,0x01,0x00,0x00,0x06,0x02,0x10,0x38,0x02,0x00,0xE0,0x01,0x10,0x38,0x00,0x00,0x07,0x00,0xC0 // 255 +}; +const uint8_t ArialMT_Plain_24[] PROGMEM = { + 0x18, // Width: 24 + 0x1C, // Height: 28 + 0x20, // First Char: 32 + 0xE0, // Numbers of Chars: 224 + + // Jump Table: + 0xFF, 0xFF, 0x00, 0x07, // 32:65535 + 0x00, 0x00, 0x13, 0x07, // 33:0 + 0x00, 0x13, 0x1A, 0x09, // 34:19 + 0x00, 0x2D, 0x33, 0x0D, // 35:45 + 0x00, 0x60, 0x2F, 0x0D, // 36:96 + 0x00, 0x8F, 0x4F, 0x15, // 37:143 + 0x00, 0xDE, 0x3B, 0x10, // 38:222 + 0x01, 0x19, 0x0A, 0x05, // 39:281 + 0x01, 0x23, 0x1C, 0x08, // 40:291 + 0x01, 0x3F, 0x1B, 0x08, // 41:319 + 0x01, 0x5A, 0x21, 0x09, // 42:346 + 0x01, 0x7B, 0x32, 0x0E, // 43:379 + 0x01, 0xAD, 0x10, 0x07, // 44:429 + 0x01, 0xBD, 0x1B, 0x08, // 45:445 + 0x01, 0xD8, 0x0F, 0x07, // 46:472 + 0x01, 0xE7, 0x19, 0x07, // 47:487 + 0x02, 0x00, 0x2F, 0x0D, // 48:512 + 0x02, 0x2F, 0x23, 0x0D, // 49:559 + 0x02, 0x52, 0x2F, 0x0D, // 50:594 + 0x02, 0x81, 0x2F, 0x0D, // 51:641 + 0x02, 0xB0, 0x2F, 0x0D, // 52:688 + 0x02, 0xDF, 0x2F, 0x0D, // 53:735 + 0x03, 0x0E, 0x2F, 0x0D, // 54:782 + 0x03, 0x3D, 0x2D, 0x0D, // 55:829 + 0x03, 0x6A, 0x2F, 0x0D, // 56:874 + 0x03, 0x99, 0x2F, 0x0D, // 57:921 + 0x03, 0xC8, 0x0F, 0x07, // 58:968 + 0x03, 0xD7, 0x10, 0x07, // 59:983 + 0x03, 0xE7, 0x2F, 0x0E, // 60:999 + 0x04, 0x16, 0x2F, 0x0E, // 61:1046 + 0x04, 0x45, 0x2E, 0x0E, // 62:1093 + 0x04, 0x73, 0x2E, 0x0D, // 63:1139 + 0x04, 0xA1, 0x5B, 0x18, // 64:1185 + 0x04, 0xFC, 0x3B, 0x10, // 65:1276 + 0x05, 0x37, 0x3B, 0x10, // 66:1335 + 0x05, 0x72, 0x3F, 0x11, // 67:1394 + 0x05, 0xB1, 0x3F, 0x11, // 68:1457 + 0x05, 0xF0, 0x3B, 0x10, // 69:1520 + 0x06, 0x2B, 0x35, 0x0F, // 70:1579 + 0x06, 0x60, 0x43, 0x13, // 71:1632 + 0x06, 0xA3, 0x3B, 0x11, // 72:1699 + 0x06, 0xDE, 0x0F, 0x07, // 73:1758 + 0x06, 0xED, 0x27, 0x0C, // 74:1773 + 0x07, 0x14, 0x3F, 0x10, // 75:1812 + 0x07, 0x53, 0x2F, 0x0D, // 76:1875 + 0x07, 0x82, 0x43, 0x14, // 77:1922 + 0x07, 0xC5, 0x3B, 0x11, // 78:1989 + 0x08, 0x00, 0x47, 0x13, // 79:2048 + 0x08, 0x47, 0x3A, 0x10, // 80:2119 + 0x08, 0x81, 0x47, 0x13, // 81:2177 + 0x08, 0xC8, 0x3F, 0x11, // 82:2248 + 0x09, 0x07, 0x3B, 0x10, // 83:2311 + 0x09, 0x42, 0x35, 0x0F, // 84:2370 + 0x09, 0x77, 0x3B, 0x11, // 85:2423 + 0x09, 0xB2, 0x39, 0x10, // 86:2482 + 0x09, 0xEB, 0x59, 0x17, // 87:2539 + 0x0A, 0x44, 0x3B, 0x10, // 88:2628 + 0x0A, 0x7F, 0x3D, 0x10, // 89:2687 + 0x0A, 0xBC, 0x37, 0x0F, // 90:2748 + 0x0A, 0xF3, 0x14, 0x07, // 91:2803 + 0x0B, 0x07, 0x1B, 0x07, // 92:2823 + 0x0B, 0x22, 0x18, 0x07, // 93:2850 + 0x0B, 0x3A, 0x2A, 0x0B, // 94:2874 + 0x0B, 0x64, 0x34, 0x0D, // 95:2916 + 0x0B, 0x98, 0x11, 0x08, // 96:2968 + 0x0B, 0xA9, 0x2F, 0x0D, // 97:2985 + 0x0B, 0xD8, 0x33, 0x0D, // 98:3032 + 0x0C, 0x0B, 0x2B, 0x0C, // 99:3083 + 0x0C, 0x36, 0x2F, 0x0D, // 100:3126 + 0x0C, 0x65, 0x2F, 0x0D, // 101:3173 + 0x0C, 0x94, 0x1A, 0x07, // 102:3220 + 0x0C, 0xAE, 0x2F, 0x0D, // 103:3246 + 0x0C, 0xDD, 0x2F, 0x0D, // 104:3293 + 0x0D, 0x0C, 0x0F, 0x05, // 105:3340 + 0x0D, 0x1B, 0x10, 0x05, // 106:3355 + 0x0D, 0x2B, 0x2F, 0x0C, // 107:3371 + 0x0D, 0x5A, 0x0F, 0x05, // 108:3418 + 0x0D, 0x69, 0x47, 0x14, // 109:3433 + 0x0D, 0xB0, 0x2F, 0x0D, // 110:3504 + 0x0D, 0xDF, 0x2F, 0x0D, // 111:3551 + 0x0E, 0x0E, 0x33, 0x0D, // 112:3598 + 0x0E, 0x41, 0x30, 0x0D, // 113:3649 + 0x0E, 0x71, 0x1E, 0x08, // 114:3697 + 0x0E, 0x8F, 0x2B, 0x0C, // 115:3727 + 0x0E, 0xBA, 0x1B, 0x07, // 116:3770 + 0x0E, 0xD5, 0x2F, 0x0D, // 117:3797 + 0x0F, 0x04, 0x2A, 0x0C, // 118:3844 + 0x0F, 0x2E, 0x42, 0x11, // 119:3886 + 0x0F, 0x70, 0x2B, 0x0C, // 120:3952 + 0x0F, 0x9B, 0x2A, 0x0C, // 121:3995 + 0x0F, 0xC5, 0x2B, 0x0C, // 122:4037 + 0x0F, 0xF0, 0x1C, 0x08, // 123:4080 + 0x10, 0x0C, 0x10, 0x06, // 124:4108 + 0x10, 0x1C, 0x1B, 0x08, // 125:4124 + 0x10, 0x37, 0x32, 0x0E, // 126:4151 + 0xFF, 0xFF, 0x00, 0x00, // 127:65535 + 0xFF, 0xFF, 0x00, 0x18, // 128:65535 + 0xFF, 0xFF, 0x00, 0x18, // 129:65535 + 0xFF, 0xFF, 0x00, 0x18, // 130:65535 + 0xFF, 0xFF, 0x00, 0x18, // 131:65535 + 0xFF, 0xFF, 0x00, 0x18, // 132:65535 + 0xFF, 0xFF, 0x00, 0x18, // 133:65535 + 0xFF, 0xFF, 0x00, 0x18, // 134:65535 + 0xFF, 0xFF, 0x00, 0x18, // 135:65535 + 0xFF, 0xFF, 0x00, 0x18, // 136:65535 + 0xFF, 0xFF, 0x00, 0x18, // 137:65535 + 0xFF, 0xFF, 0x00, 0x18, // 138:65535 + 0xFF, 0xFF, 0x00, 0x18, // 139:65535 + 0xFF, 0xFF, 0x00, 0x18, // 140:65535 + 0xFF, 0xFF, 0x00, 0x18, // 141:65535 + 0xFF, 0xFF, 0x00, 0x18, // 142:65535 + 0xFF, 0xFF, 0x00, 0x18, // 143:65535 + 0xFF, 0xFF, 0x00, 0x18, // 144:65535 + 0xFF, 0xFF, 0x00, 0x18, // 145:65535 + 0xFF, 0xFF, 0x00, 0x18, // 146:65535 + 0xFF, 0xFF, 0x00, 0x18, // 147:65535 + 0xFF, 0xFF, 0x00, 0x18, // 148:65535 + 0xFF, 0xFF, 0x00, 0x18, // 149:65535 + 0xFF, 0xFF, 0x00, 0x18, // 150:65535 + 0xFF, 0xFF, 0x00, 0x18, // 151:65535 + 0xFF, 0xFF, 0x00, 0x18, // 152:65535 + 0xFF, 0xFF, 0x00, 0x18, // 153:65535 + 0xFF, 0xFF, 0x00, 0x18, // 154:65535 + 0xFF, 0xFF, 0x00, 0x18, // 155:65535 + 0xFF, 0xFF, 0x00, 0x18, // 156:65535 + 0xFF, 0xFF, 0x00, 0x18, // 157:65535 + 0xFF, 0xFF, 0x00, 0x18, // 158:65535 + 0xFF, 0xFF, 0x00, 0x18, // 159:65535 + 0xFF, 0xFF, 0x00, 0x07, // 160:65535 + 0x10, 0x69, 0x14, 0x08, // 161:4201 + 0x10, 0x7D, 0x2B, 0x0D, // 162:4221 + 0x10, 0xA8, 0x2F, 0x0D, // 163:4264 + 0x10, 0xD7, 0x33, 0x0D, // 164:4311 + 0x11, 0x0A, 0x31, 0x0D, // 165:4362 + 0x11, 0x3B, 0x10, 0x06, // 166:4411 + 0x11, 0x4B, 0x2F, 0x0D, // 167:4427 + 0x11, 0x7A, 0x19, 0x08, // 168:4474 + 0x11, 0x93, 0x46, 0x12, // 169:4499 + 0x11, 0xD9, 0x1A, 0x09, // 170:4569 + 0x11, 0xF3, 0x27, 0x0D, // 171:4595 + 0x12, 0x1A, 0x2F, 0x0E, // 172:4634 + 0x12, 0x49, 0x1B, 0x08, // 173:4681 + 0x12, 0x64, 0x46, 0x12, // 174:4708 + 0x12, 0xAA, 0x31, 0x0D, // 175:4778 + 0x12, 0xDB, 0x1E, 0x0A, // 176:4827 + 0x12, 0xF9, 0x33, 0x0D, // 177:4857 + 0x13, 0x2C, 0x1A, 0x08, // 178:4908 + 0x13, 0x46, 0x1A, 0x08, // 179:4934 + 0x13, 0x60, 0x19, 0x08, // 180:4960 + 0x13, 0x79, 0x2F, 0x0E, // 181:4985 + 0x13, 0xA8, 0x31, 0x0D, // 182:5032 + 0x13, 0xD9, 0x12, 0x08, // 183:5081 + 0x13, 0xEB, 0x18, 0x08, // 184:5099 + 0x14, 0x03, 0x16, 0x08, // 185:5123 + 0x14, 0x19, 0x1E, 0x09, // 186:5145 + 0x14, 0x37, 0x2E, 0x0D, // 187:5175 + 0x14, 0x65, 0x4F, 0x14, // 188:5221 + 0x14, 0xB4, 0x4B, 0x14, // 189:5300 + 0x14, 0xFF, 0x4B, 0x14, // 190:5375 + 0x15, 0x4A, 0x33, 0x0F, // 191:5450 + 0x15, 0x7D, 0x3B, 0x10, // 192:5501 + 0x15, 0xB8, 0x3B, 0x10, // 193:5560 + 0x15, 0xF3, 0x3B, 0x10, // 194:5619 + 0x16, 0x2E, 0x3B, 0x10, // 195:5678 + 0x16, 0x69, 0x3B, 0x10, // 196:5737 + 0x16, 0xA4, 0x3B, 0x10, // 197:5796 + 0x16, 0xDF, 0x5B, 0x18, // 198:5855 + 0x17, 0x3A, 0x3F, 0x11, // 199:5946 + 0x17, 0x79, 0x3B, 0x10, // 200:6009 + 0x17, 0xB4, 0x3B, 0x10, // 201:6068 + 0x17, 0xEF, 0x3B, 0x10, // 202:6127 + 0x18, 0x2A, 0x3B, 0x10, // 203:6186 + 0x18, 0x65, 0x11, 0x07, // 204:6245 + 0x18, 0x76, 0x11, 0x07, // 205:6262 + 0x18, 0x87, 0x15, 0x07, // 206:6279 + 0x18, 0x9C, 0x15, 0x07, // 207:6300 + 0x18, 0xB1, 0x3F, 0x11, // 208:6321 + 0x18, 0xF0, 0x3B, 0x11, // 209:6384 + 0x19, 0x2B, 0x47, 0x13, // 210:6443 + 0x19, 0x72, 0x47, 0x13, // 211:6514 + 0x19, 0xB9, 0x47, 0x13, // 212:6585 + 0x1A, 0x00, 0x47, 0x13, // 213:6656 + 0x1A, 0x47, 0x47, 0x13, // 214:6727 + 0x1A, 0x8E, 0x2B, 0x0E, // 215:6798 + 0x1A, 0xB9, 0x47, 0x13, // 216:6841 + 0x1B, 0x00, 0x3B, 0x11, // 217:6912 + 0x1B, 0x3B, 0x3B, 0x11, // 218:6971 + 0x1B, 0x76, 0x3B, 0x11, // 219:7030 + 0x1B, 0xB1, 0x3B, 0x11, // 220:7089 + 0x1B, 0xEC, 0x3D, 0x10, // 221:7148 + 0x1C, 0x29, 0x3A, 0x10, // 222:7209 + 0x1C, 0x63, 0x37, 0x0F, // 223:7267 + 0x1C, 0x9A, 0x2F, 0x0D, // 224:7322 + 0x1C, 0xC9, 0x2F, 0x0D, // 225:7369 + 0x1C, 0xF8, 0x2F, 0x0D, // 226:7416 + 0x1D, 0x27, 0x2F, 0x0D, // 227:7463 + 0x1D, 0x56, 0x2F, 0x0D, // 228:7510 + 0x1D, 0x85, 0x2F, 0x0D, // 229:7557 + 0x1D, 0xB4, 0x53, 0x15, // 230:7604 + 0x1E, 0x07, 0x2B, 0x0C, // 231:7687 + 0x1E, 0x32, 0x2F, 0x0D, // 232:7730 + 0x1E, 0x61, 0x2F, 0x0D, // 233:7777 + 0x1E, 0x90, 0x2F, 0x0D, // 234:7824 + 0x1E, 0xBF, 0x2F, 0x0D, // 235:7871 + 0x1E, 0xEE, 0x11, 0x07, // 236:7918 + 0x1E, 0xFF, 0x11, 0x07, // 237:7935 + 0x1F, 0x10, 0x15, 0x07, // 238:7952 + 0x1F, 0x25, 0x15, 0x07, // 239:7973 + 0x1F, 0x3A, 0x2F, 0x0D, // 240:7994 + 0x1F, 0x69, 0x2F, 0x0D, // 241:8041 + 0x1F, 0x98, 0x2F, 0x0D, // 242:8088 + 0x1F, 0xC7, 0x2F, 0x0D, // 243:8135 + 0x1F, 0xF6, 0x2F, 0x0D, // 244:8182 + 0x20, 0x25, 0x2F, 0x0D, // 245:8229 + 0x20, 0x54, 0x2F, 0x0D, // 246:8276 + 0x20, 0x83, 0x32, 0x0D, // 247:8323 + 0x20, 0xB5, 0x33, 0x0F, // 248:8373 + 0x20, 0xE8, 0x2F, 0x0D, // 249:8424 + 0x21, 0x17, 0x2F, 0x0D, // 250:8471 + 0x21, 0x46, 0x2F, 0x0D, // 251:8518 + 0x21, 0x75, 0x2F, 0x0D, // 252:8565 + 0x21, 0xA4, 0x2A, 0x0C, // 253:8612 + 0x21, 0xCE, 0x2F, 0x0D, // 254:8654 + 0x21, 0xFD, 0x2A, 0x0C, // 255:8701 + + // Font Data: + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x33,0x00,0xE0,0xFF,0x33, // 33 + 0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07, // 34 + 0x00,0x0C,0x03,0x00,0x00,0x0C,0x33,0x00,0x00,0x0C,0x3F,0x00,0x00,0xFC,0x0F,0x00,0x80,0xFF,0x03,0x00,0xE0,0x0F,0x03,0x00,0x60,0x0C,0x33,0x00,0x00,0x0C,0x3F,0x00,0x00,0xFC,0x0F,0x00,0x80,0xFF,0x03,0x00,0xE0,0x0F,0x03,0x00,0x60,0x0C,0x03,0x00,0x00,0x0C,0x03, // 35 + 0x00,0x00,0x00,0x00,0x80,0x07,0x06,0x00,0xC0,0x0F,0x1E,0x00,0xC0,0x18,0x1C,0x00,0x60,0x18,0x38,0x00,0x60,0x30,0x30,0x00,0xF0,0xFF,0xFF,0x00,0x60,0x30,0x30,0x00,0x60,0x60,0x38,0x00,0xC0,0x60,0x18,0x00,0xC0,0xC1,0x1F,0x00,0x00,0x81,0x07, // 36 + 0x00,0x00,0x00,0x00,0x80,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x20,0x20,0x00,0x60,0x30,0x38,0x00,0xC0,0x1F,0x1E,0x00,0x80,0x8F,0x0F,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x8F,0x0F,0x00,0xC0,0xC3,0x1F,0x00,0xE0,0x60,0x30,0x00,0x20,0x20,0x20,0x00,0x00,0x20,0x20,0x00,0x00,0x60,0x30,0x00,0x00,0xC0,0x1F,0x00,0x00,0x80,0x0F, // 37 + 0x00,0x00,0x00,0x00,0x00,0x80,0x07,0x00,0x00,0xC0,0x0F,0x00,0x80,0xE3,0x1C,0x00,0xC0,0x77,0x38,0x00,0xE0,0x3C,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x78,0x30,0x00,0xE0,0xEC,0x38,0x00,0xC0,0x8F,0x1B,0x00,0x80,0x03,0x1F,0x00,0x00,0x00,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0xC0,0x38,0x00,0x00,0x00,0x10, // 38 + 0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xE0,0x07, // 39 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x0F,0x00,0x00,0xFE,0x7F,0x00,0x80,0x0F,0xF0,0x01,0xC0,0x01,0x80,0x03,0x60,0x00,0x00,0x06,0x20,0x00,0x00,0x04, // 40 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x04,0x60,0x00,0x00,0x06,0xC0,0x01,0x80,0x03,0x80,0x0F,0xF0,0x01,0x00,0xFE,0x7F,0x00,0x00,0xF0,0x0F, // 41 + 0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x04,0x00,0x00,0x80,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x80,0x04,0x00,0x00,0x80, // 42 + 0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xFF,0x0F,0x00,0x00,0xFF,0x0F,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 43 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x03,0x00,0x00,0xF0,0x01, // 44 + 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01, // 45 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 46 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0xE0,0x0F,0x00,0x00,0xFC,0x01,0x00,0x80,0x3F,0x00,0x00,0xE0,0x03,0x00,0x00,0x60, // 47 + 0x00,0x00,0x00,0x00,0x00,0xFE,0x03,0x00,0x80,0xFF,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x01,0x1C,0x00,0x80,0xFF,0x0F,0x00,0x00,0xFE,0x03, // 48 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x01,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 49 + 0x00,0x00,0x00,0x00,0x00,0x03,0x30,0x00,0xC0,0x03,0x38,0x00,0xC0,0x00,0x3C,0x00,0x60,0x00,0x36,0x00,0x60,0x00,0x33,0x00,0x60,0x80,0x31,0x00,0x60,0xC0,0x30,0x00,0x60,0x60,0x30,0x00,0xC0,0x30,0x30,0x00,0xC0,0x1F,0x30,0x00,0x00,0x0F,0x30, // 50 + 0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x00,0xC0,0x01,0x0E,0x00,0xC0,0x00,0x1C,0x00,0x60,0x00,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xC0,0x38,0x30,0x00,0xC0,0x6F,0x18,0x00,0x80,0xC7,0x0F,0x00,0x00,0x80,0x07, // 51 + 0x00,0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x03,0x00,0x00,0x3C,0x03,0x00,0x00,0x0E,0x03,0x00,0x80,0x07,0x03,0x00,0xC0,0x01,0x03,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x03, // 52 + 0x00,0x00,0x00,0x00,0x00,0x30,0x06,0x00,0x80,0x3F,0x0E,0x00,0xE0,0x1F,0x18,0x00,0x60,0x08,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x0C,0x30,0x00,0x60,0x18,0x1C,0x00,0x60,0xF0,0x0F,0x00,0x00,0xE0,0x03, // 53 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x03,0x00,0x80,0xFF,0x0F,0x00,0xC0,0x63,0x1C,0x00,0xC0,0x30,0x38,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0x60,0x18,0x30,0x00,0xE0,0x30,0x18,0x00,0xC0,0xF1,0x0F,0x00,0x80,0xC1,0x07, // 54 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x3C,0x00,0x60,0x80,0x3F,0x00,0x60,0xE0,0x03,0x00,0x60,0x78,0x00,0x00,0x60,0x0E,0x00,0x00,0x60,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60, // 55 + 0x00,0x00,0x00,0x00,0x00,0x80,0x07,0x00,0x80,0xC7,0x1F,0x00,0xC0,0x6F,0x18,0x00,0xE0,0x38,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xE0,0x38,0x30,0x00,0xC0,0x6F,0x18,0x00,0x80,0xC7,0x1F,0x00,0x00,0x80,0x07, // 56 + 0x00,0x00,0x00,0x00,0x00,0x1F,0x0C,0x00,0x80,0x7F,0x1C,0x00,0xC0,0x61,0x38,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0xC0,0x30,0x00,0x60,0x60,0x18,0x00,0xC0,0x31,0x1E,0x00,0x80,0xFF,0x0F,0x00,0x00,0xFE,0x01, // 57 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30, // 58 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x30,0x03,0x00,0x06,0xF0,0x01, // 59 + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x04,0x01,0x00,0x00,0x06,0x03,0x00,0x00,0x06,0x03,0x00,0x00,0x03,0x06, // 60 + 0x00,0x00,0x00,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01, // 61 + 0x00,0x00,0x00,0x00,0x00,0x03,0x06,0x00,0x00,0x06,0x03,0x00,0x00,0x06,0x03,0x00,0x00,0x04,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0x8C,0x01,0x00,0x00,0xD8,0x00,0x00,0x00,0xD8,0x00,0x00,0x00,0x50,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x20, // 62 + 0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x60,0x80,0x33,0x00,0x60,0xC0,0x33,0x00,0x60,0xE0,0x00,0x00,0x60,0x30,0x00,0x00,0xC0,0x38,0x00,0x00,0xC0,0x1F,0x00,0x00,0x00,0x07, // 63 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x0F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x1E,0xF0,0x00,0x00,0x07,0xC0,0x01,0x80,0xC3,0x87,0x01,0xC0,0xF1,0x9F,0x03,0xC0,0x38,0x18,0x03,0xC0,0x0C,0x30,0x03,0x60,0x0E,0x30,0x06,0x60,0x06,0x30,0x06,0x60,0x06,0x18,0x06,0x60,0x06,0x0C,0x06,0x60,0x0C,0x1E,0x06,0x60,0xF8,0x3F,0x06,0xE0,0xFE,0x31,0x06,0xC0,0x0E,0x30,0x06,0xC0,0x01,0x18,0x03,0x80,0x03,0x1C,0x03,0x00,0x07,0x8F,0x01,0x00,0xFE,0x87,0x01,0x00,0xF8,0xC1,0x00,0x00,0x00,0x40, // 64 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x80,0x8F,0x01,0x00,0xE0,0x83,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0x83,0x01,0x00,0x80,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 65 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0xC0,0x78,0x30,0x00,0xC0,0xFF,0x18,0x00,0x80,0xC7,0x1F,0x00,0x00,0x80,0x07, // 66 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0F,0x00,0x00,0x02,0x03, // 67 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0E,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 68 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 69 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60, // 70 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x60,0x30,0x00,0x60,0x60,0x30,0x00,0xE0,0x60,0x38,0x00,0xC0,0x60,0x18,0x00,0xC0,0x61,0x18,0x00,0x80,0xE3,0x0F,0x00,0x00,0xE2,0x0F, // 71 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 72 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 73 + 0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0xE0,0xFF,0x1F,0x00,0xE0,0xFF,0x0F, // 74 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0xE7,0x01,0x00,0x80,0x83,0x07,0x00,0xC0,0x01,0x0F,0x00,0xE0,0x00,0x1E,0x00,0x60,0x00,0x38,0x00,0x20,0x00,0x30,0x00,0x00,0x00,0x20, // 75 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 76 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x3F,0x00,0x00,0xE0,0x07,0x00,0x00,0xFE,0x00,0x00,0xC0,0x0F,0x00,0x00,0xE0,0x01,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 77 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0xE0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 78 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 79 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x60,0x00,0x00,0xC0,0x30,0x00,0x00,0xC0,0x3F,0x00,0x00,0x00,0x0F, // 80 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x0C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x18,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x36,0x00,0x60,0x00,0x36,0x00,0xE0,0x00,0x3C,0x00,0xC0,0x00,0x1C,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x3F,0x00,0x00,0xFF,0x77,0x00,0x00,0xFC,0x61, // 81 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x70,0x00,0x00,0x60,0xF0,0x00,0x00,0x60,0xF0,0x03,0x00,0x60,0xB0,0x07,0x00,0xE0,0x18,0x1F,0x00,0xC0,0x1F,0x3C,0x00,0x80,0x0F,0x30,0x00,0x00,0x00,0x20, // 82 + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x07,0x0F,0x00,0xC0,0x1F,0x1C,0x00,0xC0,0x18,0x18,0x00,0x60,0x38,0x38,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x70,0x30,0x00,0xC0,0x60,0x18,0x00,0xC0,0xE1,0x18,0x00,0x80,0xC3,0x0F,0x00,0x00,0x83,0x07, // 83 + 0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 84 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 85 + 0x20,0x00,0x00,0x00,0xE0,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0xF8,0x01,0x00,0x00,0xC0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x3E,0x00,0x00,0xC0,0x0F,0x00,0x00,0xF8,0x01,0x00,0x00,0x3E,0x00,0x00,0xC0,0x0F,0x00,0x00,0xE0,0x01,0x00,0x00,0x20, // 86 + 0x60,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0x80,0xFF,0x00,0x00,0x00,0xF8,0x0F,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x3F,0x00,0x00,0xE0,0x0F,0x00,0x00,0xFC,0x01,0x00,0x80,0x1F,0x00,0x00,0xE0,0x03,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xE0,0x0F,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x30,0x00,0x00,0x80,0x3F,0x00,0x00,0xF8,0x0F,0x00,0x80,0xFF,0x00,0x00,0xE0,0x07,0x00,0x00,0x60, // 87 + 0x00,0x00,0x20,0x00,0x20,0x00,0x30,0x00,0x60,0x00,0x3C,0x00,0xE0,0x01,0x1E,0x00,0xC0,0x83,0x07,0x00,0x00,0xCF,0x03,0x00,0x00,0xFE,0x01,0x00,0x00,0x38,0x00,0x00,0x00,0xFE,0x01,0x00,0x00,0xCF,0x03,0x00,0xC0,0x03,0x07,0x00,0xE0,0x01,0x1E,0x00,0x60,0x00,0x3C,0x00,0x20,0x00,0x30,0x00,0x00,0x00,0x20, // 88 + 0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0xF0,0x3F,0x00,0x00,0xF0,0x3F,0x00,0x00,0x3C,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 89 + 0x00,0x00,0x30,0x00,0x60,0x00,0x38,0x00,0x60,0x00,0x3C,0x00,0x60,0x00,0x37,0x00,0x60,0x80,0x33,0x00,0x60,0xC0,0x31,0x00,0x60,0xE0,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x1C,0x30,0x00,0x60,0x0E,0x30,0x00,0x60,0x07,0x30,0x00,0xE0,0x01,0x30,0x00,0xE0,0x00,0x30,0x00,0x60,0x00,0x30, // 90 + 0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06, // 91 + 0x60,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x3F,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xE0,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 92 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07, // 93 + 0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1F,0x00,0x00,0xC0,0x07,0x00,0x00,0xE0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xC0,0x07,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x20, // 94 + 0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06, // 95 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x80, // 96 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x00,0x86,0x31,0x00,0x00,0x86,0x31,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x18,0x00,0x00,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 97 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x18,0x0C,0x00,0x00,0x0C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xE0,0x03, // 98 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0x18,0x0C, // 99 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0C,0x18,0x00,0x00,0x18,0x0C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 100 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 101 + 0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0xC0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x06,0x00,0x00,0x60,0x06,0x00,0x00,0x60,0x06, // 102 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x83,0x01,0x00,0xF8,0x8F,0x03,0x00,0x1C,0x1C,0x07,0x00,0x0E,0x38,0x06,0x00,0x06,0x30,0x06,0x00,0x06,0x30,0x06,0x00,0x06,0x30,0x06,0x00,0x0C,0x18,0x07,0x00,0x18,0x8C,0x03,0x00,0xFE,0xFF,0x01,0x00,0xFE,0xFF, // 103 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 104 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0x60,0xFE,0x3F, // 105 + 0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x60,0xFE,0xFF,0x07,0x60,0xFE,0xFF,0x03, // 106 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0xF0,0x01,0x00,0x00,0x98,0x07,0x00,0x00,0x0C,0x0E,0x00,0x00,0x06,0x3C,0x00,0x00,0x02,0x30,0x00,0x00,0x00,0x20, // 107 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 108 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 109 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 110 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 111 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07,0x00,0x18,0x0C,0x00,0x00,0x0C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xE0,0x03, // 112 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0C,0x18,0x00,0x00,0x18,0x0C,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07, // 113 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0x00,0x0C,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06, // 114 + 0x00,0x00,0x00,0x00,0x00,0x38,0x0C,0x00,0x00,0x7C,0x1C,0x00,0x00,0xEE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x31,0x00,0x00,0xC6,0x31,0x00,0x00,0x8E,0x39,0x00,0x00,0x9C,0x1F,0x00,0x00,0x18,0x0F, // 115 + 0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0xC0,0xFF,0x1F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30, // 116 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 117 + 0x00,0x06,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0xC0,0x07,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1F,0x00,0x00,0xC0,0x07,0x00,0x00,0xF8,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 118 + 0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x80,0x1F,0x00,0x00,0xE0,0x03,0x00,0x00,0x7C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x80,0x1F,0x00,0x00,0xF0,0x03,0x00,0x00,0x7E,0x00,0x00,0x00,0x0E, // 119 + 0x00,0x02,0x20,0x00,0x00,0x06,0x30,0x00,0x00,0x1E,0x3C,0x00,0x00,0x38,0x0E,0x00,0x00,0xF0,0x07,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x07,0x00,0x00,0x38,0x0E,0x00,0x00,0x1C,0x3C,0x00,0x00,0x0E,0x30,0x00,0x00,0x02,0x20, // 120 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0x00,0xF0,0x01,0x06,0x00,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0x00,0xC0,0x1F,0x00,0x00,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 121 + 0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x06,0x3C,0x00,0x00,0x06,0x3E,0x00,0x00,0x06,0x37,0x00,0x00,0xC6,0x33,0x00,0x00,0xE6,0x30,0x00,0x00,0x76,0x30,0x00,0x00,0x3E,0x30,0x00,0x00,0x1E,0x30,0x00,0x00,0x06,0x30, // 122 + 0x00,0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x03,0x00,0xC0,0x7F,0xFE,0x03,0xE0,0x3F,0xFC,0x07,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06, // 123 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x0F,0xE0,0xFF,0xFF,0x0F, // 124 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x06,0x60,0x00,0x00,0x06,0xE0,0x3F,0xFC,0x07,0xC0,0x7F,0xFF,0x03,0x00,0xC0,0x03,0x00,0x00,0x80,0x01, // 125 + 0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x60, // 126 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE6,0xFF,0x07,0x00,0xE6,0xFF,0x07, // 161 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x9C,0x07,0x00,0x0E,0x78,0x00,0x00,0x06,0x3F,0x00,0x00,0xF6,0x30,0x00,0x00,0x0E,0x30,0x00,0xE0,0x0D,0x1C,0x00,0x00,0x1C,0x0E,0x00,0x00,0x10,0x06, // 162 + 0x00,0x60,0x10,0x00,0x00,0x60,0x38,0x00,0x00,0x7F,0x1C,0x00,0xC0,0xFF,0x1F,0x00,0xE0,0xE0,0x19,0x00,0x60,0x60,0x18,0x00,0x60,0x60,0x18,0x00,0x60,0x60,0x30,0x00,0xE0,0x00,0x30,0x00,0xC0,0x01,0x30,0x00,0x80,0x01,0x38,0x00,0x00,0x00,0x10, // 163 + 0x00,0x00,0x00,0x00,0x00,0x02,0x04,0x00,0x00,0xF7,0x0E,0x00,0x00,0xFE,0x07,0x00,0x00,0x0C,0x03,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x06,0x06,0x00,0x00,0x0C,0x03,0x00,0x00,0xFE,0x07,0x00,0x00,0xF7,0x0E,0x00,0x00,0x02,0x04, // 164 + 0xE0,0x60,0x06,0x00,0xC0,0x61,0x06,0x00,0x80,0x67,0x06,0x00,0x00,0x7E,0x06,0x00,0x00,0x7C,0x06,0x00,0x00,0xF0,0x3F,0x00,0x00,0xF0,0x3F,0x00,0x00,0x7C,0x06,0x00,0x00,0x7E,0x06,0x00,0x80,0x67,0x06,0x00,0xC0,0x61,0x06,0x00,0xE0,0x60,0x06,0x00,0x20, // 165 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x7F,0xF8,0x0F,0xE0,0x7F,0xF8,0x0F, // 166 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x80,0xF3,0xC1,0x00,0xC0,0x1F,0xC3,0x03,0xE0,0x0C,0x07,0x03,0x60,0x1C,0x06,0x06,0x60,0x18,0x0C,0x06,0x60,0x30,0x1C,0x06,0xE0,0x70,0x38,0x07,0xC0,0xE1,0xF4,0x03,0x80,0xC1,0xE7,0x01,0x00,0x80,0x03, // 167 + 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 168 + 0x00,0xF8,0x00,0x00,0x00,0xFE,0x03,0x00,0x00,0x07,0x07,0x00,0x80,0x01,0x0C,0x00,0xC0,0x79,0x1C,0x00,0xC0,0xFE,0x19,0x00,0x60,0x86,0x31,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x03,0x33,0x00,0x60,0x87,0x33,0x00,0xC0,0x86,0x19,0x00,0xC0,0x85,0x1C,0x00,0x80,0x01,0x0C,0x00,0x00,0x07,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xF8, // 169 + 0x00,0x00,0x00,0x00,0xC0,0x1C,0x00,0x00,0xE0,0x3E,0x00,0x00,0x60,0x32,0x00,0x00,0x60,0x32,0x00,0x00,0xE0,0x3F,0x00,0x00,0xC0,0x3F, // 170 + 0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0x03,0x00,0x00,0x78,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x84,0x10,0x00,0x00,0xE0,0x03,0x00,0x00,0x78,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x04,0x10, // 171 + 0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFC,0x01, // 172 + 0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01, // 173 + 0x00,0xF8,0x00,0x00,0x00,0xFE,0x03,0x00,0x00,0x07,0x07,0x00,0x80,0x01,0x0C,0x00,0xC0,0x01,0x1C,0x00,0xC0,0xFE,0x1B,0x00,0x60,0xFE,0x33,0x00,0x60,0x66,0x30,0x00,0x60,0x66,0x30,0x00,0x60,0xE6,0x30,0x00,0x60,0xFE,0x31,0x00,0x60,0x3C,0x33,0x00,0xC0,0x00,0x1A,0x00,0xC0,0x01,0x1C,0x00,0x80,0x01,0x0C,0x00,0x00,0x07,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xF8, // 174 + 0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0C, // 175 + 0x00,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0x40,0x04,0x00,0x00,0x20,0x08,0x00,0x00,0x20,0x08,0x00,0x00,0x20,0x08,0x00,0x00,0x40,0x04,0x00,0x00,0x80,0x03, // 176 + 0x00,0x00,0x00,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0xFF,0x3F,0x00,0x00,0xFF,0x3F,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30,0x00,0x00,0x60,0x30, // 177 + 0x40,0x20,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x38,0x00,0x00,0x20,0x2C,0x00,0x00,0x20,0x26,0x00,0x00,0xE0,0x23,0x00,0x00,0xC0,0x21, // 178 + 0x40,0x10,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x22,0x00,0x00,0x20,0x22,0x00,0x00,0xE0,0x3D,0x00,0x00,0xC0,0x1D, // 179 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 180 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0xFF,0x07,0x00,0xFE,0xFF,0x07,0x00,0x00,0x1C,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x1C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 181 + 0x00,0x0F,0x00,0x00,0xC0,0x3F,0x00,0x00,0xC0,0x3F,0x00,0x00,0xE0,0x7F,0x00,0x00,0xE0,0x7F,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x60,0x00,0x00,0x00,0x60, // 182 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0x60, // 183 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0xC0,0x02,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x01, // 184 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0xE0,0x3F,0x00,0x00,0xE0,0x3F, // 185 + 0x00,0x00,0x00,0x00,0x80,0x0F,0x00,0x00,0xC0,0x1F,0x00,0x00,0xE0,0x38,0x00,0x00,0x60,0x30,0x00,0x00,0xE0,0x38,0x00,0x00,0xC0,0x1F,0x00,0x00,0x80,0x0F, // 186 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x10,0x00,0x00,0x1C,0x1C,0x00,0x00,0x78,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0x84,0x10,0x00,0x00,0x1C,0x1C,0x00,0x00,0x78,0x0F,0x00,0x00,0xE0,0x03,0x00,0x00,0x80, // 187 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x20,0x00,0xE0,0x3F,0x38,0x00,0xE0,0x3F,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x07,0x0C,0x00,0xC0,0x01,0x0E,0x00,0xE0,0x80,0x0B,0x00,0x60,0xC0,0x08,0x00,0x00,0xE0,0x3F,0x00,0x00,0xE0,0x3F,0x00,0x00,0x00,0x08, // 188 + 0x00,0x00,0x00,0x00,0x80,0x01,0x00,0x00,0xC0,0x00,0x00,0x00,0xC0,0x00,0x20,0x00,0xE0,0x3F,0x30,0x00,0xE0,0x3F,0x1C,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x01,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x4E,0x20,0x00,0x00,0x67,0x30,0x00,0xC0,0x21,0x38,0x00,0xE0,0x20,0x2C,0x00,0x60,0x20,0x26,0x00,0x00,0xE0,0x27,0x00,0x00,0xC0,0x21, // 189 + 0x40,0x10,0x00,0x00,0x60,0x30,0x00,0x00,0x20,0x20,0x00,0x00,0x20,0x22,0x20,0x00,0x20,0x22,0x30,0x00,0xE0,0x3D,0x38,0x00,0xC0,0x1D,0x0E,0x00,0x00,0x00,0x07,0x00,0x00,0x80,0x03,0x00,0x00,0xE0,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x0E,0x0C,0x00,0x00,0x07,0x0E,0x00,0x80,0x83,0x0B,0x00,0xE0,0xC0,0x08,0x00,0x60,0xE0,0x3F,0x00,0x20,0xE0,0x3F,0x00,0x00,0x00,0x08, // 190 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0xF8,0x03,0x00,0x00,0x1E,0x03,0x00,0x00,0x07,0x07,0x00,0xE6,0x03,0x06,0x00,0xE6,0x01,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x07,0x00,0x00,0x80,0x03,0x00,0x00,0xC0,0x01,0x00,0x00,0xC0, // 191 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x82,0x8F,0x01,0x00,0xE6,0x83,0x01,0x00,0x6E,0x80,0x01,0x00,0xE8,0x83,0x01,0x00,0x80,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 192 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x80,0x8F,0x01,0x00,0xE8,0x83,0x01,0x00,0x6E,0x80,0x01,0x00,0xE6,0x83,0x01,0x00,0x82,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 193 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x88,0x8F,0x01,0x00,0xEC,0x83,0x01,0x00,0x66,0x80,0x01,0x00,0xE6,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x08,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 194 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x0C,0xFE,0x01,0x00,0x8E,0x8F,0x01,0x00,0xE6,0x83,0x01,0x00,0x66,0x80,0x01,0x00,0xEC,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x0E,0xFE,0x01,0x00,0x06,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 195 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x8C,0x8F,0x01,0x00,0xEC,0x83,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0x83,0x01,0x00,0x8C,0x8F,0x01,0x00,0x0C,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 196 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3E,0x00,0x00,0x80,0x0F,0x00,0x00,0xF0,0x03,0x00,0x00,0xFE,0x01,0x00,0x9C,0x8F,0x01,0x00,0xE2,0x83,0x01,0x00,0x62,0x80,0x01,0x00,0xE2,0x83,0x01,0x00,0x9C,0x8F,0x01,0x00,0x00,0xFE,0x01,0x00,0x00,0xF0,0x03,0x00,0x00,0x80,0x0F,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x30, // 197 + 0x00,0x00,0x30,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x0F,0x00,0x00,0xC0,0x03,0x00,0x00,0xF0,0x01,0x00,0x00,0xBC,0x01,0x00,0x00,0x8F,0x01,0x00,0xC0,0x83,0x01,0x00,0xE0,0x80,0x01,0x00,0x60,0x80,0x01,0x00,0x60,0x80,0x01,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 198 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0x60,0x00,0x30,0x02,0x60,0x00,0x30,0x02,0x60,0x00,0xF0,0x02,0x60,0x00,0xB0,0x03,0x60,0x00,0x30,0x01,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0F,0x00,0x00,0x02,0x03, // 199 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x62,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x6E,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 200 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x6E,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x62,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 201 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x66,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x68,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 202 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x6C,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30, // 203 + 0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0xE6,0xFF,0x3F,0x00,0xEE,0xFF,0x3F,0x00,0x08, // 204 + 0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0xEE,0xFF,0x3F,0x00,0xE6,0xFF,0x3F,0x00,0x02, // 205 + 0x08,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xE6,0xFF,0x3F,0x00,0xE6,0xFF,0x3F,0x00,0x0C,0x00,0x00,0x00,0x08, // 206 + 0x0C,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x0C,0x00,0x00,0x00,0x0C, // 207 + 0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x30,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x03,0x0E,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 208 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0xC0,0x01,0x00,0x00,0x8C,0x03,0x00,0x00,0x0E,0x0E,0x00,0x00,0x06,0x3C,0x00,0x00,0x06,0x70,0x00,0x00,0x0C,0xE0,0x01,0x00,0x0C,0x80,0x03,0x00,0x0E,0x00,0x0F,0x00,0x06,0x00,0x1C,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F, // 209 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x62,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0x68,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 210 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0x68,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x62,0x00,0x30,0x00,0xE0,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 211 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x68,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0xE8,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 212 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xCC,0x00,0x18,0x00,0xEE,0x00,0x38,0x00,0x66,0x00,0x30,0x00,0x66,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x6E,0x00,0x30,0x00,0xE6,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 213 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x01,0x00,0x00,0xFF,0x07,0x00,0x80,0x07,0x0F,0x00,0xC0,0x01,0x1C,0x00,0xC0,0x00,0x18,0x00,0xE0,0x00,0x38,0x00,0x6C,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x6C,0x00,0x30,0x00,0xEC,0x00,0x38,0x00,0xC0,0x00,0x18,0x00,0xC0,0x01,0x1C,0x00,0x80,0x07,0x0F,0x00,0x00,0xFF,0x07,0x00,0x00,0xFC,0x01, // 214 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x03,0x00,0x00,0x8E,0x03,0x00,0x00,0xDC,0x01,0x00,0x00,0xF8,0x00,0x00,0x00,0x70,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0xDC,0x01,0x00,0x00,0x8E,0x03,0x00,0x00,0x06,0x03, // 215 + 0x00,0x00,0x00,0x00,0x00,0xFC,0x21,0x00,0x00,0xFF,0x77,0x00,0x80,0x07,0x3F,0x00,0xC0,0x01,0x1E,0x00,0xC0,0x00,0x1F,0x00,0xE0,0x80,0x3B,0x00,0x60,0xC0,0x31,0x00,0x60,0xE0,0x30,0x00,0x60,0x70,0x30,0x00,0x60,0x38,0x30,0x00,0x60,0x1C,0x30,0x00,0xE0,0x0E,0x38,0x00,0xC0,0x07,0x18,0x00,0xC0,0x03,0x1C,0x00,0xE0,0x07,0x0F,0x00,0x70,0xFF,0x07,0x00,0x20,0xFC,0x01, // 216 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x02,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x0E,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 217 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x0E,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x02,0x00,0x30,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 218 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x08,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x06,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x08,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 219 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x03,0x00,0xE0,0xFF,0x0F,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x38,0x00,0x0C,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x0C,0x00,0x30,0x00,0x0C,0x00,0x38,0x00,0x00,0x00,0x1C,0x00,0xE0,0xFF,0x0F,0x00,0xE0,0xFF,0x03, // 220 + 0x20,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xC0,0x01,0x00,0x00,0x80,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x3C,0x00,0x00,0x08,0xF0,0x3F,0x00,0x0E,0xF0,0x3F,0x00,0x06,0x3C,0x00,0x00,0x02,0x1E,0x00,0x00,0x00,0x07,0x00,0x00,0xC0,0x03,0x00,0x00,0xE0,0x01,0x00,0x00,0x60,0x00,0x00,0x00,0x20, // 221 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0x3F,0x00,0xE0,0xFF,0x3F,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x06,0x00,0x00,0x03,0x07,0x00,0x00,0x86,0x03,0x00,0x00,0xFE,0x01,0x00,0x00,0xF8, // 222 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x3F,0x00,0xC0,0xFF,0x3F,0x00,0xC0,0x00,0x00,0x00,0x60,0x00,0x08,0x00,0x60,0x00,0x1C,0x00,0x60,0x00,0x38,0x00,0xE0,0x78,0x30,0x00,0xC0,0x7F,0x30,0x00,0x80,0xC7,0x30,0x00,0x00,0x80,0x39,0x00,0x00,0x80,0x1F,0x00,0x00,0x00,0x0F, // 223 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x20,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0xE0,0xC6,0x30,0x00,0x80,0xC6,0x18,0x00,0x00,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 224 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x00,0x86,0x31,0x00,0x80,0x86,0x31,0x00,0xE0,0xC6,0x30,0x00,0x60,0xC6,0x18,0x00,0x20,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 225 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x80,0x8C,0x39,0x00,0xC0,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0x60,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0x80,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 226 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0xC0,0x1C,0x1F,0x00,0xE0,0x8C,0x39,0x00,0x60,0x86,0x31,0x00,0x60,0x86,0x31,0x00,0xC0,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0xE0,0xCE,0x0C,0x00,0x60,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 227 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0xC0,0x8C,0x39,0x00,0xC0,0x86,0x31,0x00,0x00,0x86,0x31,0x00,0x00,0xC6,0x30,0x00,0xC0,0xC6,0x18,0x00,0xC0,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 228 + 0x00,0x00,0x00,0x00,0x00,0x18,0x0E,0x00,0x00,0x1C,0x1F,0x00,0x00,0x8C,0x39,0x00,0x70,0x86,0x31,0x00,0x88,0x86,0x31,0x00,0x88,0xC6,0x30,0x00,0x88,0xC6,0x18,0x00,0x70,0xCE,0x0C,0x00,0x00,0xFC,0x1F,0x00,0x00,0xF8,0x3F,0x00,0x00,0x00,0x20, // 229 + 0x00,0x00,0x00,0x00,0x00,0x10,0x0F,0x00,0x00,0x9C,0x1F,0x00,0x00,0xCC,0x39,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0x66,0x18,0x00,0x00,0x6E,0x1C,0x00,0x00,0xFC,0x0F,0x00,0x00,0xFC,0x1F,0x00,0x00,0xCC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xCC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xE0,0x04, // 230 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x02,0x00,0x06,0x30,0x02,0x00,0x06,0xF0,0x02,0x00,0x06,0xB0,0x03,0x00,0x0E,0x38,0x01,0x00,0x1C,0x1C,0x00,0x00,0x18,0x0C, // 231 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x20,0xCE,0x38,0x00,0x60,0xC6,0x30,0x00,0xE0,0xC6,0x30,0x00,0x80,0xC6,0x30,0x00,0x00,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 232 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x00,0xCE,0x38,0x00,0x80,0xC6,0x30,0x00,0xE0,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0x20,0xCE,0x38,0x00,0x00,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 233 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0x80,0xCE,0x38,0x00,0xC0,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0x60,0xC6,0x30,0x00,0xC0,0xCE,0x38,0x00,0x80,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 234 + 0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0xDC,0x1C,0x00,0xC0,0xCE,0x38,0x00,0xC0,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0x00,0xC6,0x30,0x00,0xC0,0xCE,0x38,0x00,0xC0,0xDC,0x18,0x00,0x00,0xF8,0x0C,0x00,0x00,0xF0,0x04, // 235 + 0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0xE0,0xFE,0x3F,0x00,0x80, // 236 + 0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0xE0,0xFE,0x3F,0x00,0x60,0xFE,0x3F,0x00,0x20, // 237 + 0x80,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x60,0xFE,0x3F,0x00,0x60,0xFE,0x3F,0x00,0xC0,0x00,0x00,0x00,0x80, // 238 + 0xC0,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F,0x00,0xC0,0x00,0x00,0x00,0xC0, // 239 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1D,0x1C,0x00,0xA0,0x0F,0x38,0x00,0xA0,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0xC0,0x06,0x30,0x00,0xC0,0x0F,0x38,0x00,0x20,0x1F,0x1C,0x00,0x00,0xFC,0x0F,0x00,0x00,0xE0,0x07, // 240 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x3F,0x00,0xC0,0xFE,0x3F,0x00,0xE0,0x18,0x00,0x00,0x60,0x0C,0x00,0x00,0x60,0x06,0x00,0x00,0xC0,0x06,0x00,0x00,0xC0,0x06,0x00,0x00,0xE0,0x0E,0x00,0x00,0x60,0xFC,0x3F,0x00,0x00,0xF8,0x3F, // 241 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x20,0x0E,0x38,0x00,0x60,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0x80,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 242 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x38,0x00,0x80,0x06,0x30,0x00,0xE0,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0x20,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 243 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0x80,0x0E,0x38,0x00,0xC0,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0x80,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 244 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0xC0,0x1C,0x1C,0x00,0xE0,0x0E,0x38,0x00,0x60,0x06,0x30,0x00,0x60,0x06,0x30,0x00,0xC0,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0xE0,0x1C,0x1C,0x00,0x60,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 245 + 0x00,0x00,0x00,0x00,0x00,0xF0,0x07,0x00,0x00,0xF8,0x0F,0x00,0x00,0x1C,0x1C,0x00,0xC0,0x0E,0x38,0x00,0xC0,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0xC0,0x0E,0x38,0x00,0xC0,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x07, // 246 + 0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0xB6,0x01,0x00,0x00,0xB6,0x01,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30, // 247 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0x67,0x00,0x00,0xF8,0x7F,0x00,0x00,0x1C,0x1C,0x00,0x00,0x0E,0x3F,0x00,0x00,0x86,0x33,0x00,0x00,0xE6,0x31,0x00,0x00,0x76,0x30,0x00,0x00,0x3E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xFF,0x0F,0x00,0x00,0xF3,0x07, // 248 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x20,0x00,0x38,0x00,0x60,0x00,0x30,0x00,0xE0,0x00,0x30,0x00,0x80,0x00,0x30,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 249 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x30,0x00,0x80,0x00,0x30,0x00,0xE0,0x00,0x30,0x00,0x60,0x00,0x18,0x00,0x20,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 250 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0x80,0x00,0x38,0x00,0xC0,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0x60,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0x80,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 251 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x0F,0x00,0x00,0xFE,0x1F,0x00,0xC0,0x00,0x38,0x00,0xC0,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x30,0x00,0xC0,0x00,0x18,0x00,0xC0,0x00,0x0C,0x00,0x00,0xFE,0x3F,0x00,0x00,0xFE,0x3F, // 252 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0x00,0xF0,0x01,0x06,0x00,0x80,0x0F,0x07,0x80,0x00,0xFE,0x03,0xE0,0x00,0xFC,0x00,0x60,0xC0,0x1F,0x00,0x20,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06, // 253 + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x00,0x1C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x03, // 254 + 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0xC0,0xF0,0x01,0x06,0xC0,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0xC0,0xC0,0x1F,0x00,0xC0,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06 // 255 +}; +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp new file mode 100644 index 00000000..6fcfafbf --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp @@ -0,0 +1,468 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#include "OLEDDisplayUi.h" + +void LoadingDrawDefault(OLEDDisplay *display, LoadingStage* stage, uint8_t progress) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_10); + display->drawString(64, 18, stage->process); + display->drawProgressBar(4, 32, 120, 8, progress); +}; + + +OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) { + this->display = display; + + indicatorPosition = BOTTOM; + indicatorDirection = LEFT_RIGHT; + activeSymbol = ANIMATION_activeSymbol; + inactiveSymbol = ANIMATION_inactiveSymbol; + frameAnimationDirection = SLIDE_RIGHT; + lastTransitionDirection = 1; + frameCount = 0; + nextFrameNumber = -1; + overlayCount = 0; + indicatorDrawState = 1; + loadingDrawFunction = LoadingDrawDefault; + updateInterval = 33; + state.lastUpdate = 0; + state.ticksSinceLastStateSwitch = 0; + state.frameState = FIXED; + state.currentFrame = 0; + state.frameTransitionDirection = 1; + state.isIndicatorDrawen = true; + state.manuelControll = false; + state.userData = NULL; + shouldDrawIndicators = true; + autoTransition = true; + setTimePerFrame(5000); + setTimePerTransition(500); +} + +void OLEDDisplayUi::init() { + this->display->init(); +} + +void OLEDDisplayUi::setTargetFPS(uint8_t fps){ + this->updateInterval = ((float) 1.0 / (float) fps) * 1000; + + this->ticksPerFrame = timePerFrame / updateInterval; + this->ticksPerTransition = timePerTransition / updateInterval; +} + +// -/------ Automatic controll ------\- + +void OLEDDisplayUi::enableAutoTransition(){ + this->autoTransition = true; +} +void OLEDDisplayUi::disableAutoTransition(){ + this->autoTransition = false; +} +void OLEDDisplayUi::setAutoTransitionForwards(){ + this->state.frameTransitionDirection = 1; + this->lastTransitionDirection = 1; +} +void OLEDDisplayUi::setAutoTransitionBackwards(){ + this->state.frameTransitionDirection = -1; + this->lastTransitionDirection = -1; +} +void OLEDDisplayUi::setTimePerFrame(uint16_t time){ + this->timePerFrame = time; + this->ticksPerFrame = timePerFrame / updateInterval; +} +void OLEDDisplayUi::setTimePerTransition(uint16_t time){ + this->timePerTransition = time; + this->ticksPerTransition = timePerTransition / updateInterval; +} + +// -/------ Customize indicator position and style -------\- +void OLEDDisplayUi::enableIndicator(){ + this->state.isIndicatorDrawen = true; +} + +void OLEDDisplayUi::disableIndicator(){ + this->state.isIndicatorDrawen = false; +} + +void OLEDDisplayUi::enableAllIndicators(){ + this->shouldDrawIndicators = true; +} + +void OLEDDisplayUi::disableAllIndicators(){ + this->shouldDrawIndicators = false; +} + +void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) { + this->indicatorPosition = pos; +} +void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) { + this->indicatorDirection = dir; +} +void OLEDDisplayUi::setActiveSymbol(const uint8_t* symbol) { + this->activeSymbol = symbol; +} +void OLEDDisplayUi::setInactiveSymbol(const uint8_t* symbol) { + this->inactiveSymbol = symbol; +} + + +// -/----- Frame settings -----\- +void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) { + this->frameAnimationDirection = dir; +} +void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) { + this->frameFunctions = frameFunctions; + this->frameCount = frameCount; + this->resetState(); +} + +// -/----- Overlays ------\- +void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){ + this->overlayFunctions = overlayFunctions; + this->overlayCount = overlayCount; +} + +// -/----- Loading Process -----\- + +void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) { + this->loadingDrawFunction = loadingDrawFunction; +} + +void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) { + uint8_t progress = 0; + uint8_t increment = 100 / stagesCount; + + for (uint8_t i = 0; i < stagesCount; i++) { + display->clear(); + this->loadingDrawFunction(this->display, &stages[i], progress); + display->display(); + + stages[i].callback(); + + progress += increment; + yield(); + } + + display->clear(); + this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress); + display->display(); + + delay(150); +} + +// -/----- Manuel control -----\- +void OLEDDisplayUi::nextFrame() { + if (this->state.frameState != IN_TRANSITION) { + this->state.manuelControll = true; + this->state.frameState = IN_TRANSITION; + this->state.ticksSinceLastStateSwitch = 0; + this->lastTransitionDirection = this->state.frameTransitionDirection; + this->state.frameTransitionDirection = 1; + } +} +void OLEDDisplayUi::previousFrame() { + if (this->state.frameState != IN_TRANSITION) { + this->state.manuelControll = true; + this->state.frameState = IN_TRANSITION; + this->state.ticksSinceLastStateSwitch = 0; + this->lastTransitionDirection = this->state.frameTransitionDirection; + this->state.frameTransitionDirection = -1; + } +} + +void OLEDDisplayUi::switchToFrame(uint8_t frame) { + if (frame >= this->frameCount) return; + this->state.ticksSinceLastStateSwitch = 0; + if (frame == this->state.currentFrame) return; + this->state.frameState = FIXED; + this->state.currentFrame = frame; + this->state.isIndicatorDrawen = true; +} + +void OLEDDisplayUi::transitionToFrame(uint8_t frame) { + if (frame >= this->frameCount) return; + this->state.ticksSinceLastStateSwitch = 0; + if (frame == this->state.currentFrame) return; + this->nextFrameNumber = frame; + this->lastTransitionDirection = this->state.frameTransitionDirection; + this->state.manuelControll = true; + this->state.frameState = IN_TRANSITION; + this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1; +} + + +// -/----- State information -----\- +OLEDDisplayUiState* OLEDDisplayUi::getUiState(){ + return &this->state; +} + +int16_t OLEDDisplayUi::update(){ +#ifdef ARDUINO + unsigned long frameStart = millis(); +#elif __MBED__ + Timer t; + t.start(); + unsigned long frameStart = t.read_ms(); +#else +#error "Unkown operating system" +#endif + int32_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate); + if ( timeBudget <= 0) { + // Implement frame skipping to ensure time budget is keept + if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil((double)-timeBudget / (double)this->updateInterval); + + this->state.lastUpdate = frameStart; + this->tick(); + } +#ifdef ARDUINO + return this->updateInterval - (millis() - frameStart); +#elif __MBED__ + return this->updateInterval - (t.read_ms() - frameStart); +#else +#error "Unkown operating system" +#endif +} + + +void OLEDDisplayUi::tick() { + this->state.ticksSinceLastStateSwitch++; + + switch (this->state.frameState) { + case IN_TRANSITION: + if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){ + this->state.frameState = FIXED; + this->state.currentFrame = getNextFrameNumber(); + this->state.ticksSinceLastStateSwitch = 0; + this->nextFrameNumber = -1; + } + break; + case FIXED: + // Revert manuelControll + if (this->state.manuelControll) { + this->state.frameTransitionDirection = this->lastTransitionDirection; + this->state.manuelControll = false; + } + if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){ + if (this->autoTransition){ + this->state.frameState = IN_TRANSITION; + } + this->state.ticksSinceLastStateSwitch = 0; + } + break; + } + + this->display->clear(); + this->drawFrame(); + if (shouldDrawIndicators) { + this->drawIndicator(); + } + this->drawOverlays(); + this->display->display(); +} + +void OLEDDisplayUi::resetState() { + this->state.lastUpdate = 0; + this->state.ticksSinceLastStateSwitch = 0; + this->state.frameState = FIXED; + this->state.currentFrame = 0; + this->state.isIndicatorDrawen = true; +} + +void OLEDDisplayUi::drawFrame(){ + switch (this->state.frameState){ + case IN_TRANSITION: { + float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition; + int16_t x = 0, y = 0, x1 = 0, y1 = 0; + switch(this->frameAnimationDirection){ + case SLIDE_LEFT: + x = -this->display->width() * progress; + y = 0; + x1 = x + this->display->width(); + y1 = 0; + break; + case SLIDE_RIGHT: + x = this->display->width() * progress; + y = 0; + x1 = x - this->display->width(); + y1 = 0; + break; + case SLIDE_UP: + x = 0; + y = -this->display->height() * progress; + x1 = 0; + y1 = y + this->display->height(); + break; + case SLIDE_DOWN: + default: + x = 0; + y = this->display->height() * progress; + x1 = 0; + y1 = y - this->display->height(); + break; + } + + // Invert animation if direction is reversed. + int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1; + x *= dir; y *= dir; x1 *= dir; y1 *= dir; + + bool drawenCurrentFrame; + + + // Prope each frameFunction for the indicator Drawen state + this->enableIndicator(); + (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y); + drawenCurrentFrame = this->state.isIndicatorDrawen; + + this->enableIndicator(); + (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1); + + // Build up the indicatorDrawState + if (drawenCurrentFrame && !this->state.isIndicatorDrawen) { + // Drawen now but not next + this->indicatorDrawState = 2; + } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) { + // Not drawen now but next + this->indicatorDrawState = 1; + } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) { + // Not drawen in both frames + this->indicatorDrawState = 3; + } + + // If the indicator isn't draw in the current frame + // reflect it in state.isIndicatorDrawen + if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false; + + break; + } + case FIXED: + // Always assume that the indicator is drawn! + // And set indicatorDrawState to "not known yet" + this->indicatorDrawState = 0; + this->enableIndicator(); + (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0); + break; + } +} + +void OLEDDisplayUi::drawIndicator() { + + // Only draw if the indicator is invisible + // for both frames or + // the indiactor is shown and we are IN_TRANSITION + if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) { + return; + } + + uint8_t posOfHighlightFrame = 0; + float indicatorFadeProgress = 0; + + // if the indicator needs to be slided in we want to + // highlight the next frame in the transition + uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame; + + // Calculate the frame that needs to be highlighted + // based on the Direction the indiactor is drawn + switch (this->indicatorDirection){ + case LEFT_RIGHT: + posOfHighlightFrame = frameToHighlight; + break; + case RIGHT_LEFT: + default: + posOfHighlightFrame = this->frameCount - frameToHighlight; + break; + } + + switch (this->indicatorDrawState) { + case 1: // Indicator was not drawn in this frame but will be in next + // Slide IN + indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition); + break; + case 2: // Indicator was drawn in this frame but not in next + // Slide OUT + indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition); + break; + } + + //Space between indicators - reduce for small screen sizes + uint16_t indicatorSpacing = 12; + if (this->display->getHeight() < 64 && (this->indicatorPosition == RIGHT || this->indicatorPosition == LEFT)) { + indicatorSpacing = 6; + } + + uint16_t frameStartPos = (indicatorSpacing * frameCount / 2); + const uint8_t *image; + + uint16_t x = 0,y = 0; + + + for (uint8_t i = 0; i < this->frameCount; i++) { + + switch (this->indicatorPosition){ + case TOP: + y = 0 - (8 * indicatorFadeProgress); + x = (this->display->width() / 2) - frameStartPos + 12 * i; + break; + case BOTTOM: + y = (this->display->height() - 8) + (8 * indicatorFadeProgress); + x = (this->display->width() / 2) - frameStartPos + 12 * i; + break; + case RIGHT: + x = (this->display->width() - 8) + (8 * indicatorFadeProgress); + y = (this->display->height() / 2) - frameStartPos + 2 + 12 * i; + break; + case LEFT: + default: + x = 0 - (8 * indicatorFadeProgress); + y = (this->display->height() / 2) - frameStartPos + 2 + indicatorSpacing * i; + break; + } + + if (posOfHighlightFrame == i) { + image = this->activeSymbol; + } else { + image = this->inactiveSymbol; + } + + this->display->drawFastImage(x, y, 8, 8, image); + } +} + +void OLEDDisplayUi::drawOverlays() { + for (uint8_t i=0;ioverlayCount;i++){ + (this->overlayFunctions[i])(this->display, &this->state); + } +} + +uint8_t OLEDDisplayUi::getNextFrameNumber(){ + if (this->nextFrameNumber != -1) return this->nextFrameNumber; + return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount; +} diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.h b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.h new file mode 100644 index 00000000..9aa3f320 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.h @@ -0,0 +1,315 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef OLEDDISPLAYUI_h +#define OLEDDISPLAYUI_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#include +#else +#error "Unkown operating system" +#endif + +#include "OLEDDisplay.h" + +//#define DEBUG_OLEDDISPLAYUI(...) Serial.printf( __VA_ARGS__ ) + +#ifndef DEBUG_OLEDDISPLAYUI +#define DEBUG_OLEDDISPLAYUI(...) +#endif + +enum AnimationDirection { + SLIDE_UP, + SLIDE_DOWN, + SLIDE_LEFT, + SLIDE_RIGHT +}; + +enum IndicatorPosition { + TOP, + RIGHT, + BOTTOM, + LEFT +}; + +enum IndicatorDirection { + LEFT_RIGHT, + RIGHT_LEFT +}; + +enum FrameState { + IN_TRANSITION, + FIXED +}; + + +const uint8_t ANIMATION_activeSymbol[] PROGMEM = { + 0x00, 0x18, 0x3c, 0x7e, 0x7e, 0x3c, 0x18, 0x00 +}; + +const uint8_t ANIMATION_inactiveSymbol[] PROGMEM = { + 0x00, 0x0, 0x0, 0x18, 0x18, 0x0, 0x0, 0x00 +}; + + +// Structure of the UiState +struct OLEDDisplayUiState { + uint64_t lastUpdate; + uint16_t ticksSinceLastStateSwitch; + + FrameState frameState; + uint8_t currentFrame; + + bool isIndicatorDrawen; + + // Normal = 1, Inverse = -1; + int8_t frameTransitionDirection; + + bool manuelControll; + + // Custom data that can be used by the user + void* userData; +}; + +struct LoadingStage { + const char* process; + void (*callback)(); +}; + +typedef void (*FrameCallback)(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); +typedef void (*OverlayCallback)(OLEDDisplay *display, OLEDDisplayUiState* state); +typedef void (*LoadingDrawFunction)(OLEDDisplay *display, LoadingStage* stage, uint8_t progress); + +class OLEDDisplayUi { + private: + OLEDDisplay *display; + + // Symbols for the Indicator + IndicatorPosition indicatorPosition; + IndicatorDirection indicatorDirection; + + const uint8_t* activeSymbol; + const uint8_t* inactiveSymbol; + + bool shouldDrawIndicators; + + // Values for the Frames + AnimationDirection frameAnimationDirection; + + int8_t lastTransitionDirection; + + uint16_t ticksPerFrame; // ~ 5000ms at 30 FPS + uint16_t ticksPerTransition; // ~ 500ms at 30 FPS + + bool autoTransition; + + FrameCallback* frameFunctions; + uint8_t frameCount; + + // Internally used to transition to a specific frame + int8_t nextFrameNumber; + + // Values for Overlays + OverlayCallback* overlayFunctions; + uint8_t overlayCount; + + // Will the Indicator be drawen + // 3 Not drawn in both frames + // 2 Drawn this frame but not next + // 1 Not drown this frame but next + // 0 Not known yet + uint8_t indicatorDrawState; + + // Loading screen + LoadingDrawFunction loadingDrawFunction; + + // UI State + OLEDDisplayUiState state; + + // Bookeeping for update + uint16_t updateInterval = 33; + + uint16_t timePerFrame; + uint16_t timePerTransition; + + uint8_t getNextFrameNumber(); + void drawIndicator(); + void drawFrame(); + void drawOverlays(); + void tick(); + void resetState(); + + public: + + OLEDDisplayUi(OLEDDisplay *display); + + /** + * Initialise the display + */ + void init(); + + /** + * Configure the internal used target FPS + */ + void setTargetFPS(uint8_t fps); + + // Automatic Controll + /** + * Enable automatic transition to next frame after the some time can be configured with `setTimePerFrame` and `setTimePerTransition`. + */ + void enableAutoTransition(); + + /** + * Disable automatic transition to next frame. + */ + void disableAutoTransition(); + + /** + * Set the direction if the automatic transitioning + */ + void setAutoTransitionForwards(); + void setAutoTransitionBackwards(); + + /** + * Set the approx. time a frame is displayed + */ + void setTimePerFrame(uint16_t time); + + /** + * Set the approx. time a transition will take + */ + void setTimePerTransition(uint16_t time); + + // Customize indicator position and style + + /** + * Draw the indicator. + * This is the defaut state for all frames if + * the indicator was hidden on the previous frame + * it will be slided in. + */ + void enableIndicator(); + + /** + * Don't draw the indicator. + * This will slide out the indicator + * when transitioning to the next frame. + */ + void disableIndicator(); + + /** + * Enable drawing of indicators + */ + void enableAllIndicators(); + + /** + * Disable draw of indicators. + */ + void disableAllIndicators(); + + /** + * Set the position of the indicator bar. + */ + void setIndicatorPosition(IndicatorPosition pos); + + /** + * Set the direction of the indicator bar. Defining the order of frames ASCENDING / DESCENDING + */ + void setIndicatorDirection(IndicatorDirection dir); + + /** + * Set the symbol to indicate an active frame in the indicator bar. + */ + void setActiveSymbol(const uint8_t* symbol); + + /** + * Set the symbol to indicate an inactive frame in the indicator bar. + */ + void setInactiveSymbol(const uint8_t* symbol); + + + // Frame settings + + /** + * Configure what animation is used to transition from one frame to another + */ + void setFrameAnimation(AnimationDirection dir); + + /** + * Add frame drawing functions + */ + void setFrames(FrameCallback* frameFunctions, uint8_t frameCount); + + // Overlay + + /** + * Add overlays drawing functions that are draw independent of the Frames + */ + void setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount); + + + // Loading animation + /** + * Set the function that will draw each step + * in the loading animation + */ + void setLoadingDrawFunction(LoadingDrawFunction loadingFunction); + + + /** + * Run the loading process + */ + void runLoadingProcess(LoadingStage* stages, uint8_t stagesCount); + + + // Manual Control + void nextFrame(); + void previousFrame(); + + /** + * Switch without transition to frame `frame`. + */ + void switchToFrame(uint8_t frame); + + /** + * Transition to frame `frame`, when the `frame` number is bigger than the current + * frame the forward animation will be used, otherwise the backwards animation is used. + */ + void transitionToFrame(uint8_t frame); + + // State Info + OLEDDisplayUiState* getUiState(); + + int16_t update(); +}; +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SH1106.h b/lib/esp8266-oled-ssd1306-master/src/SH1106.h new file mode 100644 index 00000000..47188d1f --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SH1106.h @@ -0,0 +1,39 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SH1106_h +#define SH1106_h +#include "SH1106Wire.h" + +// For make SH1106 an alias for SH1106Wire +typedef SH1106Wire SH1106; + + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SH1106Brzo.h b/lib/esp8266-oled-ssd1306-master/src/SH1106Brzo.h new file mode 100644 index 00000000..be9c0c74 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SH1106Brzo.h @@ -0,0 +1,141 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SH1106Brzo_h +#define SH1106Brzo_h + +#include "OLEDDisplay.h" +#include + +#if F_CPU == 160000000L + #define BRZO_I2C_SPEED 1000 +#else + #define BRZO_I2C_SPEED 800 +#endif + +class SH1106Brzo : public OLEDDisplay { + private: + uint8_t _address; + uint8_t _sda; + uint8_t _scl; + + public: + SH1106Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) { + setGeometry(g); + + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; + } + + bool connect(){ + brzo_i2c_setup(_sda, _scl, 0); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT8_MAX) return; + + byte k = 0; + uint8_t sendBuffer[17]; + sendBuffer[0] = 0x40; + + // Calculate the colum offset + uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F; + uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 ); + + brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); + + for (y = minBoundY; y <= maxBoundY; y++) { + sendCommand(0xB0 + y); + sendCommand(minBoundXp2H); + sendCommand(minBoundXp2L); + for (x = minBoundX; x <= maxBoundX; x++) { + k++; + sendBuffer[k] = buffer[x + y * displayWidth]; + if (k == 16) { + brzo_i2c_write(sendBuffer, 17, true); + k = 0; + } + } + if (k != 0) { + brzo_i2c_write(sendBuffer, k + 1, true); + k = 0; + } + yield(); + } + if (k != 0) { + brzo_i2c_write(sendBuffer, k + 1, true); + } + brzo_i2c_end_transaction(); + #else + #endif + } + + private: + int getBufferOffset(void) { + return 0; + } + inline void sendCommand(uint8_t com) __attribute__((always_inline)){ + uint8_t command[2] = {0x80 /* command mode */, com}; + brzo_i2c_start_transaction(_address, BRZO_I2C_SPEED); + brzo_i2c_write(command, 2, true); + brzo_i2c_end_transaction(); + } +}; + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SH1106Spi.h b/lib/esp8266-oled-ssd1306-master/src/SH1106Spi.h new file mode 100644 index 00000000..23693bc4 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SH1106Spi.h @@ -0,0 +1,135 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SH1106Spi_h +#define SH1106Spi_h + +#include "OLEDDisplay.h" +#include + +class SH1106Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + + public: + SH1106Spi(uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) { + setGeometry(g); + + this->_rst = _rst; + this->_dc = _dc; + } + + bool connect(){ + pinMode(_dc, OUTPUT); + pinMode(_rst, OUTPUT); + + SPI.begin (); + SPI.setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT8_MAX) return; + + // Calculate the colum offset + uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F; + uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 ); + + for (y = minBoundY; y <= maxBoundY; y++) { + sendCommand(0xB0 + y); + sendCommand(minBoundXp2H); + sendCommand(minBoundXp2L); + digitalWrite(_dc, HIGH); // data mode + for (x = minBoundX; x <= maxBoundX; x++) { + SPI.transfer(buffer[x + y * displayWidth]); + } + yield(); + } + #else + for (uint8_t y=0; y + +#define SH1106_SET_PUMP_VOLTAGE 0X30 +#define SH1106_SET_PUMP_MODE 0XAD +#define SH1106_PUMP_ON 0X8B +#define SH1106_PUMP_OFF 0X8A +//-------------------------------------- + +class SH1106Wire : public OLEDDisplay { + private: + uint8_t _address; + uint8_t _sda; + uint8_t _scl; + bool _doI2cAutoInit = false; + TwoWire* _wire = NULL; + int _frequency; + + public: + /** + * Create and initialize the Display using Wire library + * + * Beware for retro-compatibility default values are provided for all parameters see below. + * Please note that if you don't wan't SD1306Wire to initialize and change frequency speed ot need to + * ensure -1 value are specified for all 3 parameters. This can be usefull to control TwoWire with multiple + * device on the same bus. + * + * @param _address I2C Display address + * @param _sda I2C SDA pin number, default to -1 to skip Wire begin call + * @param _scl I2C SCL pin number, default to -1 (only SDA = -1 is considered to skip Wire begin call) + * @param g display geometry dafault to generic GEOMETRY_128_64, see OLEDDISPLAY_GEOMETRY definition for other options + * @param _i2cBus on ESP32 with 2 I2C HW buses, I2C_ONE for 1st Bus, I2C_TWO fot 2nd bus, default I2C_ONE + * @param _frequency for Frequency by default Let's use ~700khz if ESP8266 is in 160Mhz mode, this will be limited to ~400khz if the ESP8266 in 80Mhz mode + */ + SH1106Wire(uint8_t _address, uint8_t _sda, uint8_t _scl, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64, HW_I2C _i2cBus = I2C_ONE, int _frequency = 700000) { + setGeometry(g); + + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; +#if !defined(ARDUINO_ARCH_ESP32) + this->_wire = &Wire; +#else + this->_wire = (_i2cBus==I2C_ONE) ? &Wire : &Wire1; +#endif + this->_frequency = _frequency; + } + + bool connect() { +#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH8266) + _wire->begin(); +#else + // On ESP32 arduino, -1 means 'don't change pins', someone else has called begin for us. + if(this->_sda != -1) + _wire->begin(this->_sda, this->_scl); +#endif + // Let's use ~700khz if ESP8266 is in 160Mhz mode + // this will be limited to ~400khz if the ESP8266 in 80Mhz mode. + if(this->_frequency != -1) + _wire->setClock(this->_frequency); + return true; + } + + void display(void) { + initI2cIfNeccesary(); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT8_MAX) return; + + // Calculate the colum offset + uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F; + uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 ); + + byte k = 0; + for (y = minBoundY; y <= maxBoundY; y++) { + sendCommand(0xB0 + y); + sendCommand(minBoundXp2H); + sendCommand(minBoundXp2L); + for (x = minBoundX; x <= maxBoundX; x++) { + if (k == 0) { + _wire->beginTransmission(_address); + _wire->write(0x40); + } + _wire->write(buffer[x + y * displayWidth]); + k++; + if (k == 16) { + _wire->endTransmission(); + k = 0; + } + } + if (k != 0) { + _wire->endTransmission(); + k = 0; + } + yield(); + } + + if (k != 0) { + _wire->endTransmission(); + } + #else + uint8_t * p = &buffer[0]; + for (uint8_t y=0; y<8; y++) { + sendCommand(0xB0+y); + sendCommand(0x02); + sendCommand(0x10); + for( uint8_t x=0; x<8; x++) { + _wire->beginTransmission(_address); + _wire->write(0x40); + for (uint8_t k = 0; k < 16; k++) { + _wire->write(*p++); + } + _wire->endTransmission(); + } + } + #endif + } + + void setI2cAutoInit(bool doI2cAutoInit) { + _doI2cAutoInit = doI2cAutoInit; + } + + private: + int getBufferOffset(void) { + return 0; + } + inline void sendCommand(uint8_t command) __attribute__((always_inline)){ + _wire->beginTransmission(_address); + _wire->write(0x80); + _wire->write(command); + _wire->endTransmission(); + } + + void initI2cIfNeccesary() { + if (_doI2cAutoInit) { +#ifdef ARDUINO_ARCH_AVR + _wire->begin(); +#else + _wire->begin(this->_sda, this->_scl); +#endif + } + } + +}; + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SSD1306.h b/lib/esp8266-oled-ssd1306-master/src/SSD1306.h new file mode 100644 index 00000000..f6bd554c --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SSD1306.h @@ -0,0 +1,39 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SSD1306_h +#define SSD1306_h +#include "SSD1306Wire.h" + +// For legacy support make SSD1306 an alias for SSD1306 +typedef SSD1306Wire SSD1306; + + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SSD1306Brzo.h b/lib/esp8266-oled-ssd1306-master/src/SSD1306Brzo.h new file mode 100644 index 00000000..fbcffcda --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SSD1306Brzo.h @@ -0,0 +1,167 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SSD1306Brzo_h +#define SSD1306Brzo_h + +#include "OLEDDisplay.h" +#include + +#if F_CPU == 160000000L + #define BRZO_I2C_SPEED 1000 +#else + #define BRZO_I2C_SPEED 800 +#endif + +class SSD1306Brzo : public OLEDDisplay { + private: + uint8_t _address; + uint8_t _sda; + uint8_t _scl; + + public: + SSD1306Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) { + setGeometry(g); + + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; + } + + bool connect(){ + brzo_i2c_setup(_sda, _scl, 0); + return true; + } + + void display(void) { + const int x_offset = (128 - this->width()) / 2; + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (this->height() / 8); y++) { + for (x = 0; x < this->width(); x++) { + uint16_t pos = x + y * this->width(); + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT8_MAX) return; + + sendCommand(COLUMNADDR); + sendCommand(x_offset + minBoundX); + sendCommand(x_offset + maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + byte k = 0; + + int buflen = ( this->width() / 8 ) + 1; + + uint8_t sendBuffer[buflen]; + sendBuffer[0] = 0x40; + brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + k++; + sendBuffer[k] = buffer[x + y * this->width()]; + if (k == (buflen-1)) { + brzo_i2c_write(sendBuffer, buflen, true); + k = 0; + } + } + yield(); + } + brzo_i2c_write(sendBuffer, k + 1, true); + brzo_i2c_end_transaction(); + #else + // No double buffering + sendCommand(COLUMNADDR); + + sendCommand(x_offset); + sendCommand(x_offset + (this->width() - 1)); + + sendCommand(PAGEADDR); + sendCommand(0x0); + sendCommand((this->height() / 8) - 1); + + int buflen = ( this->width() / 8 ) + 1; + + uint8_t sendBuffer[buflen]; + sendBuffer[0] = 0x40; + + brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); + + for (uint16_t i=0; i + +#ifndef UINT8_MAX + #define UINT8_MAX 0xff +#endif + +class SSD1306I2C : public OLEDDisplay { +public: + SSD1306I2C(uint8_t _address, PinName _sda, PinName _scl, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) { + setGeometry(g); + + this->_address = _address << 1; // convert from 7 to 8 bit for mbed. + this->_sda = _sda; + this->_scl = _scl; + _i2c = new I2C(_sda, _scl); + } + + bool connect() { + // mbed supports 100k and 400k some device maybe 1000k +#ifdef TARGET_STM32L4 + _i2c->frequency(1000000); +#else + _i2c->frequency(400000); +#endif + return true; + } + + void display(void) { + const int x_offset = (128 - this->width()) / 2; +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (this->height() / 8); y++) { + for (x = 0; x < this->width(); x++) { + uint16_t pos = x + y * this->width(); + if (buffer[pos] != buffer_back[pos]) { + minBoundY = std::min(minBoundY, y); + maxBoundY = std::max(maxBoundY, y); + minBoundX = std::min(minBoundX, x); + maxBoundX = std::max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + + if (minBoundY == UINT8_MAX) return; + + sendCommand(COLUMNADDR); + sendCommand(x_offset + minBoundX); // column start address (0 = reset) + sendCommand(x_offset + maxBoundX); // column end address (127 = reset) + + sendCommand(PAGEADDR); + sendCommand(minBoundY); // page start address + sendCommand(maxBoundY); // page end address + + for (y = minBoundY; y <= maxBoundY; y++) { + uint8_t *start = &buffer[(minBoundX + y * this->width())-1]; + uint8_t save = *start; + + *start = 0x40; // control + _i2c->write(_address, (char *)start, (maxBoundX-minBoundX) + 1 + 1); + *start = save; + } +#else + + sendCommand(COLUMNADDR); + sendCommand(x_offset); // column start address (0 = reset) + sendCommand(x_offset + (this->width() - 1));// column end address (127 = reset) + + sendCommand(PAGEADDR); + sendCommand(0x0); // page start address (0 = reset) + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x7); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x3); + } + + buffer[-1] = 0x40; // control + _i2c->write(_address, (char *)&buffer[-1], displayBufferSize + 1); +#endif + } + +private: + int getBufferOffset(void) { + return 0; + } + + inline void sendCommand(uint8_t command) __attribute__((always_inline)) { + char _data[2]; + _data[0] = 0x80; // control + _data[1] = command; + _i2c->write(_address, _data, sizeof(_data)); + } + + uint8_t _address; + PinName _sda; + PinName _scl; + I2C *_i2c; +}; + +#endif + +#endif diff --git a/lib/esp8266-oled-ssd1306-master/src/SSD1306Spi.h b/lib/esp8266-oled-ssd1306-master/src/SSD1306Spi.h new file mode 100644 index 00000000..08cb4a80 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/src/SSD1306Spi.h @@ -0,0 +1,163 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef SSD1306Spi_h +#define SSD1306Spi_h + +#include "OLEDDisplay.h" +#include + +#if F_CPU == 160000000L + #define BRZO_I2C_SPEED 1000 +#else + #define BRZO_I2C_SPEED 800 +#endif + +class SSD1306Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + + public: + SSD1306Spi(uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) { + setGeometry(g); + + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + } + + bool connect(){ + pinMode(_dc, OUTPUT); + pinMode(_cs, OUTPUT); + pinMode(_rst, OUTPUT); + + SPI.begin (); + SPI.setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (displayHeight / 8); y++) { + for (x = 0; x < displayWidth; x++) { + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT8_MAX) return; + + sendCommand(COLUMNADDR); + sendCommand(minBoundX); + sendCommand(maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + digitalWrite(_cs, HIGH); + digitalWrite(_dc, HIGH); // data mode + digitalWrite(_cs, LOW); + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + SPI.transfer(buffer[x + y * displayWidth]); + } + yield(); + } + digitalWrite(_cs, HIGH); + #else + // No double buffering + sendCommand(COLUMNADDR); + sendCommand(0x0); + sendCommand(0x7F); + + sendCommand(PAGEADDR); + sendCommand(0x0); + + if (geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32 ) { + sendCommand(0x7); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x3); + } + + digitalWrite(_cs, HIGH); + digitalWrite(_dc, HIGH); // data mode + digitalWrite(_cs, LOW); + for (uint16_t i=0; i +#include + +#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_STM32) +#define _min min +#define _max max +#endif +//-------------------------------------- + +class SSD1306Wire : public OLEDDisplay { + private: + uint8_t _address; + int _sda; + int _scl; + bool _doI2cAutoInit = false; + TwoWire* _wire = NULL; + int _frequency; + + public: + + /** + * Create and initialize the Display using Wire library + * + * Beware for retro-compatibility default values are provided for all parameters see below. + * Please note that if you don't wan't SD1306Wire to initialize and change frequency speed ot need to + * ensure -1 value are specified for all 3 parameters. This can be usefull to control TwoWire with multiple + * device on the same bus. + * + * @param _address I2C Display address + * @param _sda I2C SDA pin number, default to -1 to skip Wire begin call + * @param _scl I2C SCL pin number, default to -1 (only SDA = -1 is considered to skip Wire begin call) + * @param g display geometry dafault to generic GEOMETRY_128_64, see OLEDDISPLAY_GEOMETRY definition for other options + * @param _i2cBus on ESP32 with 2 I2C HW buses, I2C_ONE for 1st Bus, I2C_TWO fot 2nd bus, default I2C_ONE + * @param _frequency for Frequency by default Let's use ~700khz if ESP8266 is in 160Mhz mode, this will be limited to ~400khz if the ESP8266 in 80Mhz mode + */ + SSD1306Wire(uint8_t _address, int _sda = -1, int _scl = -1, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64, HW_I2C _i2cBus = I2C_ONE, int _frequency = 700000) { + setGeometry(g); + + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; +#if !defined(ARDUINO_ARCH_ESP32) + this->_wire = &Wire; +#else + this->_wire = (_i2cBus==I2C_ONE) ? &Wire : &Wire1; +#endif + this->_frequency = _frequency; + } + + bool connect() { +#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH8266) + _wire->begin(); +#else + // On ESP32 arduino, -1 means 'don't change pins', someone else has called begin for us. + if(this->_sda != -1) + _wire->begin(this->_sda, this->_scl); +#endif + // Let's use ~700khz if ESP8266 is in 160Mhz mode + // this will be limited to ~400khz if the ESP8266 in 80Mhz mode. + if(this->_frequency != -1) + _wire->setClock(this->_frequency); + return true; + } + + void display(void) { + initI2cIfNeccesary(); + const int x_offset = (128 - this->width()) / 2; + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = UINT8_MAX; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = UINT8_MAX; + uint8_t maxBoundX = 0; + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (this->height() / 8); y++) { + for (x = 0; x < this->width(); x++) { + uint16_t pos = x + y * this->width(); + if (buffer[pos] != buffer_back[pos]) { + minBoundY = std::min(minBoundY, y); + maxBoundY = std::max(maxBoundY, y); + minBoundX = std::min(minBoundX, x); + maxBoundX = std::max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + + if (minBoundY == UINT8_MAX) return; + + sendCommand(COLUMNADDR); + sendCommand(x_offset + minBoundX); + sendCommand(x_offset + maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + byte k = 0; + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + if (k == 0) { + _wire->beginTransmission(_address); + _wire->write(0x40); + } + + _wire->write(buffer[x + y * this->width()]); + k++; + if (k == 16) { + _wire->endTransmission(); + k = 0; + } + } + yield(); + } + + if (k != 0) { + _wire->endTransmission(); + } + #else + + sendCommand(COLUMNADDR); + sendCommand(x_offset); + sendCommand(x_offset + (this->width() - 1)); + + sendCommand(PAGEADDR); + sendCommand(0x0); + + for (uint16_t i=0; i < displayBufferSize; i++) { + _wire->beginTransmission(this->_address); + _wire->write(0x40); + for (uint8_t x = 0; x < 16; x++) { + _wire->write(buffer[i]); + i++; + } + i--; + _wire->endTransmission(); + } + #endif + } + + void setI2cAutoInit(bool doI2cAutoInit) { + _doI2cAutoInit = doI2cAutoInit; + } + + private: + int getBufferOffset(void) { + return 0; + } + inline void sendCommand(uint8_t command) __attribute__((always_inline)){ + initI2cIfNeccesary(); + _wire->beginTransmission(_address); + _wire->write(0x80); + _wire->write(command); + _wire->endTransmission(); + } + + void initI2cIfNeccesary() { + if (_doI2cAutoInit) { +#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH8266) + _wire->begin(); +#else + _wire->begin(this->_sda, this->_scl); +#endif + } + } + +}; + +#endif diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 0e0b6990..29eea492 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -111,7 +111,8 @@ const char* const CFG_MODE_P[] PROGMEM = {CFG_10_PRESSES, CFG_5SEK_HOLD}; #ifdef SUPLA_OLED const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; const char SH1106[] PROGMEM = "SH1106 - 1,3''"; -const char *const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106}; +const char SSD1306_WEMOS_SHIELD[] PROGMEM = "SSD1306 - 0,66'' WEMOS OLED shield"; +const char *const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106, SSD1306_WEMOS_SHIELD}; #endif String StateString(uint8_t adr); diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index 0dfa7c1d..70b9b328 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -103,8 +103,10 @@ void displayRelayState(OLEDDisplay* display) { } x += 15; } - display->setColor(WHITE); - display->drawHorizontalLine(0, 14, display->getWidth()); + if (!GEOMETRY_64_48) { + display->setColor(WHITE); + display->drawHorizontalLine(0, 14, display->getWidth()); + } } void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state) { @@ -149,45 +151,78 @@ void displayBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, in void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name) { int drawHeightIcon = display->getHeight() / 2 - 10; - int drawStringIcon = display->getHeight() / 2 - 5; + int drawStringIcon = display->getHeight() / 2 - 6; + + int temp_width = TEMP_WIDTH; + int temp_height = TEMP_HEIGHT; + + if (GEOMETRY_64_48) { + temp_width = 0; + temp_height = 0; + } + else { + temp_width = temp_width + 10; + } display->setColor(WHITE); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawXbm(x + 0, y + drawHeightIcon, temp_width, temp_height, temp_bits); display->setFont(ArialMT_Plain_10); if (name != NULL) { - display->drawString(x + temp_width + 20, y + display->getHeight() / 2 - 15, name); + display->drawString(x + temp_width, y + display->getHeight() / 2 - 15, name); } display->setFont(ArialMT_Plain_24); - display->drawString(x + temp_width + 10, y + drawStringIcon, getTempString(temp)); + display->drawString(x + temp_width, y + drawStringIcon, getTempString(temp)); display->setFont(ArialMT_Plain_16); - display->drawString(x + temp_width + 10 + (getTempString(temp).length() * 12), y + drawStringIcon, "ºC"); + display->drawString(x + temp_width + (getTempString(temp).length() * 12), y + drawStringIcon, "ºC"); } void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { int drawHeightIcon = display->getHeight() / 2 - 10; - int drawStringIcon = display->getHeight() / 2 - 5; + int drawStringIcon = display->getHeight() / 2 - 6; + + int humidity_width = HUMIDITY_WIDTH; + int humidity_height = HUMIDITY_HEIGHT; + + if (GEOMETRY_64_48) { + humidity_width = 0; + humidity_height = 0; + } + else { + humidity_width = humidity_width + 10; + } display->setColor(WHITE); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawXbm(x + 0, y + drawHeightIcon, humidity_width, humidity_height, humidity_bits); display->setFont(ArialMT_Plain_24); - display->drawString(x + humidity_width + 20, y + drawStringIcon, getHumidityString(humidity)); + display->drawString(x + humidity_width, y + drawStringIcon, getHumidityString(humidity)); display->setFont(ArialMT_Plain_16); - display->drawString(x + humidity_width + 20 + (getHumidityString(humidity).length() * 12), y + drawStringIcon, "%"); + display->drawString(x + humidity_width + (getHumidityString(humidity).length() * 12), y + drawStringIcon, "%"); } void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { int drawHeightIcon = display->getHeight() / 2 - 10; - int drawStringIcon = display->getHeight() / 2 - 5; + int drawStringIcon = display->getHeight() / 2 - 6; + + int pressure_width = PRESSURE_WIDTH; + int pressure_height = PRESSURE_HEIGHT; + + if (GEOMETRY_64_48) { + pressure_width = 0; + pressure_height = 0; + } + else { + pressure_width = pressure_width + 15; + } display->setColor(WHITE); display->setTextAlignment(TEXT_ALIGN_LEFT); display->drawXbm(x + 0, y + drawHeightIcon, pressure_width, pressure_height, pressure_bits); display->setFont(ArialMT_Plain_24); - display->drawString(x + pressure_width + 15, y + drawStringIcon, getPressureString(pressure)); + display->drawString(x + pressure_width, y + drawStringIcon, getPressureString(pressure)); display->setFont(ArialMT_Plain_10); - display->drawString(x + pressure_width + 15 + (getPressureString(pressure).length() * 14), y + drawStringIcon, "hPa"); + display->drawString(x + pressure_width + (getPressureString(pressure).length() * 14), y + drawStringIcon, "hPa"); } void displayDs18b20(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { @@ -229,11 +264,16 @@ void displayMAX6675Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t SuplaOled::SuplaOled() { if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { - if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { - display = new SH1106Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); - } - else { - display = new SSD1306Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { + case OLED_SSD1306_0_96: + display = new SSD1306Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + break; + case OLED_SH1106_1_3: + display = new SH1106Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); + break; + case OLED_SSD1306_0_66: + display = new SSD1306Wire(0x3c, ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL), GEOMETRY_64_48); + break; } ui = new OLEDDisplayUi(display); diff --git a/src/SuplaOled.h b/src/SuplaOled.h index 3e6c6a96..dc3b6c4d 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -8,7 +8,7 @@ #include #include #include // Only needed for Arduino 1.6.5 and earlier -#include //OLED 0,96" +#include //OLED 0,96" or 0.66" WEMOS OLED shield #include //OLED 1.3" #include @@ -17,6 +17,13 @@ enum customActions TURN_ON_OLED }; +enum _OLED +{ + OLED_SSD1306_0_96 = 1, + OLED_SH1106_1_3, + OLED_SSD1306_0_66 +}; + String getTempString(double temperature); String getHumidityString(double humidity); String getPressureString(double pressure); @@ -50,10 +57,10 @@ class SuplaOled : public Supla::Triggerable, public Supla::Element { void iterateAlways(); void runAction(int event, int action); - OLEDDisplay *display; - OLEDDisplayUi *ui; + OLEDDisplay* display; + OLEDDisplayUi* ui; - FrameCallback *frames; + FrameCallback* frames; int frameCount = 0; OverlayCallback overlays[1]; int overlaysCount = 1; @@ -65,8 +72,8 @@ class SuplaOled : public Supla::Triggerable, public Supla::Element { }; // https://www.online-utility.org/image/convert/to/XBM -#define temp_width 32 -#define temp_height 32 +#define TEMP_WIDTH 32 +#define TEMP_HEIGHT 32 const uint8_t temp_bits[] PROGMEM = {0x00, 0x3C, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x81, 0xF8, 0x03, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0xF8, 0x03, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0xF8, 0x03, 0x00, 0x99, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, @@ -75,8 +82,8 @@ const uint8_t temp_bits[] PROGMEM = {0x00, 0x3C, 0x00, 0x00, 0x00, 0x66, 0x00, 0 0x00, 0x20, 0x7E, 0x04, 0x00, 0x20, 0x7E, 0x04, 0x00, 0x20, 0x7E, 0x04, 0x00, 0x60, 0x3C, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x80, 0x81, 0x01, 0x00, 0x00, 0xC3, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00}; -#define humidity_width 32 -#define humidity_height 32 +#define HUMIDITY_WIDTH 32 +#define HUMIDITY_HEIGHT 32 const uint8_t humidity_bits[] PROGMEM = { 0x00, 0x80, 0x01, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x30, 0x0C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x80, 0x01, 0x80, 0x00, @@ -85,8 +92,8 @@ const uint8_t humidity_bits[] PROGMEM = { 0x30, 0x10, 0x09, 0x0C, 0x30, 0x00, 0x06, 0x0C, 0x20, 0x00, 0x00, 0x04, 0x60, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x0E, 0x70, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0xF0, 0x0F, 0x00}; -#define pressure_width 32 -#define pressure_height 32 +#define PRESSURE_WIDTH 32 +#define PRESSURE_HEIGHT 32 const uint8_t pressure_bits[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x80, 0x81, 0x81, 0x01, 0xC0, 0x80, 0x01, 0x03, From 7f9a935418b904a93cbe32c87a326ec1cfe064b2 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Mon, 28 Dec 2020 16:59:13 +0100 Subject: [PATCH 094/233] wsparcie dla HLW8012 --- lib/SuplaDevice/src/supla/supla_lib_config.h | 2 +- platformio.ini | 7 +- src/GUI-Generic.ino | 9 +- src/GUI-Generic_Config.h | 1 + src/Markup.cpp | 35 ++++++ src/Markup.h | 4 + src/SuplaConfigESP.cpp | 7 ++ src/SuplaConfigManager.h | 58 +++++----- src/SuplaDeviceGUI.cpp | 18 +++- src/SuplaDeviceGUI.h | 8 ++ src/SuplaTemplateBoard.cpp | 18 ++++ src/SuplaTemplateBoard.h | 5 +- src/SuplaWebPageSensor.cpp | 107 ++++++++++++++++++- src/SuplaWebPageSensor.h | 16 +++ src/SuplaWebPageUpload.cpp | 3 +- 15 files changed, 257 insertions(+), 41 deletions(-) diff --git a/lib/SuplaDevice/src/supla/supla_lib_config.h b/lib/SuplaDevice/src/supla/supla_lib_config.h index 4e8071bb..d6df3384 100644 --- a/lib/SuplaDevice/src/supla/supla_lib_config.h +++ b/lib/SuplaDevice/src/supla/supla_lib_config.h @@ -7,6 +7,6 @@ #ifndef supla_lib_config_h_ #define supla_lib_config_h_ -#define SUPLA_COMM_DEBUG +//#define SUPLA_COMM_DEBUG #endif diff --git a/platformio.ini b/platformio.ini index 51e6c23e..2faedc17 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.0.9"' +build_flags = -D BUILD_VERSION='"GUI 1.1.4"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -42,7 +42,8 @@ build_flags = -D BUILD_VERSION='"GUI 1.0.9"' -D SUPLA_MAX6675 -D SUPLA_HC_SR04 -D SUPLA_IMPULSE_COUNTER - -D SUPLA_OLED + -D SUPLA_OLED + -D SUPLA_HLW8012 [env] framework = arduino @@ -59,7 +60,7 @@ lib_deps = datacute/DoubleResetDetector@^1.0.3 closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 - thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.1.0 + ;thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.1.0 xoseperez/HLW8012 @ ^1.1.1 extra_scripts = tools/copy_files.py diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 96e8018e..70fd892b 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -162,7 +162,8 @@ void setup() { #ifdef SUPLA_MAX6675 if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { - Supla::GUI::sensorMAX6675_K.push_back(new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0))); + Supla::GUI::sensorMAX6675_K.push_back( + new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0))); } #endif @@ -179,6 +180,12 @@ void setup() { #endif +#ifdef SUPLA_HLW8012 + if (ConfigESP->getGpio(FUNCTION_CF) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CF1) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SEL) != OFF_GPIO) { + Supla::GUI::addHLW8012(ConfigESP->getGpio(FUNCTION_CF), ConfigESP->getGpio(FUNCTION_CF1), ConfigESP->getGpio(FUNCTION_SEL)); + } +#endif + Supla::GUI::begin(); } diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index 537d5328..f81948fb 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -45,6 +45,7 @@ // ##### Other ##### #define SUPLA_HC_SR04 #define SUPLA_IMPULSE_COUNTER +#define SUPLA_HLW8012 #endif #endif // GUI-Generic_Config_h diff --git a/src/Markup.cpp b/src/Markup.cpp index b1cba4b8..afdc7cf2 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -94,6 +94,41 @@ void addNumberBox(String& html, const String& input_id, const String& name, uint html += F("'>"); } +void addNumberBox(String& html, const String& input_id, const String& name, const String& placeholder, bool required, const String& value) { + html += F(""); +} + +void addLinkBox(String& html, const String& name, const String& url) { + html += F(""); + html += F(""); + html += F(""); +} + void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { html += F("

"); + addFormHeaderEnd(page); + + addForm(page, F("post"), PATH_SAVE_HLW8012_CALIBRATE); + addFormHeader(page, "Ustawienia kalibracji"); + addNumberBox(page, INPUT_CALIB_POWER, "Moc żarówki [W]", "25", true); + addNumberBox(page, INPUT_CALIB_VOLTAGE, "Napięcie [V]", "230", true); + addFormHeaderEnd(page); + + addButtonSubmit(page, "Kalibracja"); + addFormEnd(page); + + addButton(page, S_RETURN, PATH_OTHER); + return page; +} #endif \ No newline at end of file diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index f9cbdd5e..8113cffa 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -127,6 +127,22 @@ class SuplaWebPageSensor { #if defined(SUPLA_HC_SR04) || defined(SUPLA_IMPULSE_COUNTER) String supla_webpage_other(int save); #endif + +#if defined(SUPLA_HLW8012) +#define INPUT_CF "cf" +#define INPUT_CF1 "cf1" +#define INPUT_SEL "sel" + +#define PATH_HLW8012_CALIBRATE "calibrate" +#define PATH_SAVE_HLW8012_CALIBRATE "savecalibrate" + +#define INPUT_CALIB_POWER "power" +#define INPUT_CALIB_VOLTAGE "voltage" + + void handleHLW8012Calibrate(); + void handleHLW8012CalibrateSave(); + String suplaWebpageHLW8012Calibrate(uint8_t save); +#endif }; extern SuplaWebPageSensor* WebPageSensor; diff --git a/src/SuplaWebPageUpload.cpp b/src/SuplaWebPageUpload.cpp index 4b5d24b1..c364e500 100644 --- a/src/SuplaWebPageUpload.cpp +++ b/src/SuplaWebPageUpload.cpp @@ -38,7 +38,7 @@ void handleUpload(int save) { content += getURL(PATH_TOOLS); content += F("'>"); + content += F("

"); WebServer->sendContent(content); // WebServer->httpServer.send(200, PSTR("text/html"), FPSTR(uploadIndex)); @@ -65,6 +65,7 @@ void handleFileUpload() { dataFile.close(); // WebServer->httpServer.sendHeader("Location", "/upload"); // WebServer->httpServer.send(303); + ConfigManager->load(); handleUpload(1); } else { From ef7964f13a281f01435427ba9a056fa289931f6d Mon Sep 17 00:00:00 2001 From: krycha88 Date: Mon, 28 Dec 2020 21:22:03 +0100 Subject: [PATCH 095/233] #define supla_lib_config_h_ --- lib/SuplaDevice/src/supla/supla_lib_config.h | 2 +- src/GUI-Generic_Config.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/SuplaDevice/src/supla/supla_lib_config.h b/lib/SuplaDevice/src/supla/supla_lib_config.h index d6df3384..4e8071bb 100644 --- a/lib/SuplaDevice/src/supla/supla_lib_config.h +++ b/lib/SuplaDevice/src/supla/supla_lib_config.h @@ -7,6 +7,6 @@ #ifndef supla_lib_config_h_ #define supla_lib_config_h_ -//#define SUPLA_COMM_DEBUG +#define SUPLA_COMM_DEBUG #endif diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index f81948fb..70aa3273 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -1,6 +1,8 @@ #ifndef GUI_Generic_Config_h #define GUI_Generic_Config_h +#define supla_lib_config_h_ // silences unnecessary debug messages "should be disabled by default" + //#define USE_CUSTOM // User configuration From c80c44c8a57ad0a76dbb967a01141d0cb47dad21 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Mon, 28 Dec 2020 21:22:37 +0100 Subject: [PATCH 096/233] =?UTF-8?q?zmiana=20czasu=20zapisu=20dla=20licznik?= =?UTF-8?q?=C3=B3w=20energi=20na=2010min?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformio.ini | 2 +- src/SuplaDeviceGUI.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 2faedc17..1e6fd4bc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.4"' +build_flags = -D BUILD_VERSION='"GUI 1.1.5"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index d16717b8..6e2d5471 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -19,7 +19,7 @@ #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) || defined(SUPLA_IMPULSE_COUNTER) || defined(SUPLA_HLW8012) #define TIME_SAVE_PERIOD_SEK 30 // the time is given in seconds -#define TIME_SAVE_PERIOD_IMPULSE_COUNTER_SEK 300 // 5min +#define TIME_SAVE_PERIOD_IMPULSE_COUNTER_SEK 600 // 10min #define STORAGE_OFFSET 0 #include Supla::Eeprom eeprom(STORAGE_OFFSET); From 3529abad0b9dfe42ca3e9c4c98af8ff591941e5f Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 29 Dec 2020 06:47:41 +0100 Subject: [PATCH 097/233] =?UTF-8?q?poprawa=20wybierania=20BOARD=5FGOSUND?= =?UTF-8?q?=5FSP111=20z=20szablonu=20p=C5=82ytek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformio.ini | 2 +- src/SuplaTemplateBoard.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 1e6fd4bc..36bfbc41 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.5"' +build_flags = -D BUILD_VERSION='"GUI 1.1.6"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index fe887f6e..474322b8 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -42,6 +42,7 @@ void addHLW8012(int8_t pinCF, int8_t pinCF1, int8_t pinSEL) { ConfigESP->setGpio(pinCF, FUNCTION_CF); ConfigESP->setGpio(pinCF1, FUNCTION_CF1); ConfigESP->setGpio(pinSEL, FUNCTION_SEL); + Supla::GUI::addHLW8012(ConfigESP->getGpio(FUNCTION_CF), ConfigESP->getGpio(FUNCTION_CF1), ConfigESP->getGpio(FUNCTION_SEL)); } void chooseTemplateBoard(uint8_t board) { From a895adb44a1ee39370f39229c48925ecf5436bd4 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 12 Jan 2021 06:48:15 +0100 Subject: [PATCH 098/233] =?UTF-8?q?poprawa=20wy=C5=9Bwietlania=20ikonek=20?= =?UTF-8?q?na=20OLED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/SuplaOled.cpp | 62 ++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index 70b9b328..5f792999 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -27,7 +27,7 @@ String getPressureString(double pressure) { return F("error"); } else { - return String(floor(pressure), 0); + return String(pressure, 0); } } @@ -103,10 +103,8 @@ void displayRelayState(OLEDDisplay* display) { } x += 15; } - if (!GEOMETRY_64_48) { - display->setColor(WHITE); - display->drawHorizontalLine(0, 14, display->getWidth()); - } + display->setColor(WHITE); + display->drawHorizontalLine(0, 14, display->getWidth()); } void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state) { @@ -150,27 +148,29 @@ void displayBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, in } void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name) { + uint8_t temp_width, temp_height; + int drawHeightIcon = display->getHeight() / 2 - 10; int drawStringIcon = display->getHeight() / 2 - 6; - int temp_width = TEMP_WIDTH; - int temp_height = TEMP_HEIGHT; + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (GEOMETRY_64_48) { + if (display->getWidth() <= 64 || display->getHeight() <= 48) { temp_width = 0; temp_height = 0; } else { - temp_width = temp_width + 10; - } + display->drawXbm(x + 0, y + drawHeightIcon, TEMP_WIDTH, TEMP_HEIGHT, temp_bits); + display->setFont(ArialMT_Plain_10); + if (name != NULL) { + display->drawString(x + TEMP_WIDTH + 20, y + display->getHeight() / 2 - 15, name); + } - display->setColor(WHITE); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawXbm(x + 0, y + drawHeightIcon, temp_width, temp_height, temp_bits); - display->setFont(ArialMT_Plain_10); - if (name != NULL) { - display->drawString(x + temp_width, y + display->getHeight() / 2 - 15, name); + temp_width = TEMP_WIDTH + 10; + temp_height = TEMP_HEIGHT; } + display->setFont(ArialMT_Plain_24); display->drawString(x + temp_width, y + drawStringIcon, getTempString(temp)); display->setFont(ArialMT_Plain_16); @@ -178,23 +178,24 @@ void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int } void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { + uint8_t humidity_width, humidity_height; + int drawHeightIcon = display->getHeight() / 2 - 10; int drawStringIcon = display->getHeight() / 2 - 6; - int humidity_width = HUMIDITY_WIDTH; - int humidity_height = HUMIDITY_HEIGHT; + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (GEOMETRY_64_48) { + if (display->getWidth() <= 64 || display->getHeight() <= 48) { humidity_width = 0; humidity_height = 0; } else { - humidity_width = humidity_width + 10; + display->drawXbm(x + 0, y + drawHeightIcon, HUMIDITY_WIDTH, HUMIDITY_HEIGHT, humidity_bits); + humidity_width = HUMIDITY_WIDTH + 20; + humidity_height = HUMIDITY_HEIGHT; } - display->setColor(WHITE); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawXbm(x + 0, y + drawHeightIcon, humidity_width, humidity_height, humidity_bits); display->setFont(ArialMT_Plain_24); display->drawString(x + humidity_width, y + drawStringIcon, getHumidityString(humidity)); display->setFont(ArialMT_Plain_16); @@ -202,23 +203,24 @@ void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, } void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { + uint8_t pressure_width, pressure_height; + int drawHeightIcon = display->getHeight() / 2 - 10; int drawStringIcon = display->getHeight() / 2 - 6; - int pressure_width = PRESSURE_WIDTH; - int pressure_height = PRESSURE_HEIGHT; + display->setColor(WHITE); + display->setTextAlignment(TEXT_ALIGN_LEFT); - if (GEOMETRY_64_48) { + if (display->getWidth() <= 64 || display->getHeight() <= 48) { pressure_width = 0; pressure_height = 0; } else { - pressure_width = pressure_width + 15; + display->drawXbm(x + 0, y + drawHeightIcon, PRESSURE_WIDTH, PRESSURE_HEIGHT, pressure_bits); + pressure_width = PRESSURE_WIDTH + 15; + pressure_height = PRESSURE_HEIGHT; } - display->setColor(WHITE); - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawXbm(x + 0, y + drawHeightIcon, pressure_width, pressure_height, pressure_bits); display->setFont(ArialMT_Plain_24); display->drawString(x + pressure_width, y + drawStringIcon, getPressureString(pressure)); display->setFont(ArialMT_Plain_10); From da2d73acd3d0fba846aa94d9254a5cea43ca9b3a Mon Sep 17 00:00:00 2001 From: krycha88 Date: Tue, 12 Jan 2021 06:50:06 +0100 Subject: [PATCH 099/233] aktualizacja SuplaDevice --- .../SequenceButton/SequenceButton.ino | 92 +++++++ lib/SuplaDevice/extras/test/CMakeLists.txt | 39 +++ .../extras/test/UptimeTests/uptime_tests.cpp | 60 ++++ lib/SuplaDevice/src/CMakeLists.txt | 5 + .../src/supla/control/MCP23017/S_MCP23017.cpp | 205 ++++++++++++++ .../src/supla/control/MCP23017/S_MCP23017.h | 52 ++++ .../Ejemplo_Mcp23017_io.ino | 189 +++++++++++++ .../src/supla/control/MCP23017/readme.txt | 15 + .../supla/control/MCP23017/supla_mcp23017.h | 78 ++++++ .../src/supla/control/bistable_relay.cpp | 7 +- .../supla/control/bistable_roller_shutter.cpp | 11 +- lib/SuplaDevice/src/supla/control/button.cpp | 107 ++----- lib/SuplaDevice/src/supla/control/button.h | 40 +-- lib/SuplaDevice/src/supla/control/relay.cpp | 50 ++-- .../src/supla/control/roller_shutter.cpp | 20 +- .../src/supla/control/sequence_button.cpp | 137 +++++++++ .../src/supla/control/sequence_button.h | 58 ++++ .../src/supla/control/simple_button.cpp | 108 ++++++++ .../src/supla/control/simple_button.h | 71 +++++ .../src/supla/control/virtual_relay.h | 4 + lib/SuplaDevice/src/supla/events.h | 5 +- lib/SuplaDevice/src/supla/io.cpp | 25 ++ lib/SuplaDevice/src/supla/io.h | 5 + .../src/supla/network/esp32_wifi.h | 115 +------- lib/SuplaDevice/src/supla/network/esp_wifi.h | 90 ++++-- lib/SuplaDevice/src/supla/pv/afore.cpp | 15 +- lib/SuplaDevice/src/supla/pv/afore.h | 1 + lib/SuplaDevice/src/supla/pv/fronius.cpp | 15 +- lib/SuplaDevice/src/supla/pv/fronius.h | 1 + lib/SuplaDevice/src/supla/sensor/binary.cpp | 45 +++ lib/SuplaDevice/src/supla/sensor/binary.h | 31 +-- .../src/supla/sensor/electricity_meter.cpp | 260 ++++++++++++++++++ .../src/supla/sensor/electricity_meter.h | 203 ++------------ .../src/supla/sensor/impulse_counter.cpp | 7 +- lib/SuplaDevice/src/supla/storage/storage.cpp | 14 + lib/SuplaDevice/src/supla/storage/storage.h | 2 + lib/SuplaDevice/src/supla/timer.cpp | 23 +- 37 files changed, 1682 insertions(+), 523 deletions(-) create mode 100644 lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino create mode 100644 lib/SuplaDevice/extras/test/CMakeLists.txt create mode 100644 lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp create mode 100644 lib/SuplaDevice/src/CMakeLists.txt create mode 100644 lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.cpp create mode 100644 lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.h create mode 100644 lib/SuplaDevice/src/supla/control/MCP23017/examples/Ejemplo_Mcp23017_io/Ejemplo_Mcp23017_io.ino create mode 100644 lib/SuplaDevice/src/supla/control/MCP23017/readme.txt create mode 100644 lib/SuplaDevice/src/supla/control/MCP23017/supla_mcp23017.h create mode 100644 lib/SuplaDevice/src/supla/control/sequence_button.cpp create mode 100644 lib/SuplaDevice/src/supla/control/sequence_button.h create mode 100644 lib/SuplaDevice/src/supla/control/simple_button.cpp create mode 100644 lib/SuplaDevice/src/supla/control/simple_button.h create mode 100644 lib/SuplaDevice/src/supla/sensor/binary.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/electricity_meter.cpp diff --git a/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino b/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino new file mode 100644 index 00000000..8647974a --- /dev/null +++ b/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino @@ -0,0 +1,92 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include +#include + +// Choose proper network interface for your card: +// Arduino Mega with EthernetShield W5100: +//#include +// Ethernet MAC address +//uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +//Supla::EthernetShield ethernet(mac); +// +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +// +// ESP8266 based board: +#include +Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// +// ESP32 based board: +// #include +// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); + + +void setup() { + + Serial.begin(115200); + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + auto secretRelay = new Supla::Control::Relay(30, false); // Low level trigger relay on pin 30 + auto alarmRelay = new Supla::Control::Relay(31, false); // Low level trigger relay on pin 31 + auto seqButton = new Supla::Control::SequenceButton(D9, true, true); // Button on pin 28 with internal pullUp + // and LOW is considered as "pressed" state + + // Sequence of lenghts [ms] of button being presset, released, pressed, released, etc. + // Aplication will print on Serial recorded sequence, so use it to record your rhythm and put it here + uint16_t sequence[30] = {90, 590, 90, 140, 90, 290, 90, 230, 90, 140, 90}; + + seqButton->setSequence(sequence); + seqButton->setMargin(0.5); // we allow +- 50% deviation of state length compared to matching sequence + + // Button will trigger secretRelay when correct rhythm will be detected or alarmRelay otherwise + seqButton->addAction(Supla::TURN_ON, secretRelay, Supla::ON_SEQUENCE_MATCH); + seqButton->addAction(Supla::TURN_ON, alarmRelay, Supla::ON_SEQUENCE_DOESNT_MATCH); + + /* + * SuplaDevice Initialization. + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); +} diff --git a/lib/SuplaDevice/extras/test/CMakeLists.txt b/lib/SuplaDevice/extras/test/CMakeLists.txt new file mode 100644 index 00000000..12e94f97 --- /dev/null +++ b/lib/SuplaDevice/extras/test/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.11) + +project(supladevice) + +enable_testing() + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +include_directories(../../src) + +add_subdirectory(../../src/ build) + +include(FetchContent) + +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG release-1.8.0 + ) + +FetchContent_GetProperties(googletest) +if(NOT googletest_POPULATED) + FetchContent_Populate(googletest) + add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) +endif() + +file(GLOB SRCS UptimeTests/*.cpp) + +add_executable(supladevicetests ${SRCS}) + +target_link_libraries(supladevicetests + gtest + gtest_main + supladevicelib + ) + +add_test(NAME supladevicetests + COMMAND supladevicetests) diff --git a/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp b/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp new file mode 100644 index 00000000..0280d0e1 --- /dev/null +++ b/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp @@ -0,0 +1,60 @@ +#include +#include + +TEST(UptimeTests, LastResetCauseSetAndGet) { + Supla::Uptime uptime; + EXPECT_EQ(uptime.getUptime(), 0); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + // setter should not work unless first resetConnectionUptime is called + uptime.setConnectionLostCause(SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST); + EXPECT_EQ(uptime.getUptime(), 0); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + uptime.resetConnectionUptime(); + uptime.setConnectionLostCause(SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST); + EXPECT_EQ(uptime.getUptime(), 0); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_WIFI_CONNECTION_LOST); +} + +TEST(UptimeTests, IterateShouldIncreaseUptimeCounters) { + Supla::Uptime uptime; + unsigned long millis = 0; + + EXPECT_EQ(uptime.getUptime(), 0); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + millis += 999; + uptime.iterate(millis); + EXPECT_EQ(uptime.getUptime(), 0); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + millis += 1500; + uptime.iterate(millis); + EXPECT_EQ(uptime.getUptime(), 2); + EXPECT_EQ(uptime.getConnectionUptime(), 2); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + millis += 20000; + uptime.iterate(millis); + EXPECT_EQ(uptime.getUptime(), 22); + EXPECT_EQ(uptime.getConnectionUptime(), 22); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + uptime.resetConnectionUptime(); + EXPECT_EQ(uptime.getUptime(), 22); + EXPECT_EQ(uptime.getConnectionUptime(), 0); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); + + millis += 2500; + uptime.iterate(millis); + EXPECT_EQ(uptime.getUptime(), 24); + EXPECT_EQ(uptime.getConnectionUptime(), 2); + EXPECT_EQ(uptime.getLastResetCause(), SUPLA_LASTCONNECTIONRESETCAUSE_UNKNOWN); +} + diff --git a/lib/SuplaDevice/src/CMakeLists.txt b/lib/SuplaDevice/src/CMakeLists.txt new file mode 100644 index 00000000..2643ea2b --- /dev/null +++ b/lib/SuplaDevice/src/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SRCS + supla/uptime.cpp +) + +add_library(supladevicelib SHARED ${SRCS}) diff --git a/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.cpp b/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.cpp new file mode 100644 index 00000000..af9a21c3 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.cpp @@ -0,0 +1,205 @@ +#include +#include +#include "S_MCP23017.h" + +static const uint8_t MCP23017_BASEADDRESS = 0x20; + +static const uint8_t MCP23017_IODIRA = 0x00; +static const uint8_t MCP23017_IODIRB = 0x01; +static const uint8_t MCP23017_IPOLA = 0x02; +static const uint8_t MCP23017_IPOLB = 0x03; +static const uint8_t MCP23017_GPINTENA = 0x04; +static const uint8_t MCP23017_GPINTENB = 0x05; +static const uint8_t MCP23017_DEFVALA = 0x06; +static const uint8_t MCP23017_DEFVALB = 0x07; +static const uint8_t MCP23017_INTCONA = 0x08; +static const uint8_t MCP23017_INTCONB = 0x09; +static const uint8_t MCP23017_IOCONA = 0x0A; +static const uint8_t MCP23017_IOCONB = 0x0B; +static const uint8_t MCP23017_GPPUA = 0x0C; +static const uint8_t MCP23017_GPPUB = 0x0D; +static const uint8_t MCP23017_INTFA = 0x0E; +static const uint8_t MCP23017_INTFB = 0x0F; +static const uint8_t MCP23017_INTCAPA = 0x10; +static const uint8_t MCP23017_INTCAPB = 0x11; +static const uint8_t MCP23017_GPIOA = 0x12; +static const uint8_t MCP23017_GPIOB = 0x13; +static const uint8_t MCP23017_OLATA = 0x14; +static const uint8_t MCP23017_OLATB = 0x15; + +#ifdef ESP8266 +void MCP23017::init(uint8_t sda, uint8_t scl, bool fast) { + Wire.begin(sda, scl); + if (fast) + Wire.setClock(400000); +} +#else +void MCP23017::init(bool fast) { + Wire.begin(); + if (fast) + Wire.setClock(400000); +} +#endif + +bool MCP23017::begin(uint8_t address) { + _address = MCP23017_BASEADDRESS | (address & 0x07); + return (writeReg16(MCP23017_IOCONA, 0x4242) && writeReg16(MCP23017_IODIRA, 0xFFFF)); // INT MIRROR & INT POL HIGH, ALL INPUTS +} + +void MCP23017::pinMode(uint8_t pin, uint8_t mode) { + if (pin < 16) { + if (mode == OUTPUT) { + updateReg(pin < 8 ? MCP23017_IODIRA : MCP23017_IODIRB, ~(uint8_t)(1 << (pin % 8)), 0x00); + } else if ((mode == INPUT) || (mode == INPUT_PULLUP)) { + updateReg(pin < 8 ? MCP23017_IODIRA : MCP23017_IODIRB, 0xFF, 1 << (pin % 8)); + if (mode == INPUT_PULLUP) { + updateReg(pin < 8 ? MCP23017_GPPUA : MCP23017_GPPUB, 0xFF, 1 << (pin % 8)); + updateReg(pin < 8 ? MCP23017_IPOLA : MCP23017_IPOLB, 0xFF, 1 << (pin % 8)); + } else { + updateReg(pin < 8 ? MCP23017_GPPUA : MCP23017_GPPUB, ~(uint8_t)(1 << (pin % 8)), 0x00); + updateReg(pin < 8 ? MCP23017_IPOLA : MCP23017_IPOLB, ~(uint8_t)(1 << (pin % 8)), 0x00); + } + } + } +} + +void MCP23017::setPullup(uint8_t pin, bool pullup, bool inverse) { + if (pin < 16) { + if (pullup) + updateReg(pin < 8 ? MCP23017_GPPUA : MCP23017_GPPUB, 0xFF, 1 << (pin % 8)); + else + updateReg(pin < 8 ? MCP23017_GPPUA : MCP23017_GPPUB, ~(uint8_t)(1 << (pin % 8)), 0x00); + if (inverse) + updateReg(pin < 8 ? MCP23017_IPOLA : MCP23017_IPOLB, 0xFF, 1 << (pin % 8)); + else + updateReg(pin < 8 ? MCP23017_IPOLA : MCP23017_IPOLB, ~(uint8_t)(1 << (pin % 8)), 0x00); + } +} + +bool MCP23017::digitalRead(uint8_t pin) { + unsigned long now = millis(); + if (now > get_ba){ + ba = readReg16(MCP23017_GPIOA); // --------- reads "readReg16" only once every 20 ms ---- + get_ba = now + 20; + } + if (pin < 16) { + return ((ba >> (pin % 16)) & 0x01); + } +} + +void MCP23017::digitalWrite(uint8_t pin, bool value) { + if (pin < 16) { + if (value) + updateReg(pin < 8 ? MCP23017_GPIOA : MCP23017_GPIOB, 0xFF, 1 << (pin % 8)); + else + updateReg(pin < 8 ? MCP23017_GPIOA : MCP23017_GPIOB, ~(uint8_t)(1 << (pin % 8)), 0x00); + } +} + +uint16_t MCP23017::digitalReads() { + return readReg16(MCP23017_GPIOA); +} + +void MCP23017::digitalWrites(uint16_t values) { + writeReg16(MCP23017_GPIOA, values); +} + +void MCP23017::attachInterrupt(uint8_t pin, callback_t callback) { + _callback = callback; + ::pinMode(pin, INPUT); + ::attachInterrupt(digitalPinToInterrupt(pin), std::bind(&MCP23017::_interrupt, this), RISING); +} + +void MCP23017::detachInterrupt(uint8_t pin) { + ::detachInterrupt(digitalPinToInterrupt(pin)); + _callback = NULL; +} + +void MCP23017::setupInterrupt(uint8_t pin, bool enable) { + if (pin < 16) { + if (enable) + updateReg(pin < 8 ? MCP23017_GPINTENA : MCP23017_GPINTENB, 0xFF, 1 << (pin % 8)); + else + updateReg(pin < 8 ? MCP23017_GPINTENA : MCP23017_GPINTENB, ~(uint8_t)(1 << (pin % 8)), 0x00); + } +} + +void MCP23017::setupInterrupts(uint16_t pins, bool enable) { + if (enable) + updateReg16(MCP23017_GPINTENA, 0xFFFF, pins); + else + updateReg16(MCP23017_GPINTENA, ~pins, 0x00); +} + +bool MCP23017::writeReg(uint8_t reg, uint8_t value) { + Wire.beginTransmission(_address); + Wire.write(reg); + Wire.write(value); + return (Wire.endTransmission() == 0); +} + +bool MCP23017::writeReg16(uint8_t reg, uint16_t value) { + Wire.beginTransmission(_address); + Wire.write(reg); + Wire.write(value & 0xFF); + Wire.write(value >> 8); + return (Wire.endTransmission() == 0); +} + +uint8_t MCP23017::readReg(uint8_t reg) { + Wire.beginTransmission(_address); + Wire.write(reg); + if (Wire.endTransmission() != 0) + return 0; // Error! + Wire.requestFrom(_address, (uint8_t)1); + return Wire.read(); +} + +uint16_t MCP23017::readReg16(uint8_t reg) { + Wire.beginTransmission(_address); + Wire.write(reg); + if (Wire.endTransmission() != 0) + return 0; // Error! + Wire.requestFrom(_address, (uint8_t)2); + uint8_t a = Wire.read(); + return ((Wire.read() << 8) | a); +} + +bool MCP23017::updateReg(uint8_t reg, uint8_t andMask, uint8_t orMask) { + Wire.beginTransmission(_address); + Wire.write(reg); + if (Wire.endTransmission() != 0) + return false; // Error! + Wire.requestFrom(_address, (uint8_t)1); + uint8_t a = (Wire.read() & andMask) | orMask; + Wire.beginTransmission(_address); + Wire.write(reg); + Wire.write(a); + return (Wire.endTransmission() == 0); +} + +bool MCP23017::updateReg16(uint8_t reg, uint16_t andMask, uint16_t orMask) { + Wire.beginTransmission(_address); + Wire.write(reg); + if (Wire.endTransmission() != 0) + return false; // Error! + Wire.requestFrom(_address, (uint8_t)2); + uint16_t ab = Wire.read(); + ab |= (Wire.read() << 8); + ab &= andMask; + ab |= orMask; + Wire.beginTransmission(_address); + Wire.write(reg); + Wire.write(ab & 0xFF); + Wire.write(ab >> 8); + return (Wire.endTransmission() == 0); +} + +void IRAM_ATTR MCP23017::_interrupt() { + uint16_t pins, values; + + pins = readReg16(MCP23017_INTFA); + values = readReg16(MCP23017_INTCAPA); + if (_callback) + _callback(pins, values); +} diff --git a/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.h b/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.h new file mode 100644 index 00000000..ef08791e --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/MCP23017/S_MCP23017.h @@ -0,0 +1,52 @@ +#ifndef _S_MCP23017_H +#define _S_MCP23017_H + +#include +#include + +class MCP23017 { +public: + typedef std::function callback_t; + + MCP23017() : _callback(NULL) {} + +#ifdef ESP8266 + static void init(uint8_t sda, uint8_t scl, bool fast = true); + static void init(bool fast = true) { + init(SDA, SCL, fast); + } +#else + static void init(bool fast = true); +#endif + + bool begin(uint8_t address = 0); + + void pinMode(uint8_t pin, uint8_t mode); + void setPullup(uint8_t pin, bool pullup, bool inverse = false); + bool digitalRead(uint8_t pin); + void digitalWrite(uint8_t pin, bool value); + uint16_t digitalReads(); + void digitalWrites(uint16_t values); + + void attachInterrupt(uint8_t pin, callback_t callback); + void detachInterrupt(uint8_t pin); + void setupInterrupt(uint8_t pin, bool enable = true); + void setupInterrupts(uint16_t pins, bool enable = true); + +protected: + bool writeReg(uint8_t reg, uint8_t value); + bool writeReg16(uint8_t reg, uint16_t value); + uint8_t readReg(uint8_t reg); + uint16_t readReg16(uint8_t reg); + bool updateReg(uint8_t reg, uint8_t andMask, uint8_t orMask); + bool updateReg16(uint8_t reg, uint16_t andMask, uint16_t orMask); + + void _interrupt(); + + uint8_t _address; + uint16_t ba = 0; + unsigned long get_ba = 0; + callback_t _callback; +}; + +#endif diff --git a/lib/SuplaDevice/src/supla/control/MCP23017/examples/Ejemplo_Mcp23017_io/Ejemplo_Mcp23017_io.ino b/lib/SuplaDevice/src/supla/control/MCP23017/examples/Ejemplo_Mcp23017_io/Ejemplo_Mcp23017_io.ino new file mode 100644 index 00000000..dd4ee2be --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/MCP23017/examples/Ejemplo_Mcp23017_io/Ejemplo_Mcp23017_io.ino @@ -0,0 +1,189 @@ +#define supla_lib_config_h_ // silences unnecessary debug messages "should be disabled by default" +#include +#include +#include +#include + +// +// ESP8266 based board: +#include +Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +// + +void setup() { + + Serial.begin(115200); + + mcp1.init(4, 5); // init(uint8_t sda, uint8_t scl, bool fast) = Wire.begin + + if (! mcp1.begin(0))Serial.println("MCP23017 1 not found!"); // begin(uint8_t address) "Pin 100 - 115" + if (! mcp2.begin(1))Serial.println("MCP23017 2 not found!"); // begin(uint8_t address) "Pin 116 - 131" + if (! mcp3.begin(2))Serial.println("MCP23017 3 not found!"); // begin(uint8_t address) "Pin 132 - 147" + if (! mcp4.begin(3))Serial.println("MCP23017 4 not found!"); // begin(uint8_t address) "Pin 148 - 163" + + // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ + + auto relay_1 = new Supla::Control::Relay(100, true); + auto relay_2 = new Supla::Control::Relay(101, true); + auto relay_3 = new Supla::Control::Relay(102, true); + auto relay_4 = new Supla::Control::Relay(103, true); + auto relay_5 = new Supla::Control::Relay(104, true); + auto relay_6 = new Supla::Control::Relay(105, true); + auto relay_7 = new Supla::Control::Relay(106, true); + auto relay_8 = new Supla::Control::Relay(107, true); + auto relay_9 = new Supla::Control::Relay(108, true); + auto relay_10 = new Supla::Control::Relay(109, true); + auto relay_11 = new Supla::Control::Relay(110, true); + auto relay_12 = new Supla::Control::Relay(111, true); + auto relay_13 = new Supla::Control::Relay(112, true); + auto relay_14 = new Supla::Control::Relay(113, true); + auto relay_15 = new Supla::Control::Relay(114, true); + auto relay_16 = new Supla::Control::Relay(115, true); + + auto relay_17 = new Supla::Control::Relay(148, true); + auto relay_18 = new Supla::Control::Relay(149, true); + auto relay_19 = new Supla::Control::Relay(150, true); + auto relay_20 = new Supla::Control::Relay(151, true); + auto relay_21 = new Supla::Control::Relay(152, true); + auto relay_22 = new Supla::Control::Relay(153, true); + auto relay_23 = new Supla::Control::Relay(154, true); + auto relay_24 = new Supla::Control::Relay(155, true); + auto relay_25 = new Supla::Control::Relay(156, true); + auto relay_26 = new Supla::Control::Relay(157, true); + auto relay_27 = new Supla::Control::Relay(158, true); + auto relay_28 = new Supla::Control::Relay(159, true); + auto relay_29 = new Supla::Control::Relay(160, true); + auto relay_30 = new Supla::Control::Relay(161, true); + auto relay_31 = new Supla::Control::Relay(162, true); + auto relay_32 = new Supla::Control::Relay(163, true); + + auto button_1 = new Supla::Control::Button(116, true, true); + auto button_2 = new Supla::Control::Button(117, true, true); + auto button_3 = new Supla::Control::Button(118, true, true); + auto button_4 = new Supla::Control::Button(119, true, true); + auto button_5 = new Supla::Control::Button(120, true, true); + auto button_6 = new Supla::Control::Button(121, true, true); + auto button_7 = new Supla::Control::Button(122, true, true); + auto button_8 = new Supla::Control::Button(123, true, true); + auto button_9 = new Supla::Control::Button(124, true, true); + auto button_10 = new Supla::Control::Button(125, true, true); + auto button_11 = new Supla::Control::Button(126, true, true); + auto button_12 = new Supla::Control::Button(127, true, true); + auto button_13 = new Supla::Control::Button(128, true, true); + auto button_14 = new Supla::Control::Button(129, true, true); + auto button_15 = new Supla::Control::Button(130, true, true); + auto button_16 = new Supla::Control::Button(131, true, true); + + auto button_17 = new Supla::Control::Button(132, true, true); + auto button_18 = new Supla::Control::Button(133, true, true); + auto button_19 = new Supla::Control::Button(134, true, true); + auto button_20 = new Supla::Control::Button(135, true, true); + auto button_21 = new Supla::Control::Button(136, true, true); + auto button_22 = new Supla::Control::Button(137, true, true); + auto button_23 = new Supla::Control::Button(138, true, true); + auto button_24 = new Supla::Control::Button(139, true, true); + auto button_25 = new Supla::Control::Button(140, true, true); + auto button_26 = new Supla::Control::Button(141, true, true); + auto button_27 = new Supla::Control::Button(142, true, true); + auto button_28 = new Supla::Control::Button(143, true, true); + auto button_29 = new Supla::Control::Button(144, true, true); + auto button_30 = new Supla::Control::Button(145, true, true); + auto button_31 = new Supla::Control::Button(146, true, true); + auto button_32 = new Supla::Control::Button(147, true, true); + + button_1->addAction(Supla::TOGGLE, relay_1, Supla::ON_PRESS); + button_1->setSwNoiseFilterDelay(50); + button_2->addAction(Supla::TOGGLE, relay_2, Supla::ON_PRESS); + button_2->setSwNoiseFilterDelay(50); + button_3->addAction(Supla::TOGGLE, relay_3, Supla::ON_PRESS); + button_3->setSwNoiseFilterDelay(50); + button_4->addAction(Supla::TOGGLE, relay_4, Supla::ON_PRESS); + button_4->setSwNoiseFilterDelay(50); + button_5->addAction(Supla::TOGGLE, relay_5, Supla::ON_PRESS); + button_5->setSwNoiseFilterDelay(50); + button_6->addAction(Supla::TOGGLE, relay_6, Supla::ON_PRESS); + button_6->setSwNoiseFilterDelay(50); + button_7->addAction(Supla::TOGGLE, relay_7, Supla::ON_PRESS); + button_7->setSwNoiseFilterDelay(50); + button_8->addAction(Supla::TOGGLE, relay_8, Supla::ON_PRESS); + button_8->setSwNoiseFilterDelay(50); + button_9->addAction(Supla::TOGGLE, relay_9, Supla::ON_PRESS); + button_9->setSwNoiseFilterDelay(50); + button_10->addAction(Supla::TOGGLE, relay_10, Supla::ON_PRESS); + button_10->setSwNoiseFilterDelay(50); + button_11->addAction(Supla::TOGGLE, relay_11, Supla::ON_PRESS); + button_11->setSwNoiseFilterDelay(50); + button_12->addAction(Supla::TOGGLE, relay_12, Supla::ON_PRESS); + button_12->setSwNoiseFilterDelay(50); + button_13->addAction(Supla::TOGGLE, relay_13, Supla::ON_PRESS); + button_13->setSwNoiseFilterDelay(50); + button_14->addAction(Supla::TOGGLE, relay_14, Supla::ON_PRESS); + button_14->setSwNoiseFilterDelay(50); + button_15->addAction(Supla::TOGGLE, relay_15, Supla::ON_PRESS); + button_15->setSwNoiseFilterDelay(50); + button_16->addAction(Supla::TOGGLE, relay_16, Supla::ON_PRESS); + button_16->setSwNoiseFilterDelay(50); + + button_17->addAction(Supla::TOGGLE, relay_17, Supla::ON_PRESS); + button_17->setSwNoiseFilterDelay(50); + button_18->addAction(Supla::TOGGLE, relay_18, Supla::ON_PRESS); + button_18->setSwNoiseFilterDelay(50); + button_19->addAction(Supla::TOGGLE, relay_19, Supla::ON_PRESS); + button_19->setSwNoiseFilterDelay(50); + button_20->addAction(Supla::TOGGLE, relay_20, Supla::ON_PRESS); + button_20->setSwNoiseFilterDelay(50); + button_21->addAction(Supla::TOGGLE, relay_21, Supla::ON_PRESS); + button_21->setSwNoiseFilterDelay(50); + button_22->addAction(Supla::TOGGLE, relay_22, Supla::ON_PRESS); + button_22->setSwNoiseFilterDelay(50); + button_23->addAction(Supla::TOGGLE, relay_23, Supla::ON_PRESS); + button_23->setSwNoiseFilterDelay(50); + button_24->addAction(Supla::TOGGLE, relay_24, Supla::ON_PRESS); + button_24->setSwNoiseFilterDelay(50); + button_25->addAction(Supla::TOGGLE, relay_25, Supla::ON_PRESS); + button_25->setSwNoiseFilterDelay(50); + button_26->addAction(Supla::TOGGLE, relay_26, Supla::ON_PRESS); + button_26->setSwNoiseFilterDelay(50); + button_27->addAction(Supla::TOGGLE, relay_27, Supla::ON_PRESS); + button_27->setSwNoiseFilterDelay(50); + button_28->addAction(Supla::TOGGLE, relay_28, Supla::ON_PRESS); + button_28->setSwNoiseFilterDelay(50); + button_29->addAction(Supla::TOGGLE, relay_29, Supla::ON_PRESS); + button_29->setSwNoiseFilterDelay(50); + button_30->addAction(Supla::TOGGLE, relay_30, Supla::ON_PRESS); + button_30->setSwNoiseFilterDelay(50); + button_31->addAction(Supla::TOGGLE, relay_31, Supla::ON_PRESS); + button_31->setSwNoiseFilterDelay(50); + button_32->addAction(Supla::TOGGLE, relay_32, Supla::ON_PRESS); + button_32->setSwNoiseFilterDelay(50); + + /* + * SuplaDevice Initialization. + * Server address is available at https://cloud.supla.org + * If you do not have an account, you can create it at https://cloud.supla.org/account/create + * SUPLA and SUPLA CLOUD are free of charge + * + */ + + SuplaDevice.begin(GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key + +} + +void loop() { + SuplaDevice.iterate(); + delay(25); +} diff --git a/lib/SuplaDevice/src/supla/control/MCP23017/readme.txt b/lib/SuplaDevice/src/supla/control/MCP23017/readme.txt new file mode 100644 index 00000000..9cb235ee --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/MCP23017/readme.txt @@ -0,0 +1,15 @@ +Support for up to 4 x "MCP23017" additional 64 Gpios. + + +BASIC USE: + +INSTANTIATE +#include + +SETUP + mcp1.init(uint8_t sda, uint8_t scl); // init(uint8_t sda, uint8_t scl) = Wire.begin(); + + mcp1.begin(uint8_t mcp23017_address); // Pin 100 - 115 + mcp2.begin(uint8_t mcp23017_address); // Pin 116 - 131 + mcp3.begin(uint8_t mcp23017_address); // Pin 132 - 147 + mcp4.begin(uint8_t mcp23017_address); // Pin 148 - 163 \ No newline at end of file diff --git a/lib/SuplaDevice/src/supla/control/MCP23017/supla_mcp23017.h b/lib/SuplaDevice/src/supla/control/MCP23017/supla_mcp23017.h new file mode 100644 index 00000000..8dfa9598 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/MCP23017/supla_mcp23017.h @@ -0,0 +1,78 @@ + +#ifndef S_mcp_23017_h +#define S_mcp_23017_h + +#include +#include +#include + + + MCP23017 mcp1; + MCP23017 mcp2; + MCP23017 mcp3; + MCP23017 mcp4; + + +class CustomControl : public Supla::Io { + public: + void customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val) { + if (pin < 100) { + return ::digitalWrite(pin,val); + } + if ((pin > 99) && (pin < 116)){ + mcp1.digitalWrite(pin - 100, val); + return; + } + if ((pin > 115) && (pin < 132)){ + mcp2.digitalWrite(pin - 116, val); + } + if ((pin > 131) && (pin < 148)){ + mcp3.digitalWrite(pin - 132, val); + return; + } + if ((pin > 147) && (pin < 164)){ + mcp4.digitalWrite(pin - 148, val); + return; + } + } + int customDigitalRead(int channelNumber, uint8_t pin) { + if (pin < 100){ + return ::digitalRead(pin); + } + if ((pin > 99)&& (pin < 116)){ + return mcp1.digitalRead(pin - 100); + } + if ((pin > 115)&& (pin < 132)){ + return mcp2.digitalRead(pin - 116); + } + if ((pin > 131)&& (pin < 148)){ + return mcp3.digitalRead(pin - 132); + } + if ((pin > 147)&& (pin < 164)){ + return mcp4.digitalRead(pin - 148); + } + } + void customPinMode(int channelNumber, uint8_t pin, uint8_t mode) { + (void)(channelNumber); + if (pin < 100){ + return ::pinMode(pin, mode); + } + if ((pin > 99)&& (pin < 116)){ + mcp1.pinMode(pin - 100, mode); + } + if ((pin > 115)&& (pin < 132)){ + mcp2.pinMode(pin - 116, mode); + } + if ((pin > 131)&& (pin < 148)){ + mcp3.pinMode(pin - 132, mode); + } + if ((pin > 147)&& (pin < 164)){ + mcp4.pinMode(pin - 148, mode); + } + } + +}CustomControl; + + +#endif + diff --git a/lib/SuplaDevice/src/supla/control/bistable_relay.cpp b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp index 3fabf297..d03f2aa4 100644 --- a/lib/SuplaDevice/src/supla/control/bistable_relay.cpp +++ b/lib/SuplaDevice/src/supla/control/bistable_relay.cpp @@ -40,7 +40,7 @@ BistableRelay::BistableRelay(int pin, void BistableRelay::onInit() { if (statusPin >= 0) { - pinMode(statusPin, statusPullUp ? INPUT_PULLUP : INPUT); + Supla::Io::pinMode(channel.getChannelNumber(), statusPin, statusPullUp ? INPUT_PULLUP : INPUT); channel.setNewValue(isOn()); } else { channel.setNewValue(false); @@ -56,7 +56,7 @@ void BistableRelay::onInit() { turnOff(); } - pinMode(pin, OUTPUT); + Supla::Io::pinMode(channel.getChannelNumber(), pin, OUTPUT); } void BistableRelay::iterateAlways() { @@ -138,6 +138,9 @@ void BistableRelay::internalToggle() { busy = true; disarmTimeMs = millis(); Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOnValue()); + + // Schedule save in 5 s after state change + Supla::Storage::ScheduleSave(5000); } void BistableRelay::toggle(_supla_int_t duration) { diff --git a/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp index 5fc8fe1f..9401633f 100644 --- a/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp +++ b/lib/SuplaDevice/src/supla/control/bistable_roller_shutter.cpp @@ -15,6 +15,7 @@ */ #include "bistable_roller_shutter.h" +#include namespace Supla { namespace Control { @@ -33,28 +34,30 @@ void BistableRollerShutter::stopMovement() { } currentDirection = STOP_DIR; doNothingTime = millis(); + // Schedule save in 5 s after stop movement of roller shutter + Supla::Storage::ScheduleSave(5000); } void BistableRollerShutter::relayDownOn() { activeBiRelay = true; toggleTime = millis(); - digitalWrite(pinDown, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? HIGH : LOW); } void BistableRollerShutter::relayUpOn() { activeBiRelay = true; toggleTime = millis(); - digitalWrite(pinUp, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? HIGH : LOW); } void BistableRollerShutter::relayDownOff() { activeBiRelay = false; - digitalWrite(pinDown, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); } void BistableRollerShutter::relayUpOff() { activeBiRelay = false; - digitalWrite(pinUp, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); } void BistableRollerShutter::onTimer() { diff --git a/lib/SuplaDevice/src/supla/control/button.cpp b/lib/SuplaDevice/src/supla/control/button.cpp index 950b9f3e..8fcb294e 100644 --- a/lib/SuplaDevice/src/supla/control/button.cpp +++ b/lib/SuplaDevice/src/supla/control/button.cpp @@ -16,60 +16,13 @@ #include "button.h" -enum StateResults {PRESSED, RELEASED, TO_PRESSED, TO_RELEASED}; - -Supla::Control::ButtonState::ButtonState(int pin, bool pullUp, bool invertLogic) - : debounceTimeMs(0), - filterTimeMs(0), - debounceDelayMs(50), - swNoiseFilterDelayMs(20), - pin(pin), - newStatusCandidate(LOW), - prevState(LOW), - pullUp(pullUp), - invertLogic(invertLogic) { -} - -int Supla::Control::ButtonState::update() { - if (millis() - debounceTimeMs > debounceDelayMs) { - int currentState = digitalRead(pin); - if (currentState != prevState) { - // If status is changed, then make sure that it will be kept at - // least swNoiseFilterDelayMs ms to avoid noise - if (currentState != newStatusCandidate) { - newStatusCandidate = currentState; - filterTimeMs = millis(); - } else if (millis() - filterTimeMs > swNoiseFilterDelayMs) { - // If new status is kept at least swNoiseFilterDelayMs ms, then apply - // change of status - debounceTimeMs = millis(); - prevState = currentState; - if (currentState == valueOnPress()) { - return TO_PRESSED; - } else { - return TO_RELEASED; - } - } - } else { - // If current status is the same as prevState, then reset - // new status candidate - newStatusCandidate = prevState; - } - } - if (prevState == valueOnPress()) { - return PRESSED; - } else { - return RELEASED; - } -} Supla::Control::Button::Button(int pin, bool pullUp, bool invertLogic) - : state(pin, pullUp, invertLogic), + : SimpleButton(pin, pullUp, invertLogic), holdTimeMs(0), multiclickTimeMs(0), - clickCounter(0), lastStateChangeMs(0), - enableExtDetection(false), + clickCounter(0), holdSend(false), bistable(false) { } @@ -88,13 +41,24 @@ void Supla::Control::Button::onTimer() { runAction(ON_CHANGE); } + if (stateChanged) { + lastStateChangeMs = millis(); + if (stateResult == TO_PRESSED || bistable) { + clickCounter++; + } + } + if (!stateChanged) { if (!bistable && stateResult == PRESSED) { if (clickCounter <= 1 && holdTimeMs > 0 && timeDelta > holdTimeMs && !holdSend) { runAction(ON_HOLD); holdSend = true; } - } else if (bistable || stateResult == RELEASED) { + } else if (clickCounter > 0 && (bistable || stateResult == RELEASED)) { + if (multiclickTimeMs == 0) { + holdSend = false; + clickCounter = 0; + } if (multiclickTimeMs > 0 && timeDelta > multiclickTimeMs) { if (!holdSend) { switch (clickCounter) { @@ -129,49 +93,15 @@ void Supla::Control::Button::onTimer() { runAction(ON_CLICK_10); break; } - } + if (clickCounter >= 10) { + runAction(ON_CRAZY_CLICKER); + } + } holdSend = false; clickCounter = 0; } } } - - if (stateChanged) { - lastStateChangeMs = millis(); - if (stateResult == TO_PRESSED || bistable) { - clickCounter++; - } - - } -} - -void Supla::Control::Button::onInit() { - state.init(); -} - -void Supla::Control::ButtonState::init() { - pinMode(pin, pullUp ? INPUT_PULLUP : INPUT); - prevState = digitalRead(pin); - newStatusCandidate = prevState; -} - -int Supla::Control::ButtonState::valueOnPress() { - return invertLogic ? LOW : HIGH; -} - -void Supla::Control::Button::setSwNoiseFilterDelay(unsigned int newDelayMs) { - state.setSwNoiseFilterDelay(newDelayMs); -} -void Supla::Control::ButtonState::setSwNoiseFilterDelay(unsigned int newDelayMs) { - swNoiseFilterDelayMs = newDelayMs; -} - -void Supla::Control::Button::setDebounceDelay(unsigned int newDelayMs) { - state.setDebounceDelay(newDelayMs); -} - -void Supla::Control::ButtonState::setDebounceDelay(unsigned int newDelayMs) { - debounceDelayMs = newDelayMs; } void Supla::Control::Button::setHoldTime(unsigned int timeMs) { @@ -188,3 +118,4 @@ void Supla::Control::Button::setMulticlickTime(unsigned int timeMs, bool bistabl holdTimeMs = 0; } } + diff --git a/lib/SuplaDevice/src/supla/control/button.h b/lib/SuplaDevice/src/supla/control/button.h index 7cf146a3..a48328d5 100644 --- a/lib/SuplaDevice/src/supla/control/button.h +++ b/lib/SuplaDevice/src/supla/control/button.h @@ -18,58 +18,24 @@ #define _button_h #include - -#include "../element.h" -#include "../events.h" -#include "../local_action.h" +#include "simple_button.h" namespace Supla { namespace Control { -class ButtonState { - public: - ButtonState(int pin, bool pullUp, bool invertLogic); - int update(); - void init(); - - void setSwNoiseFilterDelay(unsigned int newDelayMs); - void setDebounceDelay(unsigned int newDelayMs); - void setHoldTime(unsigned int timeMs); - void setMulticlickTime(unsigned int timeMs); - - protected: - int valueOnPress(); - - unsigned long debounceTimeMs; - unsigned long filterTimeMs; - unsigned int debounceDelayMs; - unsigned int swNoiseFilterDelayMs; - int pin; - int8_t newStatusCandidate; - int8_t prevState; - bool pullUp; - bool invertLogic; -}; - -class Button : public Element, - public LocalAction { +class Button : public SimpleButton { public: Button(int pin, bool pullUp = false, bool invertLogic = false); void onTimer(); - void onInit(); - void setSwNoiseFilterDelay(unsigned int newDelayMs); - void setDebounceDelay(unsigned int newDelayMs); void setHoldTime(unsigned int timeMs); void setMulticlickTime(unsigned int timeMs, bool bistableButton = false); protected: - ButtonState state; unsigned int holdTimeMs; unsigned int multiclickTimeMs; - uint8_t clickCounter; unsigned long lastStateChangeMs; - bool enableExtDetection; + uint8_t clickCounter; bool holdSend; bool bistable; }; diff --git a/lib/SuplaDevice/src/supla/control/relay.cpp b/lib/SuplaDevice/src/supla/control/relay.cpp index 081d1fe7..d47fd9a2 100644 --- a/lib/SuplaDevice/src/supla/control/relay.cpp +++ b/lib/SuplaDevice/src/supla/control/relay.cpp @@ -57,7 +57,7 @@ void Relay::onInit() { turnOff(); } - pinMode(pin, OUTPUT); // pin mode is set after setting pin value in order to + Supla::Io::pinMode(channel.getChannelNumber(), pin, OUTPUT); // pin mode is set after setting pin value in order to // avoid problems with LOW trigger relays } @@ -94,6 +94,9 @@ void Relay::turnOn(_supla_int_t duration) { Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOnValue()); channel.setNewValue(true); + + // Schedule save in 5 s after state change + Supla::Storage::ScheduleSave(5000); } void Relay::turnOff(_supla_int_t duration) { @@ -102,6 +105,9 @@ void Relay::turnOff(_supla_int_t duration) { Supla::Io::digitalWrite(channel.getChannelNumber(), pin, pinOffValue()); channel.setNewValue(false); + + // Schedule save in 5 s after state change + Supla::Storage::ScheduleSave(5000); } bool Relay::isOn() { @@ -140,39 +146,39 @@ Channel *Relay::getChannel() { } void Relay::onSaveState() { - if (keepTurnOnDurationMs) { - Supla::Storage::WriteState((unsigned char *)&storedTurnOnDurationMs, - sizeof(storedTurnOnDurationMs)); - } + Supla::Storage::WriteState((unsigned char *)&storedTurnOnDurationMs, + sizeof(storedTurnOnDurationMs)); + bool enabled = false; if (stateOnInit < 0) { - bool enabled = isOn(); - Supla::Storage::WriteState((unsigned char *)&enabled, sizeof(enabled)); - } + enabled = isOn(); + } + Supla::Storage::WriteState((unsigned char *)&enabled, sizeof(enabled)); } void Relay::onLoadState() { + Supla::Storage::ReadState((unsigned char *)&storedTurnOnDurationMs, + sizeof(storedTurnOnDurationMs)); if (keepTurnOnDurationMs) { - Supla::Storage::ReadState((unsigned char *)&storedTurnOnDurationMs, - sizeof(storedTurnOnDurationMs)); Serial.print(F("Relay[")); Serial.print(channel.getChannelNumber()); Serial.print(F("]: restored durationMs: ")); Serial.println(storedTurnOnDurationMs); + } else { + storedTurnOnDurationMs = 0; } + bool enabled = false; + Supla::Storage::ReadState((unsigned char *)&enabled, sizeof(enabled)); if (stateOnInit < 0) { - bool enabled = false; - if (Supla::Storage::ReadState((unsigned char *)&enabled, sizeof(enabled))) { - Serial.print(F("Relay[")); - Serial.print(channel.getChannelNumber()); - Serial.print(F("]: restored relay state: ")); - if (enabled) { - Serial.println(F("ON")); - stateOnInit = STATE_ON_INIT_RESTORED_ON; - } else { - Serial.println(F("OFF")); - stateOnInit = STATE_ON_INIT_RESTORED_OFF; - } + Serial.print(F("Relay[")); + Serial.print(channel.getChannelNumber()); + Serial.print(F("]: restored relay state: ")); + if (enabled) { + Serial.println(F("ON")); + stateOnInit = STATE_ON_INIT_RESTORED_ON; + } else { + Serial.println(F("OFF")); + stateOnInit = STATE_ON_INIT_RESTORED_OFF; } } } diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp index 630fb105..12e91b88 100644 --- a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp @@ -15,7 +15,7 @@ */ #include "roller_shutter.h" -#include "supla/storage/storage.h" +#include namespace Supla { namespace Control { @@ -53,10 +53,10 @@ RollerShutter::RollerShutter(int pinUp, int pinDown, bool highIsOn) } void RollerShutter::onInit() { - pinMode(pinUp, OUTPUT); - pinMode(pinDown, OUTPUT); - digitalWrite(pinUp, highIsOn ? LOW : HIGH); - digitalWrite(pinDown, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); + Supla::Io::pinMode(channel.getChannelNumber(), pinUp, OUTPUT); + Supla::Io::pinMode(channel.getChannelNumber(), pinDown, OUTPUT); } /* @@ -268,22 +268,24 @@ void RollerShutter::stopMovement() { switchOffRelays(); currentDirection = STOP_DIR; doNothingTime = millis(); + // Schedule save in 5 s after stop movement of roller shutter + Supla::Storage::ScheduleSave(5000); } void RollerShutter::relayDownOn() { - digitalWrite(pinDown, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? HIGH : LOW); } void RollerShutter::relayUpOn() { - digitalWrite(pinUp, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? HIGH : LOW); } void RollerShutter::relayDownOff() { - digitalWrite(pinDown, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); } void RollerShutter::relayUpOff() { - digitalWrite(pinUp, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); } void RollerShutter::startClosing() { diff --git a/lib/SuplaDevice/src/supla/control/sequence_button.cpp b/lib/SuplaDevice/src/supla/control/sequence_button.cpp new file mode 100644 index 00000000..f86dd4c8 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/sequence_button.cpp @@ -0,0 +1,137 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "sequence_button.h" +#include + +Supla::Control::SequenceButton::SequenceButton(int pin, bool pullUp, bool invertLogic) + : SimpleButton(pin, pullUp, invertLogic), + lastStateChangeMs(0), + longestSequenceTimeDeltaWithMargin(800), + clickCounter(0), + sequenceDetectecion(true), + currentSequence(), + matchSequence(), + margin(0.3) { +} + +void Supla::Control::SequenceButton::onTimer() { + unsigned int timeDelta = millis() - lastStateChangeMs; + bool stateChanged = false; + int stateResult = state.update(); + if (stateResult == TO_PRESSED) { + stateChanged = true; + runAction(ON_PRESS); + runAction(ON_CHANGE); + } else if (stateResult == TO_RELEASED) { + stateChanged = true; + runAction(ON_RELEASE); + runAction(ON_CHANGE); + } + + if (stateChanged) { + lastStateChangeMs = millis(); + if (clickCounter > 0 && clickCounter < SEQUENCE_MAX_SIZE + 1) { + currentSequence.data[clickCounter - 1] = timeDelta; + } + if (clickCounter == 0) { + memset(currentSequence.data, 0, sizeof(uint16_t [SEQUENCE_MAX_SIZE])); + } + clickCounter++; + } + + if (!stateChanged) { + if (clickCounter > 0 && stateResult == RELEASED) { + if (timeDelta > longestSequenceTimeDeltaWithMargin) { + Serial.print(F("Recorded sequence: ")); + if (clickCounter > 31) { + clickCounter = 31; + } + for (int i = 0; i < clickCounter - 1; i++) { + Serial.print(currentSequence.data[i]); + Serial.print(F(", ")); + } + Serial.println(); + + int matchSequenceSize = 0; + for (; matchSequenceSize < 30; matchSequenceSize++) { + if (matchSequence.data[matchSequenceSize] == 0) { + break; + } + } + if (matchSequenceSize != clickCounter - 1) { + Serial.println(F("Sequence size doesn't match")); + runAction(ON_SEQUENCE_DOESNT_MATCH); + } else { + bool match = true; + for (int i = 0; i < clickCounter - 1; i++) { + unsigned int marginValue = calculateMargin(matchSequence.data[i]); + if (!(matchSequence.data[i] - marginValue <= currentSequence.data[i] && matchSequence.data[i] + marginValue >= currentSequence.data[i])) { + match = false; + break; + } + } + if (match) { + Serial.println(F("Sequence match")); + runAction(ON_SEQUENCE_MATCH); + } else { + Serial.println(F("Sequence doesn't match")); + runAction(ON_SEQUENCE_DOESNT_MATCH); + } + + } + clickCounter = 0; + } + } + } + +} + +unsigned int Supla::Control::SequenceButton::calculateMargin(unsigned int value) { + unsigned int result = margin*value; + if (result < 20) { + result = 20; + } + return result; +} + +void Supla::Control::SequenceButton::setMargin(float newMargin) { + margin = newMargin; + if (margin < 0) { + margin = 0; + } else if (margin > 1) { + margin = 1; + } +} + +void Supla::Control::SequenceButton::setSequence(uint16_t *sequence) { + uint16_t maxValue = 0; + for (int i = 0; i < SEQUENCE_MAX_SIZE; i++) { + matchSequence.data[i] = sequence[i]; + if (sequence[i] > maxValue) { + maxValue = sequence[i]; + } + } + maxValue *= 1.5; + if (maxValue < 500) { + maxValue = 500; + } + longestSequenceTimeDeltaWithMargin = maxValue; +} + +void Supla::Control::SequenceButton::getLastRecordedSequence(uint16_t *sequence) { + memcpy(sequence, currentSequence.data, sizeof(uint16_t [SEQUENCE_MAX_SIZE])); +} diff --git a/lib/SuplaDevice/src/supla/control/sequence_button.h b/lib/SuplaDevice/src/supla/control/sequence_button.h new file mode 100644 index 00000000..d4e12b12 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/sequence_button.h @@ -0,0 +1,58 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _sequence_button_h +#define _sequence_button_h + +#include "button.h" + +namespace Supla { +namespace Control { + +#define SEQUENCE_MAX_SIZE 30 + +struct ClickSequence { + uint16_t data[SEQUENCE_MAX_SIZE]; +}; + +class SequenceButton : public SimpleButton { + public: + SequenceButton(int pin, bool pullUp = false, bool invertLogic = false); + + void onTimer(); + + void setSequence(uint16_t *sequence); + void setMargin(float); + void getLastRecordedSequence(uint16_t *sequence); + + protected: + unsigned long lastStateChangeMs; + uint16_t longestSequenceTimeDeltaWithMargin; + uint8_t clickCounter; + bool sequenceDetectecion; + + ClickSequence currentSequence; + ClickSequence matchSequence; + + float margin; + unsigned int calculateMargin(unsigned int); + +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/simple_button.cpp b/lib/SuplaDevice/src/supla/control/simple_button.cpp new file mode 100644 index 00000000..7714f675 --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/simple_button.cpp @@ -0,0 +1,108 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "button.h" +#include "../io.h" + +Supla::Control::ButtonState::ButtonState(int pin, bool pullUp, bool invertLogic) + : debounceTimeMs(0), + filterTimeMs(0), + debounceDelayMs(50), + swNoiseFilterDelayMs(20), + pin(pin), + newStatusCandidate(LOW), + prevState(LOW), + pullUp(pullUp), + invertLogic(invertLogic) { +} + +int Supla::Control::ButtonState::update() { + if (millis() - debounceTimeMs > debounceDelayMs) { + int currentState = Supla::Io::digitalRead(pin); + if (currentState != prevState) { + // If status is changed, then make sure that it will be kept at + // least swNoiseFilterDelayMs ms to avoid noise + if (currentState != newStatusCandidate) { + newStatusCandidate = currentState; + filterTimeMs = millis(); + } else if (millis() - filterTimeMs > swNoiseFilterDelayMs) { + // If new status is kept at least swNoiseFilterDelayMs ms, then apply + // change of status + debounceTimeMs = millis(); + prevState = currentState; + if (currentState == valueOnPress()) { + return TO_PRESSED; + } else { + return TO_RELEASED; + } + } + } else { + // If current status is the same as prevState, then reset + // new status candidate + newStatusCandidate = prevState; + } + } + if (prevState == valueOnPress()) { + return PRESSED; + } else { + return RELEASED; + } +} + +Supla::Control::SimpleButton::SimpleButton(int pin, bool pullUp, bool invertLogic) + : state(pin, pullUp, invertLogic) { +} + +void Supla::Control::SimpleButton::onTimer() { + int stateResult = state.update(); + if (stateResult == TO_PRESSED) { + runAction(ON_PRESS); + runAction(ON_CHANGE); + } else if (stateResult == TO_RELEASED) { + runAction(ON_RELEASE); + runAction(ON_CHANGE); + } +} + +void Supla::Control::SimpleButton::onInit() { + state.init(); +} + +void Supla::Control::ButtonState::init() { + Supla::Io::pinMode(pin, pullUp ? INPUT_PULLUP : INPUT); + prevState = Supla::Io::digitalRead(pin); + newStatusCandidate = prevState; +} + +int Supla::Control::ButtonState::valueOnPress() { + return invertLogic ? LOW : HIGH; +} + +void Supla::Control::SimpleButton::setSwNoiseFilterDelay(unsigned int newDelayMs) { + state.setSwNoiseFilterDelay(newDelayMs); +} +void Supla::Control::ButtonState::setSwNoiseFilterDelay(unsigned int newDelayMs) { + swNoiseFilterDelayMs = newDelayMs; +} + +void Supla::Control::SimpleButton::setDebounceDelay(unsigned int newDelayMs) { + state.setDebounceDelay(newDelayMs); +} + +void Supla::Control::ButtonState::setDebounceDelay(unsigned int newDelayMs) { + debounceDelayMs = newDelayMs; +} + diff --git a/lib/SuplaDevice/src/supla/control/simple_button.h b/lib/SuplaDevice/src/supla/control/simple_button.h new file mode 100644 index 00000000..8b66fb7e --- /dev/null +++ b/lib/SuplaDevice/src/supla/control/simple_button.h @@ -0,0 +1,71 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _simple_button_h +#define _simple_button_h + +#include + +#include "../element.h" +#include "../events.h" +#include "../local_action.h" + +namespace Supla { +namespace Control { + +enum StateResults {PRESSED, RELEASED, TO_PRESSED, TO_RELEASED}; + +class ButtonState { + public: + ButtonState(int pin, bool pullUp, bool invertLogic); + int update(); + void init(); + + void setSwNoiseFilterDelay(unsigned int newDelayMs); + void setDebounceDelay(unsigned int newDelayMs); + + protected: + int valueOnPress(); + + unsigned long debounceTimeMs; + unsigned long filterTimeMs; + unsigned int debounceDelayMs; + unsigned int swNoiseFilterDelayMs; + int pin; + int8_t newStatusCandidate; + int8_t prevState; + bool pullUp; + bool invertLogic; +}; + +class SimpleButton : public Element, + public LocalAction { + public: + SimpleButton(int pin, bool pullUp = false, bool invertLogic = false); + + void onTimer(); + void onInit(); + void setSwNoiseFilterDelay(unsigned int newDelayMs); + void setDebounceDelay(unsigned int newDelayMs); + + protected: + ButtonState state; +}; + +}; // namespace Control +}; // namespace Supla + +#endif diff --git a/lib/SuplaDevice/src/supla/control/virtual_relay.h b/lib/SuplaDevice/src/supla/control/virtual_relay.h index 1a54c51f..15ed52a4 100644 --- a/lib/SuplaDevice/src/supla/control/virtual_relay.h +++ b/lib/SuplaDevice/src/supla/control/virtual_relay.h @@ -46,6 +46,8 @@ class VirtualRelay : public Relay { state = true; channel.setNewValue(state); + // Schedule save in 5 s after state change + Supla::Storage::ScheduleSave(5000); } virtual void turnOff(_supla_int_t duration = 0) { @@ -54,6 +56,8 @@ class VirtualRelay : public Relay { state = false; channel.setNewValue(state); + // Schedule save in 5 s after state change + Supla::Storage::ScheduleSave(5000); } virtual bool isOn() { diff --git a/lib/SuplaDevice/src/supla/events.h b/lib/SuplaDevice/src/supla/events.h index c72c122f..cadbb616 100644 --- a/lib/SuplaDevice/src/supla/events.h +++ b/lib/SuplaDevice/src/supla/events.h @@ -34,7 +34,10 @@ enum Event { ON_CLICK_7, ON_CLICK_8, ON_CLICK_9, - ON_CLICK_10 + ON_CLICK_10, + ON_CRAZY_CLICKER, // triggered on >= 10 clicks + ON_SEQUENCE_MATCH, // triggered by SequenceButton + ON_SEQUENCE_DOESNT_MATCH // triggered by SequenceButton }; }; diff --git a/lib/SuplaDevice/src/supla/io.cpp b/lib/SuplaDevice/src/supla/io.cpp index 59cccc81..84c8684d 100644 --- a/lib/SuplaDevice/src/supla/io.cpp +++ b/lib/SuplaDevice/src/supla/io.cpp @@ -19,6 +19,26 @@ #include namespace Supla { +void Io::pinMode(uint8_t pin, uint8_t mode) { + return pinMode(-1, pin, mode); +} + +int Io::digitalRead(uint8_t pin) { + return digitalRead(-1, pin); +} + +void Io::digitalWrite(uint8_t pin, uint8_t val) { + digitalWrite(-1, pin, val); +} + +void Io::pinMode(int channelNumber, uint8_t pin, uint8_t mode) { + if (ioInstance) { + ioInstance->customPinMode(channelNumber, pin, mode); + } else { + ::pinMode(pin, mode); + } +} + int Io::digitalRead(int channelNumber, uint8_t pin) { if (ioInstance) { return ioInstance->customDigitalRead(channelNumber, pin); @@ -56,4 +76,9 @@ void Io::customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val) { ::digitalWrite(pin, val); } +void Io::customPinMode(int channelNumber, uint8_t pin, uint8_t mode) { + (void)(channelNumber); + ::pinMode(pin, mode); +} + }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/io.h b/lib/SuplaDevice/src/supla/io.h index 461f8b6a..722d6685 100644 --- a/lib/SuplaDevice/src/supla/io.h +++ b/lib/SuplaDevice/src/supla/io.h @@ -30,12 +30,17 @@ namespace Supla { // changed. class Io { public: + static void pinMode(uint8_t pin, uint8_t mode); + static int digitalRead(uint8_t pin); + static void digitalWrite(uint8_t pin, uint8_t val); + static void pinMode(int channelNumber, uint8_t pin, uint8_t mode); static int digitalRead(int channelNumber, uint8_t pin); static void digitalWrite(int channelNumber, uint8_t pin, uint8_t val); static Io *ioInstance; Io(); + virtual void customPinMode(int channelNumber, uint8_t pin, uint8_t mode); virtual int customDigitalRead(int channelNumber, uint8_t pin); virtual void customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val); }; diff --git a/lib/SuplaDevice/src/supla/network/esp32_wifi.h b/lib/SuplaDevice/src/supla/network/esp32_wifi.h index fa3915d1..e45ee3cc 100644 --- a/lib/SuplaDevice/src/supla/network/esp32_wifi.h +++ b/lib/SuplaDevice/src/supla/network/esp32_wifi.h @@ -14,120 +14,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#ifndef esp_wifi_h__ -#define esp_wifi_h__ +// DEPRECATED: please use esp_wifi.h instead -#include -#include +#ifndef esp32_wifi_h__ +#define esp32_wifi_h__ -#include "../supla_lib_config.h" -#include "network.h" - -#define MAX_SSID_SIZE 32 -#define MAX_WIFI_PASSWORD_SIZE 64 - -// TODO: change logs to supla_log +#include "esp_wifi.h" namespace Supla { -class ESP32Wifi : public Supla::Network { - public: - ESP32Wifi(const char *wifiSsid, - const char *wifiPassword, - IPAddress *ip = NULL) - : Network(ip) { - strcpy(ssid, wifiSsid); - strcpy(password, wifiPassword); - } - - int read(void *buf, int count) { - _supla_int_t size = client.available(); - - if (size > 0) { - if (size > count) size = count; - long readSize = client.read((uint8_t *)buf, size); -#ifdef SUPLA_COMM_DEBUG - Serial.print(F("Received: [")); - for (int i = 0; i < readSize; i++) { - Serial.print(static_cast(buf)[i], HEX); - Serial.print(F(" ")); - } - Serial.println(F("]")); -#endif - - return readSize; - } - return -1; - } - - int write(void *buf, int count) { -#ifdef SUPLA_COMM_DEBUG - Serial.print(F("Sending: [")); - for (int i = 0; i < count; i++) { - Serial.print(static_cast(buf)[i], HEX); - Serial.print(F(" ")); - } - Serial.println(F("]")); -#endif - long sendSize = client.write((const uint8_t *)buf, count); - return sendSize; - } - - int connect(const char *server, int port = -1) { - int connectionPort = (port == -1 ? 2015 : port); - supla_log( - LOG_DEBUG, "Establishing connection with: %s (port: %d)", server, connectionPort); - return client.connect(server, connectionPort); - } - - bool connected() { - return client.connected(); - } - - bool isReady() { - return WiFi.status() == WL_CONNECTED; - } - - void disconnect() { - client.stop(); - } - - // TODO: add handling of custom local ip - void setup() { - WiFiEventId_t event_gotIP = WiFi.onEvent( - [](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.print(F("local IP: ")); - Serial.println(WiFi.localIP()); - Serial.print(F("subnetMask: ")); - Serial.println(WiFi.subnetMask()); - Serial.print(F("gatewayIP: ")); - Serial.println(WiFi.gatewayIP()); - long rssi = WiFi.RSSI(); - Serial.print(F("Signal Strength (RSSI): ")); - Serial.print(rssi); - Serial.println(F(" dBm")); - }, - WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); - - WiFiEventId_t event_disconnected = WiFi.onEvent( - [](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.println(F("wifi Station disconnected")); - }, - WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); - - Serial.print(F("WIFI: establishing connection with SSID: \"")); - Serial.print(ssid); - Serial.println(F("\"")); - WiFi.begin(ssid, password); - yield(); - } - - protected: - WiFiClient client; - - char ssid[MAX_SSID_SIZE]; - char password[MAX_WIFI_PASSWORD_SIZE]; +typedef ESPWifi ESP32Wifi; }; -}; // namespace Supla - #endif diff --git a/lib/SuplaDevice/src/supla/network/esp_wifi.h b/lib/SuplaDevice/src/supla/network/esp_wifi.h index b819e23f..adbeac69 100644 --- a/lib/SuplaDevice/src/supla/network/esp_wifi.h +++ b/lib/SuplaDevice/src/supla/network/esp_wifi.h @@ -18,7 +18,13 @@ #define esp_wifi_h__ #include + +#ifdef ARDUINO_ARCH_ESP8266 #include +#else +#include +#endif + #include #include "../supla_lib_config.h" @@ -27,7 +33,9 @@ #define MAX_SSID_SIZE 32 #define MAX_WIFI_PASSWORD_SIZE 64 +#ifdef ARDUINO_ARCH_ESP8266 WiFiEventHandler gotIpEventHandler, disconnectedEventHandler; +#endif // TODO: change logs to supla_log @@ -42,6 +50,10 @@ class ESPWifi : public Supla::Network { password[0] = '\0'; setSsid(wifiSsid); setPassword(wifiPassword); +#ifdef ARDUINO_ARCH_ESP32 + enableSSL( + false); // current ESP32 WiFiClientSecure does not suport "setInsecure" +#endif } int read(void *buf, int count) { @@ -85,10 +97,18 @@ class ESPWifi : public Supla::Network { client = new WiFiClientSecure(); if (fingerprint.length() > 0) { message += " with certificate matching"; +#ifdef ARDUINO_ARCH_ESP8266 ((WiFiClientSecure *)client)->setFingerprint(fingerprint.c_str()); +#else + message += " - NOT SUPPORTED ON ESP32 implmentation"; +#endif } else { message += " without certificate matching"; +#ifdef ARDUINO_ARCH_ESP8266 ((WiFiClientSecure *)client)->setInsecure(); +#else + message += " - NOT SUPPORTED ON ESP32 implmentation"; +#endif } } else { message = "unsecured connection"; @@ -141,30 +161,52 @@ class ESPWifi : public Supla::Network { void setup() { if (!wifiConfigured) { wifiConfigured = true; - gotIpEventHandler = - WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &event) { - (void)(event); - Serial.print(F("local IP: ")); - Serial.println(WiFi.localIP()); - Serial.print(F("subnetMask: ")); - Serial.println(WiFi.subnetMask()); - Serial.print(F("gatewayIP: ")); - Serial.println(WiFi.gatewayIP()); - long rssi = WiFi.RSSI(); - Serial.print(F("Signal strength (RSSI): ")); - Serial.print(rssi); - Serial.println(F(" dBm")); - }); - disconnectedEventHandler = WiFi.onStationModeDisconnected( - [](const WiFiEventStationModeDisconnected &event) { - (void)(event); - Serial.println(F("WiFi station disconnected")); - }); - - Serial.print(F("WiFi: establishing connection with SSID: \"")); - Serial.print(ssid); - Serial.println(F("\"")); - WiFi.begin(ssid, password); +#ifdef ARDUINO_ARCH_ESP8266 + gotIpEventHandler = + WiFi.onStationModeGotIP([](const WiFiEventStationModeGotIP &event) { + (void)(event); + Serial.print(F("local IP: ")); + Serial.println(WiFi.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(WiFi.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(WiFi.gatewayIP()); + long rssi = WiFi.RSSI(); + Serial.print(F("Signal strength (RSSI): ")); + Serial.print(rssi); + Serial.println(F(" dBm")); + }); + disconnectedEventHandler = WiFi.onStationModeDisconnected( + [](const WiFiEventStationModeDisconnected &event) { + (void)(event); + Serial.println(F("WiFi station disconnected")); + }); +#else + WiFiEventId_t event_gotIP = WiFi.onEvent( + [](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.print(F("local IP: ")); + Serial.println(WiFi.localIP()); + Serial.print(F("subnetMask: ")); + Serial.println(WiFi.subnetMask()); + Serial.print(F("gatewayIP: ")); + Serial.println(WiFi.gatewayIP()); + long rssi = WiFi.RSSI(); + Serial.print(F("Signal Strength (RSSI): ")); + Serial.print(rssi); + Serial.println(F(" dBm")); + }, + WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); + + WiFiEventId_t event_disconnected = WiFi.onEvent( + [](WiFiEvent_t event, WiFiEventInfo_t info) { + Serial.println(F("wifi Station disconnected")); + }, + WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); +#endif + Serial.print(F("WiFi: establishing connection with SSID: \"")); + Serial.print(ssid); + Serial.println(F("\"")); + WiFi.begin(ssid, password); } else { Serial.println(F("WiFi: resetting WiFi connection")); if (client) { diff --git a/lib/SuplaDevice/src/supla/pv/afore.cpp b/lib/SuplaDevice/src/supla/pv/afore.cpp index 7b6f5484..06c0933f 100644 --- a/lib/SuplaDevice/src/supla/pv/afore.cpp +++ b/lib/SuplaDevice/src/supla/pv/afore.cpp @@ -30,7 +30,9 @@ Afore::Afore(IPAddress ip, int port, const char *loginAndPass) vFound(false), varFound(false), dataIsReady(false), - dataFetchInProgress(false) { + dataFetchInProgress(false), + connectionTimeoutMs(0) { + refreshRateSec = 15; int len = strlen(loginAndPass); if (len > LOGIN_AND_PASSOWORD_MAX_LENGTH) { len = LOGIN_AND_PASSOWORD_MAX_LENGTH; @@ -40,6 +42,13 @@ Afore::Afore(IPAddress ip, int port, const char *loginAndPass) void Afore::iterateAlways() { if (dataFetchInProgress) { + if (millis() - connectionTimeoutMs > 30000) { + Serial.println(F("AFORE: connection timeout. Remote host is not responding")); + pvClient.stop(); + dataFetchInProgress = false; + dataIsReady = false; + return; + } if (!pvClient.connected()) { Serial.println(F("AFORE fetch completed")); dataFetchInProgress = false; @@ -105,12 +114,13 @@ void Afore::iterateAlways() { bool Afore::iterateConnected(void *srpc) { if (!dataFetchInProgress) { - if (lastReadTime == 0 || millis() - lastReadTime > 15000) { + if (lastReadTime == 0 || millis() - lastReadTime > refreshRateSec*1000) { lastReadTime = millis(); Serial.println(F("AFORE connecting")); if (pvClient.connect(ip, port)) { retryCounter = 0; dataFetchInProgress = true; + connectionTimeoutMs = lastReadTime; Serial.println(F("Succesful connect")); pvClient.print("GET /status.html HTTP/1.1\nAuthorization: Basic "); @@ -137,3 +147,4 @@ bool Afore::iterateConnected(void *srpc) { void Afore::readValuesFromDevice() { } + diff --git a/lib/SuplaDevice/src/supla/pv/afore.h b/lib/SuplaDevice/src/supla/pv/afore.h index 85a978e6..bb46c26a 100644 --- a/lib/SuplaDevice/src/supla/pv/afore.h +++ b/lib/SuplaDevice/src/supla/pv/afore.h @@ -55,6 +55,7 @@ class Afore : public Supla::Sensor::OnePhaseElectricityMeter { bool varFound; bool dataIsReady; bool dataFetchInProgress; + unsigned long connectionTimeoutMs; }; }; // namespace PV }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/pv/fronius.cpp b/lib/SuplaDevice/src/supla/pv/fronius.cpp index 3e2200b4..871f80a3 100644 --- a/lib/SuplaDevice/src/supla/pv/fronius.cpp +++ b/lib/SuplaDevice/src/supla/pv/fronius.cpp @@ -36,11 +36,20 @@ Fronius::Fronius(IPAddress ip, int port, int deviceId) deviceId(deviceId), startCharFound(false), dataIsReady(false), - dataFetchInProgress(false) { + dataFetchInProgress(false), + connectionTimeoutMs(0) { + refreshRateSec = 15; } void Fronius::iterateAlways() { if (dataFetchInProgress) { + if (millis() - connectionTimeoutMs > 30000) { + Serial.println(F("Fronius: connection timeout. Remote host is not responding")); + pvClient.stop(); + dataFetchInProgress = false; + dataIsReady = false; + return; + } if (!pvClient.connected()) { Serial.println(F("Fronius fetch completed")); dataFetchInProgress = false; @@ -147,13 +156,14 @@ void Fronius::iterateAlways() { bool Fronius::iterateConnected(void *srpc) { if (!dataFetchInProgress) { - if (lastReadTime == 0 || millis() - lastReadTime > 15000) { + if (lastReadTime == 0 || millis() - lastReadTime > refreshRateSec*1000) { lastReadTime = millis(); Serial.print(F("Fronius connecting ")); Serial.println(deviceId); if (pvClient.connect(ip, port)) { retryCounter = 0; dataFetchInProgress = true; + connectionTimeoutMs = lastReadTime; Serial.println(F("Succesful connect")); char buf[100]; @@ -189,3 +199,4 @@ bool Fronius::iterateConnected(void *srpc) { void Fronius::readValuesFromDevice() { } + diff --git a/lib/SuplaDevice/src/supla/pv/fronius.h b/lib/SuplaDevice/src/supla/pv/fronius.h index 1f761732..f32d4699 100644 --- a/lib/SuplaDevice/src/supla/pv/fronius.h +++ b/lib/SuplaDevice/src/supla/pv/fronius.h @@ -56,6 +56,7 @@ class Fronius : public Supla::Sensor::OnePhaseElectricityMeter { bool startCharFound; bool dataIsReady; bool dataFetchInProgress; + unsigned long connectionTimeoutMs; }; }; // namespace PV }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/sensor/binary.cpp b/lib/SuplaDevice/src/supla/sensor/binary.cpp new file mode 100644 index 00000000..b305457d --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/binary.cpp @@ -0,0 +1,45 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "binary.h" +#include "../io.h" + +Supla::Sensor::Binary::Binary(int pin, bool pullUp = false) + : pin(pin), pullUp(pullUp), lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_SENSORNO); +} + +bool Supla::Sensor::Binary::getValue() { + return Supla::Io::digitalRead(channel.getChannelNumber(), pin) == LOW ? false + : true; +} + +void Supla::Sensor::Binary::iterateAlways() { + if (lastReadTime + 100 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } +} + +void Supla::Sensor::Binary::onInit() { + Supla::Io::pinMode( + channel.getChannelNumber(), pin, pullUp ? INPUT_PULLUP : INPUT); + channel.setNewValue(getValue()); +} + +Supla::Channel *Supla::Sensor::Binary::getChannel() { + return &channel; +} diff --git a/lib/SuplaDevice/src/supla/sensor/binary.h b/lib/SuplaDevice/src/supla/sensor/binary.h index 8bd7e52d..31db9fff 100644 --- a/lib/SuplaDevice/src/supla/sensor/binary.h +++ b/lib/SuplaDevice/src/supla/sensor/binary.h @@ -21,38 +21,19 @@ #include "../channel.h" #include "../element.h" -#include "../io.h" namespace Supla { namespace Sensor { class Binary : public Element { public: - Binary(int pin, bool pullUp = false) : pin(pin), pullUp(pullUp), lastReadTime(0) { - channel.setType(SUPLA_CHANNELTYPE_SENSORNO); - } - - bool getValue() { - return Supla::Io::digitalRead(channel.getChannelNumber(), pin) == LOW - ? false - : true; - } - - void iterateAlways() { - if (lastReadTime + 100 < millis()) { - lastReadTime = millis(); - channel.setNewValue(getValue()); - } - } - - void onInit() { - pinMode(pin, pullUp ? INPUT_PULLUP : INPUT); - channel.setNewValue(getValue()); - } + Binary(int pin, bool pullUp); + bool getValue(); + void iterateAlways(); + void onInit(); protected: - Channel *getChannel() { - return &channel; - } + Channel *getChannel(); + Channel channel; int pin; bool pullUp; diff --git a/lib/SuplaDevice/src/supla/sensor/electricity_meter.cpp b/lib/SuplaDevice/src/supla/sensor/electricity_meter.cpp new file mode 100644 index 00000000..13fbf36a --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/electricity_meter.cpp @@ -0,0 +1,260 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include "electricity_meter.h" + +Supla::Sensor::ElectricityMeter::ElectricityMeter() + : valueChanged(false), lastReadTime(0), refreshRateSec(5) { + extChannel.setType(SUPLA_CHANNELTYPE_ELECTRICITY_METER); + extChannel.setDefault(SUPLA_CHANNELFNC_ELECTRICITY_METER); + memset(&emValue, 0, sizeof(emValue)); + emValue.period = 5; + for (int i = 0; i < MAX_PHASES; i++) { + rawCurrent[i] = 0; + } + currentMeasurementAvailable = false; +} + +void Supla::Sensor::ElectricityMeter::updateChannelValues() { + if (!valueChanged) { + return; + } + valueChanged = false; + + emValue.m_count = 1; + + // Update current messurement precision based on last updates + if (currentMeasurementAvailable) { + bool over65A = false; + for (int i = 0; i < MAX_PHASES; i++) { + if (rawCurrent[i] > 65000) { + over65A = true; + } + } + + for (int i = 0; i < MAX_PHASES; i++) { + if (over65A) { + emValue.m[0].current[i] = rawCurrent[i] / 10; + } else { + emValue.m[0].current[i] = rawCurrent[i]; + } + } + + if (over65A) { + emValue.measured_values ^= (!EM_VAR_CURRENT); + emValue.measured_values |= EM_VAR_CURRENT_OVER_65A; + } else { + emValue.measured_values ^= (!EM_VAR_CURRENT_OVER_65A); + emValue.measured_values |= EM_VAR_CURRENT; + } + } + + // Prepare extended channel value + srpc_evtool_v2_emextended2extended(&emValue, extChannel.getExtValue()); + extChannel.setNewValue(emValue); +} + +// energy in 0.00001 kWh +void Supla::Sensor::ElectricityMeter::setFwdActEnergy( + int phase, unsigned _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_forward_active_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_forward_active_energy[phase] = energy; + emValue.measured_values |= EM_VAR_FORWARD_ACTIVE_ENERGY; + } +} + +// energy in 0.00001 kWh +void Supla::Sensor::ElectricityMeter::setRvrActEnergy( + int phase, unsigned _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_reverse_active_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_reverse_active_energy[phase] = energy; + emValue.measured_values |= EM_VAR_REVERSE_ACTIVE_ENERGY; + } +} + +// energy in 0.00001 kWh +void Supla::Sensor::ElectricityMeter::setFwdReactEnergy( + int phase, unsigned _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_forward_reactive_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_forward_reactive_energy[phase] = energy; + emValue.measured_values |= EM_VAR_FORWARD_REACTIVE_ENERGY; + } +} + +// energy in 0.00001 kWh +void Supla::Sensor::ElectricityMeter::setRvrReactEnergy( + int phase, unsigned _supla_int64_t energy) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.total_reverse_reactive_energy[phase] != energy) { + valueChanged = true; + } + emValue.total_reverse_reactive_energy[phase] = energy; + emValue.measured_values |= EM_VAR_REVERSE_REACTIVE_ENERGY; + } +} + +// voltage in 0.01 V +void Supla::Sensor::ElectricityMeter::setVoltage( + int phase, unsigned _supla_int16_t voltage) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].voltage[phase] != voltage) { + valueChanged = true; + } + emValue.m[0].voltage[phase] = voltage; + emValue.measured_values |= EM_VAR_VOLTAGE; + } +} + +// current in 0.001 A +void Supla::Sensor::ElectricityMeter::setCurrent( + int phase, unsigned _supla_int_t current) { + if (phase >= 0 && phase < MAX_PHASES) { + if (rawCurrent[phase] != current) { + valueChanged = true; + } + rawCurrent[phase] = current; + currentMeasurementAvailable = true; + } +} + +// Frequency in 0.01 Hz +void Supla::Sensor::ElectricityMeter::setFreq(unsigned _supla_int16_t freq) { + if (emValue.m[0].freq != freq) { + valueChanged = true; + } + emValue.m[0].freq = freq; + emValue.measured_values |= EM_VAR_FREQ; +} + +// power in 0.00001 kW +void Supla::Sensor::ElectricityMeter::setPowerActive(int phase, + _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_active[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_active[phase] = power; + emValue.measured_values |= EM_VAR_POWER_ACTIVE; + } +} + +// power in 0.00001 kvar +void Supla::Sensor::ElectricityMeter::setPowerReactive(int phase, + _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_reactive[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_reactive[phase] = power; + emValue.measured_values |= EM_VAR_POWER_REACTIVE; + } +} + +// power in 0.00001 kVA +void Supla::Sensor::ElectricityMeter::setPowerApparent(int phase, + _supla_int_t power) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_apparent[phase] != power) { + valueChanged = true; + } + emValue.m[0].power_apparent[phase] = power; + emValue.measured_values |= EM_VAR_POWER_APPARENT; + } +} + +// power in 0.001 +void Supla::Sensor::ElectricityMeter::setPowerFactor(int phase, + _supla_int_t powerFactor) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].power_factor[phase] != powerFactor) { + valueChanged = true; + } + emValue.m[0].power_factor[phase] = powerFactor; + emValue.measured_values |= EM_VAR_POWER_FACTOR; + } +} + +// phase angle in 0.1 degree +void Supla::Sensor::ElectricityMeter::setPhaseAngle(int phase, + _supla_int_t phaseAngle) { + if (phase >= 0 && phase < MAX_PHASES) { + if (emValue.m[0].phase_angle[phase] != phaseAngle) { + valueChanged = true; + } + emValue.m[0].phase_angle[phase] = phaseAngle; + emValue.measured_values |= EM_VAR_PHASE_ANGLE; + } +} + +void Supla::Sensor::ElectricityMeter::resetReadParameters() { + if (emValue.measured_values != 0) { + emValue.measured_values = 0; + memset(&emValue.m[0], 0, sizeof(TElectricityMeter_Measurement)); + valueChanged = true; + } +} + +// Please implement this class for reading value from elecricity meter device. +// It will be called every 5 s. Use set methods defined above in order to +// set values on channel. Don't use any other method to modify channel values. +void Supla::Sensor::ElectricityMeter::readValuesFromDevice() { +} + +// Put here initialization code for electricity meter device. +// It will be called within SuplaDevce.begin method. +// It should also read first data set, so at the end it should call those two +// methods: +// readValuesFromDevice(); +// updateChannelValues(); +void Supla::Sensor::ElectricityMeter::onInit() { +} + +void Supla::Sensor::ElectricityMeter::iterateAlways() { + if (millis() - lastReadTime > refreshRateSec*1000) { + lastReadTime = millis(); + readValuesFromDevice(); + updateChannelValues(); + } +} + +// Implement this method to reset stored energy value (i.e. to set energy +// counter back to 0 kWh +void Supla::Sensor::ElectricityMeter::resetStorage() { +} + +Supla::Channel *Supla::Sensor::ElectricityMeter::getChannel() { + return &extChannel; +} + +void Supla::Sensor::ElectricityMeter::setResreshRate(unsigned int sec) { + refreshRateSec = sec; + if (refreshRateSec == 0) { + refreshRateSec = 1; + } +} + + diff --git a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h index 3180e7e3..713ea99c 100644 --- a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h +++ b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h @@ -17,7 +17,6 @@ #ifndef _electricity_meter_h #define _electricity_meter_h -#include #include #include "../channel_extended.h" @@ -30,199 +29,52 @@ namespace Supla { namespace Sensor { class ElectricityMeter : public Element { public: - ElectricityMeter() : valueChanged(false), lastReadTime(0) { - extChannel.setType(SUPLA_CHANNELTYPE_ELECTRICITY_METER); - extChannel.setDefault(SUPLA_CHANNELFNC_ELECTRICITY_METER); - memset(&emValue, 0, sizeof(emValue)); - emValue.period = 5; - for (int i = 0; i < MAX_PHASES; i++) { - rawCurrent[i] = 0; - } - currentMeasurementAvailable = false; - } + ElectricityMeter(); - virtual void updateChannelValues() { - if (!valueChanged) { - return; - } - valueChanged = false; - - emValue.m_count = 1; - - // Update current messurement precision based on last updates - if (currentMeasurementAvailable) { - bool over65A = false; - for (int i = 0; i < MAX_PHASES; i++) { - if (rawCurrent[i] > 65000) { - over65A = true; - } - } - - for (int i = 0; i < MAX_PHASES; i++) { - if (over65A) { - emValue.m[0].current[i] = rawCurrent[i] / 10; - } else { - emValue.m[0].current[i] = rawCurrent[i]; - } - } - - if (over65A) { - emValue.measured_values ^= (!EM_VAR_CURRENT); - emValue.measured_values |= EM_VAR_CURRENT_OVER_65A; - } else { - emValue.measured_values ^= (!EM_VAR_CURRENT_OVER_65A); - emValue.measured_values |= EM_VAR_CURRENT; - } - } - - // Prepare extended channel value - srpc_evtool_v2_emextended2extended(&emValue, extChannel.getExtValue()); - extChannel.setNewValue(emValue); - } + virtual void updateChannelValues(); // energy in 0.00001 kWh - void setFwdActEnergy(int phase, unsigned _supla_int64_t energy) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.total_forward_active_energy[phase] != energy) { - valueChanged = true; - } - emValue.total_forward_active_energy[phase] = energy; - emValue.measured_values |= EM_VAR_FORWARD_ACTIVE_ENERGY; - } - } + void setFwdActEnergy(int phase, unsigned _supla_int64_t energy); // energy in 0.00001 kWh - void setRvrActEnergy(int phase, unsigned _supla_int64_t energy) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.total_reverse_active_energy[phase] != energy) { - valueChanged = true; - } - emValue.total_reverse_active_energy[phase] = energy; - emValue.measured_values |= EM_VAR_REVERSE_ACTIVE_ENERGY; - } - } + void setRvrActEnergy(int phase, unsigned _supla_int64_t energy); // energy in 0.00001 kWh - void setFwdReactEnergy(int phase, unsigned _supla_int64_t energy) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.total_forward_reactive_energy[phase] != energy) { - valueChanged = true; - } - emValue.total_forward_reactive_energy[phase] = energy; - emValue.measured_values |= EM_VAR_FORWARD_REACTIVE_ENERGY; - } - } + void setFwdReactEnergy(int phase, unsigned _supla_int64_t energy); // energy in 0.00001 kWh - void setRvrReactEnergy(int phase, unsigned _supla_int64_t energy) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.total_reverse_reactive_energy[phase] != energy) { - valueChanged = true; - } - emValue.total_reverse_reactive_energy[phase] = energy; - emValue.measured_values |= EM_VAR_REVERSE_REACTIVE_ENERGY; - } - } + void setRvrReactEnergy(int phase, unsigned _supla_int64_t energy); // voltage in 0.01 V - void setVoltage(int phase, unsigned _supla_int16_t voltage) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].voltage[phase] != voltage) { - valueChanged = true; - } - emValue.m[0].voltage[phase] = voltage; - emValue.measured_values |= EM_VAR_VOLTAGE; - } - } + void setVoltage(int phase, unsigned _supla_int16_t voltage); // current in 0.001 A - void setCurrent(int phase, unsigned _supla_int_t current) { - if (phase >= 0 && phase < MAX_PHASES) { - if (rawCurrent[phase] != current) { - valueChanged = true; - } - rawCurrent[phase] = current; - currentMeasurementAvailable = true; - } - } + void setCurrent(int phase, unsigned _supla_int_t current); // Frequency in 0.01 Hz - void setFreq(unsigned _supla_int16_t freq) { - if (emValue.m[0].freq != freq) { - valueChanged = true; - } - emValue.m[0].freq = freq; - emValue.measured_values |= EM_VAR_FREQ; - } + void setFreq(unsigned _supla_int16_t freq); // power in 0.00001 kW - void setPowerActive(int phase, _supla_int_t power) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].power_active[phase] != power) { - valueChanged = true; - } - emValue.m[0].power_active[phase] = power; - emValue.measured_values |= EM_VAR_POWER_ACTIVE; - } - } + void setPowerActive(int phase, _supla_int_t power); // power in 0.00001 kvar - void setPowerReactive(int phase, _supla_int_t power) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].power_reactive[phase] != power) { - valueChanged = true; - } - emValue.m[0].power_reactive[phase] = power; - emValue.measured_values |= EM_VAR_POWER_REACTIVE; - } - } + void setPowerReactive(int phase, _supla_int_t power); // power in 0.00001 kVA - void setPowerApparent(int phase, _supla_int_t power) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].power_apparent[phase] != power) { - valueChanged = true; - } - emValue.m[0].power_apparent[phase] = power; - emValue.measured_values |= EM_VAR_POWER_APPARENT; - } - } + void setPowerApparent(int phase, _supla_int_t power); // power in 0.001 - void setPowerFactor(int phase, _supla_int_t powerFactor) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].power_factor[phase] != powerFactor) { - valueChanged = true; - } - emValue.m[0].power_factor[phase] = powerFactor; - emValue.measured_values |= EM_VAR_POWER_FACTOR; - } - } + void setPowerFactor(int phase, _supla_int_t powerFactor); // phase angle in 0.1 degree - void setPhaseAngle(int phase, _supla_int_t phaseAngle) { - if (phase >= 0 && phase < MAX_PHASES) { - if (emValue.m[0].phase_angle[phase] != phaseAngle) { - valueChanged = true; - } - emValue.m[0].phase_angle[phase] = phaseAngle; - emValue.measured_values |= EM_VAR_PHASE_ANGLE; - } - } + void setPhaseAngle(int phase, _supla_int_t phaseAngle); - void resetReadParameters() { - if (emValue.measured_values != 0) { - emValue.measured_values = 0; - memset(&emValue.m[0], 0, sizeof(TElectricityMeter_Measurement)); - valueChanged = true; - } - } + void resetReadParameters(); // Please implement this class for reading value from elecricity meter device. // It will be called every 5 s. Use set methods defined above in order to // set values on channel. Don't use any other method to modify channel values. - virtual void readValuesFromDevice() { - } + virtual void readValuesFromDevice(); // Put here initialization code for electricity meter device. // It will be called within SuplaDevce.begin method. @@ -230,35 +82,30 @@ class ElectricityMeter : public Element { // methods: // readValuesFromDevice(); // updateChannelValues(); - void onInit() { - } + void onInit(); - void iterateAlways() { - if (lastReadTime + 5000 < millis()) { - lastReadTime = millis(); - readValuesFromDevice(); - updateChannelValues(); - } - } + void iterateAlways(); // Implement this method to reset stored energy value (i.e. to set energy // counter back to 0 kWh - virtual void resetStorage() { - } + virtual void resetStorage(); + + void setResreshRate(unsigned int sec); protected: - Channel *getChannel() { - return &extChannel; - } + Channel *getChannel(); + TElectricityMeter_ExtendedValue_V2 emValue; ChannelExtended extChannel; unsigned _supla_int_t rawCurrent[MAX_PHASES]; bool valueChanged; bool currentMeasurementAvailable; unsigned long lastReadTime; + unsigned int refreshRateSec; }; }; // namespace Sensor }; // namespace Supla #endif + diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp index d710eea3..4589a9fd 100644 --- a/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "impulse_counter.h" @@ -52,9 +53,9 @@ ImpulseCounter::ImpulseCounter(int _impulsePin, void ImpulseCounter::onInit() { if (inputPullup) { - pinMode(impulsePin, INPUT_PULLUP); + Supla::Io::pinMode(channel.getChannelNumber(), impulsePin, INPUT_PULLUP); } else { - pinMode(impulsePin, INPUT); + Supla::Io::pinMode(channel.getChannelNumber(), impulsePin, INPUT); } } @@ -88,7 +89,7 @@ void ImpulseCounter::incCounter() { } void ImpulseCounter::onFastTimer() { - int currentState = digitalRead(impulsePin); + int currentState = Supla::Io::digitalRead(channel.getChannelNumber(), impulsePin); if (prevState == (detectLowToHigh == true ? LOW : HIGH)) { if (millis() - lastImpulseMillis > debounceDelay) { if (currentState == (detectLowToHigh == true ? HIGH : LOW)) { diff --git a/lib/SuplaDevice/src/supla/storage/storage.cpp b/lib/SuplaDevice/src/supla/storage/storage.cpp index 52dfaf8e..8e21d098 100644 --- a/lib/SuplaDevice/src/supla/storage/storage.cpp +++ b/lib/SuplaDevice/src/supla/storage/storage.cpp @@ -83,6 +83,12 @@ bool Storage::SaveStateAllowed(unsigned long ms) { return false; } +void Storage::ScheduleSave(unsigned long delayMs) { + if (Instance()) { + Instance()->scheduleSave(delayMs); + } +} + Storage::Storage(unsigned int storageStartingOffset) : storageStartingOffset(storageStartingOffset), deviceConfigOffset(0), @@ -319,3 +325,11 @@ bool Storage::saveStateAllowed(unsigned long ms) { return false; } +void Storage::scheduleSave(unsigned long delayMs) { + unsigned long currentMs = millis(); + unsigned long newTimestamp = currentMs - saveStatePeriod - 1 + delayMs; + + if (currentMs - lastWriteTimestamp < currentMs - newTimestamp) { + lastWriteTimestamp = newTimestamp; + } +} diff --git a/lib/SuplaDevice/src/supla/storage/storage.h b/lib/SuplaDevice/src/supla/storage/storage.h index 66284302..f25a4091 100644 --- a/lib/SuplaDevice/src/supla/storage/storage.h +++ b/lib/SuplaDevice/src/supla/storage/storage.h @@ -36,6 +36,7 @@ class Storage { static void PrepareState(bool dryRun = false); static bool FinalizeSaveState(); static bool SaveStateAllowed(unsigned long); + static void ScheduleSave(unsigned long delayMs); Storage(unsigned int storageStartingOffset = 0); @@ -51,6 +52,7 @@ class Storage { virtual void prepareState(bool performDryRun); virtual bool finalizeSaveState(); virtual bool saveStateAllowed(unsigned long); + virtual void scheduleSave(unsigned long delayMs); virtual void commit() = 0; diff --git a/lib/SuplaDevice/src/supla/timer.cpp b/lib/SuplaDevice/src/supla/timer.cpp index b77eb54a..a0a50ec7 100644 --- a/lib/SuplaDevice/src/supla/timer.cpp +++ b/lib/SuplaDevice/src/supla/timer.cpp @@ -20,7 +20,7 @@ #include "timer.h" #if defined(ARDUINO_ARCH_ESP32) -#include +#include #endif namespace { @@ -38,14 +38,14 @@ void esp_fastTimer_cb(void *timer_arg) { SuplaDevice.onFastTimer(); } #elif defined(ARDUINO_ARCH_ESP32) -hw_timer_t *supla_esp_timer = NULL; -hw_timer_t *supla_esp_fastTimer = NULL; +Ticker supla_esp_timer; +Ticker supla_esp_fastTimer; -void IRAM_ATTR esp_timer_cb() { +void esp_timer_cb() { SuplaDevice.onTimer(); } -void IRAM_ATTR esp_fastTimer_cb() { +void esp_fastTimer_cb() { SuplaDevice.onFastTimer(); } #else @@ -71,17 +71,8 @@ void initTimers() { os_timer_arm(&supla_esp_fastTimer, 1, 1); #elif defined(ARDUINO_ARCH_ESP32) - supla_esp_timer = timerBegin(0, 80, true); // timer 0, div 80 - timerAttachInterrupt(supla_esp_timer, &esp_timer_cb, true); // attach callback - timerAlarmWrite(supla_esp_timer, 10 * 1000, false); // set time in us - timerAlarmEnable(supla_esp_timer); // enable interrupt - - supla_esp_fastTimer = timerBegin(1, 80, true); - timerAttachInterrupt(supla_esp_fastTimer, - &esp_fastTimer_cb, - true); // attach callback - timerAlarmWrite(supla_esp_fastTimer, 1 * 1000, false); // set time in us - timerAlarmEnable(supla_esp_fastTimer); // enable interrupt + supla_esp_timer.attach_ms(10, esp_timer_cb); + supla_esp_fastTimer.attach_ms(1, esp_fastTimer_cb); #else // Timer 1 for interrupt frequency 100 Hz (10 ms) TCCR1A = 0; // set entire TCCR1A register to 0 From a72e11268a37707b44ac07c620a857f6b78a4125 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 14 Jan 2021 08:29:41 +0100 Subject: [PATCH 100/233] aktualizacja SuplaDevice --- lib/SuplaDevice/examples/Afore/Afore.ino | 33 +-- lib/SuplaDevice/examples/DHT/DHT.ino | 35 ++- .../DallasTemperature/DallasTemperature.ino | 35 ++- lib/SuplaDevice/examples/Fronius/Fronius.ino | 33 +-- .../HC_SR04_Distance_sensor.ino | 37 ++- .../ImpulseCounter/ImpulseCounter.ino | 36 ++- .../examples/Pzem_V_2/Pzem_V_2.ino | 28 +- .../examples/Pzem_V_3/Pzem_V_3.ino | 80 +++--- lib/SuplaDevice/examples/RGBW/RGBW.ino | 35 ++- .../examples/RollerShutter/RollerShutter.ino | 35 +-- .../SequenceButton/SequenceButton.ino | 35 ++- lib/SuplaDevice/extras/test/CMakeLists.txt | 20 +- .../test/ChannelTests/channel_tests.cpp | 270 ++++++++++++++++++ .../extras/test/IoTests/io_tests.cpp | 26 ++ .../extras/test/UptimeTests/uptime_tests.cpp | 16 ++ lib/SuplaDevice/extras/test/doubles/Arduino.h | 109 +++++++ .../extras/test/doubles/arduino_mock.cpp | 59 ++++ lib/SuplaDevice/extras/test/doubles/log.cpp | 22 ++ lib/SuplaDevice/extras/test/doubles/srpc.cpp | 29 ++ lib/SuplaDevice/src/CMakeLists.txt | 4 + lib/SuplaDevice/src/SuplaDevice.cpp | 1 - .../src/supla-common/IEEE754tools.h | 7 - lib/SuplaDevice/src/supla/channel.cpp | 119 +++++--- lib/SuplaDevice/src/supla/channel.h | 23 +- lib/SuplaDevice/src/supla/control/relay.h | 3 +- lib/SuplaDevice/src/supla/control/rgbw_base.h | 26 +- .../src/supla/control/roller_shutter.cpp | 29 +- .../src/supla/control/roller_shutter.h | 4 +- lib/SuplaDevice/src/supla/element.cpp | 4 + lib/SuplaDevice/src/supla/element.h | 3 +- lib/SuplaDevice/src/supla/sensor/binary.h | 2 +- lib/SuplaDevice/src/supla/sensor/distance.h | 5 +- .../src/supla/sensor/electricity_meter.h | 2 +- .../sensor/general_purpose_measurement_base.h | 3 +- .../src/supla/sensor/impulse_counter.h | 3 +- lib/SuplaDevice/src/supla/sensor/pressure.h | 4 +- lib/SuplaDevice/src/supla/sensor/rain.h | 4 +- .../src/supla/sensor/therm_hygro_meter.cpp | 37 +++ .../src/supla/sensor/therm_hygro_meter.h | 26 +- .../supla/sensor/therm_hygro_press_meter.cpp | 57 ++++ .../supla/sensor/therm_hygro_press_meter.h | 41 +-- .../src/supla/sensor/thermometer.cpp | 37 +++ .../src/supla/sensor/thermometer.h | 24 +- .../src/supla/sensor/virtual_binary.h | 2 +- lib/SuplaDevice/src/supla/sensor/weight.h | 4 +- lib/SuplaDevice/src/supla/sensor/wind.h | 4 +- lib/SuplaDevice/src/supla/tools.cpp | 13 + lib/SuplaDevice/src/supla/tools.h | 2 + 48 files changed, 1066 insertions(+), 400 deletions(-) create mode 100644 lib/SuplaDevice/extras/test/ChannelTests/channel_tests.cpp create mode 100644 lib/SuplaDevice/extras/test/IoTests/io_tests.cpp create mode 100644 lib/SuplaDevice/extras/test/doubles/Arduino.h create mode 100644 lib/SuplaDevice/extras/test/doubles/arduino_mock.cpp create mode 100644 lib/SuplaDevice/extras/test/doubles/log.cpp create mode 100644 lib/SuplaDevice/extras/test/doubles/srpc.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.cpp create mode 100644 lib/SuplaDevice/src/supla/sensor/thermometer.cpp diff --git a/lib/SuplaDevice/examples/Afore/Afore.ino b/lib/SuplaDevice/examples/Afore/Afore.ino index eaace6d9..859636c9 100644 --- a/lib/SuplaDevice/examples/Afore/Afore.ino +++ b/lib/SuplaDevice/examples/Afore/Afore.ino @@ -14,28 +14,25 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { diff --git a/lib/SuplaDevice/examples/DHT/DHT.ino b/lib/SuplaDevice/examples/DHT/DHT.ino index b91c0fea..e0c85e2a 100644 --- a/lib/SuplaDevice/examples/DHT/DHT.ino +++ b/lib/SuplaDevice/examples/DHT/DHT.ino @@ -14,28 +14,25 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif /* * This example requires DHT sensor library installed. @@ -49,7 +46,7 @@ Supla::EthernetShield ethernet(mac); void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino b/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino index f10308df..81eb8fbc 100644 --- a/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino +++ b/lib/SuplaDevice/examples/DallasTemperature/DallasTemperature.ino @@ -14,7 +14,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include /* @@ -26,27 +25,25 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/Fronius/Fronius.ino b/lib/SuplaDevice/examples/Fronius/Fronius.ino index 98b3b7ad..27cc0e75 100644 --- a/lib/SuplaDevice/examples/Fronius/Fronius.ino +++ b/lib/SuplaDevice/examples/Fronius/Fronius.ino @@ -14,28 +14,25 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { diff --git a/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino b/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino index 2de1ac58..0bb45c39 100644 --- a/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino +++ b/lib/SuplaDevice/examples/HC_SR04_Distance_sensor/HC_SR04_Distance_sensor.ino @@ -14,7 +14,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include // Add include to HC_SR04 sensor @@ -22,27 +21,25 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; @@ -63,7 +60,7 @@ void setup() { /* * SuplaDevice Initialization. - * Server address, LocationID and LocationPassword are available at https://cloud.supla.org + * Server address is available at https://cloud.supla.org * If you do not have an account, you can create it at https://cloud.supla.org/account/create * SUPLA and SUPLA CLOUD are free of charge * diff --git a/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino b/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino index 5035b970..fe626866 100644 --- a/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino +++ b/lib/SuplaDevice/examples/ImpulseCounter/ImpulseCounter.ino @@ -14,7 +14,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include @@ -26,29 +25,26 @@ Supla::Eeprom eeprom(STORAGE_OFFSET); // #include // Supla::FramSpi fram(STORAGE_OFFSET); - // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino b/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino index e03d4aa9..0b8c4918 100644 --- a/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino +++ b/lib/SuplaDevice/examples/Pzem_V_2/Pzem_V_2.ino @@ -13,24 +13,32 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - // this example will work only on esp8266 and esp32 boards. On Arduino mega it will not fly. + //dependence: Arduino communication library for Peacefair PZEM-004T Energy monitor https://github.com/olehs/PZEM004T -#include #include #include -// ESP8266 based board: -#include -Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +// Choose proper network interface for your card: +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino b/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino index fd435453..85fe0bb6 100644 --- a/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino +++ b/lib/SuplaDevice/examples/Pzem_V_3/Pzem_V_3.ino @@ -13,56 +13,66 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ - // this example will work only on esp8266 and esp32 boards. On Arduino mega it will not fly. - //dependence: Arduino library for the Updated PZEM-004T v3.0 Power and Energy meter https://github.com/mandulaj/PZEM-004T-v30 -#include +// dependence: Arduino library for the Updated PZEM-004T v3.0 Power and Energy +// meter https://github.com/mandulaj/PZEM-004T-v30 + #include #include -// ESP8266 based board: +// Choose proper network interface for your card: +#ifdef ARDUINO_ARCH_AVR +// Arduino Mega with EthernetShield W5100: +#include +// Ethernet MAC address +uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; +Supla::EthernetShield ethernet(mac); + +// Arduino Mega with ENC28J60: +// #include +// Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) +// ESP8266 and ESP32 based board: #include Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { + Serial.begin(115200); - Serial.begin(9600); - - // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid - char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; - - // Replace the following AUTHKEY with value that you can retrieve from: https://www.supla.org/arduino/get-authkey - char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + // Replace the falowing GUID with value that you can retrieve from + // https://www.supla.org/arduino/get-guid + char GUID[SUPLA_GUID_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - /* - * Having your device already registered at cloud.supla.org, - * you want to change CHANNEL sequence or remove any of them, - * then you must also remove the device itself from cloud.supla.org. - * Otherwise you will get "Channel conflict!" error. - */ - - new Supla::Sensor::PZEMv3(5, 4); // (RX,TX) + // Replace the following AUTHKEY with value that you can retrieve from: + // https://www.supla.org/arduino/get-authkey + char AUTHKEY[SUPLA_AUTHKEY_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + /* + * Having your device already registered at cloud.supla.org, + * you want to change CHANNEL sequence or remove any of them, + * then you must also remove the device itself from cloud.supla.org. + * Otherwise you will get "Channel conflict!" error. + */ - /* - * SuplaDevice Initialization. - * Server address, is available at https://cloud.supla.org - * If you do not have an account, you can create it at https://cloud.supla.org/account/create - * SUPLA and SUPLA CLOUD are free of charge - * - */ + new Supla::Sensor::PZEMv3(5, 4); // (RX,TX) - SuplaDevice.begin(GUID, // Global Unique Identifier - "svr1.supla.org", // SUPLA server address - "email@address", // Email address used to login to Supla Cloud - AUTHKEY); // Authorization key + /* + * SuplaDevice Initialization. + * Server address, is available at https://cloud.supla.org + * If you do not have an account, you can create it at + * https://cloud.supla.org/account/create SUPLA and SUPLA CLOUD are free of + * charge + * + */ + SuplaDevice.begin( + GUID, // Global Unique Identifier + "svr1.supla.org", // SUPLA server address + "email@address", // Email address used to login to Supla Cloud + AUTHKEY); // Authorization key } void loop() { - SuplaDevice.iterate(); + SuplaDevice.iterate(); } diff --git a/lib/SuplaDevice/examples/RGBW/RGBW.ino b/lib/SuplaDevice/examples/RGBW/RGBW.ino index 994fd701..fb446e08 100644 --- a/lib/SuplaDevice/examples/RGBW/RGBW.ino +++ b/lib/SuplaDevice/examples/RGBW/RGBW.ino @@ -14,28 +14,25 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif /* * Youtube: https://youtu.be/FE9tqzTjmA4 @@ -83,7 +80,7 @@ class RgbwLeds : public Supla::Control::RGBWBase { }; void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino b/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino index 9747b4e6..7cb0231d 100644 --- a/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino +++ b/lib/SuplaDevice/examples/RollerShutter/RollerShutter.ino @@ -14,7 +14,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include #include @@ -27,30 +26,26 @@ Supla::Eeprom eeprom(STORAGE_OFFSET); // #include // Supla::FramSpi fram(STORAGE_OFFSET); - // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -#include -// Ethernet MAC address -uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -// #include -// Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { - Serial.begin(9600); + Serial.begin(115200); // Replace the falowing GUID with value that you can retrieve from https://www.supla.org/arduino/get-guid char GUID[SUPLA_GUID_SIZE] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; diff --git a/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino b/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino index 8647974a..75f81134 100644 --- a/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino +++ b/lib/SuplaDevice/examples/SequenceButton/SequenceButton.ino @@ -14,29 +14,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include #include #include #include // Choose proper network interface for your card: -// Arduino Mega with EthernetShield W5100: -//#include -// Ethernet MAC address -//uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; -//Supla::EthernetShield ethernet(mac); -// -// Arduino Mega with ENC28J60: -// #include -// Supla::ENC28J60 ethernet(mac); -// -// ESP8266 based board: -#include -Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); -// -// ESP32 based board: -// #include -// Supla::ESP32Wifi wifi("your_wifi_ssid", "your_wifi_password"); +#ifdef ARDUINO_ARCH_AVR + // Arduino Mega with EthernetShield W5100: + #include + // Ethernet MAC address + uint8_t mac[6] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + Supla::EthernetShield ethernet(mac); + + // Arduino Mega with ENC28J60: + // #include + // Supla::ENC28J60 ethernet(mac); +#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + // ESP8266 and ESP32 based board: + #include + Supla::ESPWifi wifi("your_wifi_ssid", "your_wifi_password"); +#endif void setup() { @@ -58,7 +55,7 @@ void setup() { auto secretRelay = new Supla::Control::Relay(30, false); // Low level trigger relay on pin 30 auto alarmRelay = new Supla::Control::Relay(31, false); // Low level trigger relay on pin 31 - auto seqButton = new Supla::Control::SequenceButton(D9, true, true); // Button on pin 28 with internal pullUp + auto seqButton = new Supla::Control::SequenceButton(28, true, true); // Button on pin 28 with internal pullUp // and LOW is considered as "pressed" state // Sequence of lenghts [ms] of button being presset, released, pressed, released, etc. diff --git a/lib/SuplaDevice/extras/test/CMakeLists.txt b/lib/SuplaDevice/extras/test/CMakeLists.txt index 12e94f97..a96d1608 100644 --- a/lib/SuplaDevice/extras/test/CMakeLists.txt +++ b/lib/SuplaDevice/extras/test/CMakeLists.txt @@ -6,17 +6,24 @@ enable_testing() set(CMAKE_CXX_STANDARD 14) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_BUILD_TYPE Debug) include_directories(../../src) +include_directories(doubles) add_subdirectory(../../src/ build) +mark_as_advanced( +BUILD_GMOCK +BUILD_GTEST +) + include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.8.0 + GIT_TAG release-1.10.0 ) FetchContent_GetProperties(googletest) @@ -25,11 +32,18 @@ if(NOT googletest_POPULATED) add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) endif() -file(GLOB SRCS UptimeTests/*.cpp) +file(GLOB TEST_SRC + UptimeTests/*.cpp + ChannelTests/*cpp + IoTests/*.cpp + ) + +file(GLOB DOUBLE_SRC doubles/*.cpp) -add_executable(supladevicetests ${SRCS}) +add_executable(supladevicetests ${TEST_SRC} ${DOUBLE_SRC}) target_link_libraries(supladevicetests + gmock gtest gtest_main supladevicelib diff --git a/lib/SuplaDevice/extras/test/ChannelTests/channel_tests.cpp b/lib/SuplaDevice/extras/test/ChannelTests/channel_tests.cpp new file mode 100644 index 00000000..4fc872f4 --- /dev/null +++ b/lib/SuplaDevice/extras/test/ChannelTests/channel_tests.cpp @@ -0,0 +1,270 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include +#include + +TEST(ChannelTests, ChannelMethods) { + Supla::Channel first; + Supla::Channel second; + + EXPECT_EQ(first.getChannelNumber(), 0); + EXPECT_EQ(second.getChannelNumber(), 1); + + EXPECT_EQ(first.isExtended(), false); + EXPECT_EQ(first.isUpdateReady(), false); + EXPECT_EQ(first.getChannelType(), 0); + EXPECT_EQ(first.getExtValue(), nullptr); + + int number = first.getChannelNumber(); + char emptyArray[SUPLA_CHANNELVALUE_SIZE] = {}; + EXPECT_EQ(number, Supla::Channel::reg_dev.channels[number].Number); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Type, 0); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].FuncList, 0); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Default, 0); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Flags, SUPLA_CHANNEL_FLAG_CHANNELSTATE); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, emptyArray, SUPLA_CHANNELVALUE_SIZE)); + + first.setType(10); + EXPECT_EQ(first.getChannelType(), 10); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Type, 10); + + first.setDefault(14); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Default, 14); + + first.setFlag(2); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Flags, SUPLA_CHANNEL_FLAG_CHANNELSTATE | 2); + + first.setFlag(4); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Flags, SUPLA_CHANNEL_FLAG_CHANNELSTATE | 2 | 4); + + first.unsetFlag(2); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Flags, SUPLA_CHANNEL_FLAG_CHANNELSTATE | 4); + + first.unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].Flags, 4); + + first.setFuncList(11); + EXPECT_EQ(Supla::Channel::reg_dev.channels[number].FuncList, 11); + +} + +TEST(ChannelTests, SetNewValue) { + Supla::Channel channel; + int number = channel.getChannelNumber(); + char emptyArray[SUPLA_CHANNELVALUE_SIZE] = {}; + + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, emptyArray, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_FALSE(channel.isUpdateReady()); + + char array[SUPLA_CHANNELVALUE_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7}; + channel.setNewValue(array); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, array, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + + channel.clearUpdateReady(); + EXPECT_FALSE(channel.isUpdateReady()); + + channel.setNewValue(array); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, array, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_FALSE(channel.isUpdateReady()); + + array[4] = 15; + channel.setNewValue(array); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, array, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + + ASSERT_EQ(sizeof(double), 8); + double temp = 3.1415; + channel.setNewValue(temp); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &temp, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + char arrayBool[SUPLA_CHANNELVALUE_SIZE] = {}; + arrayBool[0] = true; + channel.setNewValue(true); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, arrayBool, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + channel.setNewValue(false); + arrayBool[0] = false; + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, arrayBool, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + int value = 1234; + ASSERT_EQ(sizeof(int), 4); + channel.setNewValue(value); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &value, sizeof(int))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + _supla_int64_t value64 = 124346; + ASSERT_EQ(sizeof(value64), 8); + channel.setNewValue(value64); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &value64, sizeof(value64))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + double humi = 95.2234123; + temp = 23.443322; + + int expectedTemp = temp * 1000; + int expectedHumi = humi * 1000; + + channel.setNewValue(temp, humi); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedTemp, sizeof(expectedTemp))); + EXPECT_TRUE(0 == memcmp(&(Supla::Channel::reg_dev.channels[number].value[4]), &expectedHumi, sizeof(expectedHumi))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + // RGBW channel setting + channel.setNewValue(1, 2, 3, 4, 5); + char rgbwArray[SUPLA_CHANNELVALUE_SIZE] = {5, 4, 3, 2, 1, 0, 0, 0}; + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, rgbwArray, SUPLA_CHANNELVALUE_SIZE)); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + TElectricityMeter_ExtendedValue_V2 emVal = {}; + TElectricityMeter_Value expectedValue = {}; + + emVal.m_count = 1; + emVal.measured_values |= EM_VAR_FORWARD_ACTIVE_ENERGY; + emVal.total_forward_active_energy[0] = 1000; + emVal.total_forward_active_energy[1] = 2000; + emVal.total_forward_active_energy[2] = 4000; + + expectedValue.total_forward_active_energy = (1000 + 2000 + 4000) / 1000; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + emVal.measured_values |= EM_VAR_VOLTAGE; + emVal.m[0].voltage[0] = 10; + emVal.m[0].voltage[1] = 0; + emVal.m[0].voltage[2] = 0; + + expectedValue.flags = 0; + expectedValue.flags |= EM_VALUE_FLAG_PHASE1_ON; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + emVal.m[0].voltage[0] = 0; + emVal.m[0].voltage[1] = 20; + emVal.m[0].voltage[2] = 0; + + expectedValue.flags = 0; + expectedValue.flags |= EM_VALUE_FLAG_PHASE2_ON; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + + emVal.m[0].voltage[0] = 0; + emVal.m[0].voltage[1] = 0; + emVal.m[0].voltage[2] = 300; + + expectedValue.flags = 0; + expectedValue.flags |= EM_VALUE_FLAG_PHASE3_ON; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + + emVal.m[0].voltage[0] = 10; + emVal.m[0].voltage[1] = 0; + emVal.m[0].voltage[2] = 540; + + expectedValue.flags = 0; + expectedValue.flags |= EM_VALUE_FLAG_PHASE1_ON | EM_VALUE_FLAG_PHASE3_ON; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); + + emVal.m[0].voltage[0] = 10; + emVal.m[0].voltage[1] = 230; + emVal.m[0].voltage[2] = 540; + + expectedValue.flags = 0; + expectedValue.flags |= EM_VALUE_FLAG_PHASE1_ON | EM_VALUE_FLAG_PHASE3_ON | EM_VALUE_FLAG_PHASE2_ON; + + channel.setNewValue(emVal); + EXPECT_TRUE(0 == memcmp(Supla::Channel::reg_dev.channels[number].value, &expectedValue, sizeof(expectedValue))); + EXPECT_TRUE(channel.isUpdateReady()); + channel.clearUpdateReady(); +} + +TEST(ChannelTests, ExtendedChannelMethods) { + Supla::ChannelExtended extChannel; + + EXPECT_TRUE(extChannel.isExtended()); + EXPECT_NE(nullptr, extChannel.getExtValue() ); + +} + +TEST(ChannelTests, ChannelValueGetters) { + Supla::Channel channel; + + EXPECT_DOUBLE_EQ(channel.getValueDouble(), 0); + + double pi = 3.1415; + channel.setNewValue(pi); + EXPECT_DOUBLE_EQ(channel.getValueDouble(), pi); + + double e = 2.71828; + channel.setNewValue(pi, e); + EXPECT_NEAR(channel.getValueDoubleFirst(), pi, 0.001); + EXPECT_NEAR(channel.getValueDoubleSecond(), e, 0.001); + + int valueInt = 2021; + channel.setNewValue(valueInt); + EXPECT_EQ(channel.getValueInt32(), valueInt); + + _supla_int64_t valueInt64 = 202013012021000; + channel.setNewValue(valueInt64); + EXPECT_EQ(channel.getValueInt64(), valueInt64); + + channel.setNewValue(true); + EXPECT_TRUE(channel.getValueBool()); + + channel.setNewValue(false); + EXPECT_FALSE(channel.getValueBool()); + + uint8_t red = 10, green = 20, blue = 30, colorBright = 50, bright = 90; + channel.setNewValue(red, green, blue, colorBright, bright); + EXPECT_EQ(channel.getValueRed(), red); + EXPECT_EQ(channel.getValueGreen(), green); + EXPECT_EQ(channel.getValueBlue(), blue); + EXPECT_EQ(channel.getValueColorBrightness(), colorBright); + EXPECT_EQ(channel.getValueBrightness(), bright); + + +} diff --git a/lib/SuplaDevice/extras/test/IoTests/io_tests.cpp b/lib/SuplaDevice/extras/test/IoTests/io_tests.cpp new file mode 100644 index 00000000..b0cd081d --- /dev/null +++ b/lib/SuplaDevice/extras/test/IoTests/io_tests.cpp @@ -0,0 +1,26 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +#include + + +TEST(IoTests, DefaultBehavior) { + EXPECT_TRUE(true); + +} + diff --git a/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp b/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp index 0280d0e1..59241f9b 100644 --- a/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp +++ b/lib/SuplaDevice/extras/test/UptimeTests/uptime_tests.cpp @@ -1,3 +1,19 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + #include #include diff --git a/lib/SuplaDevice/extras/test/doubles/Arduino.h b/lib/SuplaDevice/extras/test/doubles/Arduino.h new file mode 100644 index 00000000..66d39925 --- /dev/null +++ b/lib/SuplaDevice/extras/test/doubles/Arduino.h @@ -0,0 +1,109 @@ +#ifndef _test_double_arduino_h +#define _test_double_arduino_h + +#include + +#include + +typedef std::string String; + +#define LSBFIRST 0 + +void digitalWrite(uint8_t pin, uint8_t val); +int digitalRead(uint8_t pin); +void pinMode(uint8_t pin, uint8_t mode); + + +class SerialStub { + public: + SerialStub() { + } + + virtual ~SerialStub() { + } + + int printf(const char *format, ...) { + return 0; + } + int print(const String &) { + return 0; + } + + int print(const char[]) { + return 0; + } + + int print(char) { + return 0; + } + + int print(unsigned char) { + return 0; + } + + int print(int) { + return 0; + } + + int print(unsigned int) { + return 0; + } + + int print(long) { + return 0; + } + + int print(unsigned long) { + return 0; + } + + int print(double) { + return 0; + } + + + int println(const String &s) { + return 0; + } + + int println(const char[]) { + return 0; + } + + int println(char) { + return 0; + } + + int println(unsigned char) { + return 0; + } + + int println(int) { + return 0; + } + + int println(unsigned int) { + return 0; + } + + int println(long) { + return 0; + } + + int println(unsigned long) { + return 0; + } + + int println(double) { + return 0; + } + + int println(void) { + return 0; + } + +}; + +extern SerialStub Serial; + +#endif diff --git a/lib/SuplaDevice/extras/test/doubles/arduino_mock.cpp b/lib/SuplaDevice/extras/test/doubles/arduino_mock.cpp new file mode 100644 index 00000000..442b1c71 --- /dev/null +++ b/lib/SuplaDevice/extras/test/doubles/arduino_mock.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + + +#include "Arduino.h" +#include + +SerialStub Serial; + +class DigitalInterface { + public: + DigitalInterface() { + instance = this; + } + virtual ~DigitalInterface() { + instance = nullptr; + } + + virtual void digitalWrite(uint8_t, uint8_t) = 0; + virtual int digitalRead(uint8_t) = 0; + virtual void pinMode(uint8_t, uint8_t) = 0; + + static DigitalInterface *instance; +}; + +DigitalInterface *DigitalInterface::instance = nullptr; + +class DigitalInterfaceMock : public DigitalInterface { + MOCK_METHOD(void, digitalWrite, (uint8_t, uint8_t), (override)); +// MOCK_METHOD(int, digitalRead, (uint8_t), (override)); + // MOCK_METHOD(void, pinMode, (uint8_t, uint8_t), (override)); + +}; + +void digitalWrite(uint8_t pin, uint8_t val) { + DigitalInterface::instance->digitalWrite(pin, val); +} + +int digitalRead(uint8_t pin) { + return DigitalInterface::instance->digitalRead(pin); +} + +void pinMode(uint8_t pin, uint8_t mode) { + DigitalInterface::instance->pinMode(pin, mode); +} + diff --git a/lib/SuplaDevice/extras/test/doubles/log.cpp b/lib/SuplaDevice/extras/test/doubles/log.cpp new file mode 100644 index 00000000..e117cc36 --- /dev/null +++ b/lib/SuplaDevice/extras/test/doubles/log.cpp @@ -0,0 +1,22 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +void supla_log(int __pri, const char *__fmt, ...) { + return; +} + diff --git a/lib/SuplaDevice/extras/test/doubles/srpc.cpp b/lib/SuplaDevice/extras/test/doubles/srpc.cpp new file mode 100644 index 00000000..5ac9386a --- /dev/null +++ b/lib/SuplaDevice/extras/test/doubles/srpc.cpp @@ -0,0 +1,29 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_extendedvalue_changed( + void *_srpc, unsigned char channel_number, + TSuplaChannelExtendedValue *value) { + return 0; +} + +_supla_int_t SRPC_ICACHE_FLASH srpc_ds_async_channel_value_changed( + void *_srpc, unsigned char channel_number, char *value) { + return 0; +} + diff --git a/lib/SuplaDevice/src/CMakeLists.txt b/lib/SuplaDevice/src/CMakeLists.txt index 2643ea2b..a4a988b7 100644 --- a/lib/SuplaDevice/src/CMakeLists.txt +++ b/lib/SuplaDevice/src/CMakeLists.txt @@ -1,5 +1,9 @@ set(SRCS supla/uptime.cpp + supla/channel.cpp + supla/channel_extended.cpp + supla/io.cpp + supla/tools.cpp ) add_library(supladevicelib SHARED ${SRCS}) diff --git a/lib/SuplaDevice/src/SuplaDevice.cpp b/lib/SuplaDevice/src/SuplaDevice.cpp index 210a909e..de04f397 100644 --- a/lib/SuplaDevice/src/SuplaDevice.cpp +++ b/lib/SuplaDevice/src/SuplaDevice.cpp @@ -330,7 +330,6 @@ void SuplaDeviceClass::iterate(void) { if (!srpc_ds_async_registerdevice_e(srpc, &Supla::Channel::reg_dev)) { supla_log(LOG_DEBUG, "Fatal SRPC failure!"); } - Supla::Channel::clearAllUpdateReady(); } else if (registered == 1) { if (Supla::Network::Ping() == false) { diff --git a/lib/SuplaDevice/src/supla-common/IEEE754tools.h b/lib/SuplaDevice/src/supla-common/IEEE754tools.h index 5a42beec..c38cfbc1 100644 --- a/lib/SuplaDevice/src/supla-common/IEEE754tools.h +++ b/lib/SuplaDevice/src/supla-common/IEEE754tools.h @@ -13,13 +13,6 @@ #ifndef IEEE754tools_h #define IEEE754tools_h - -#if defined(ARDUINO) && ARDUINO >= 100 -#include "Arduino.h" -#else -#include "WProgram.h" -#endif - // IEEE754 float layout; struct IEEEfloat { diff --git a/lib/SuplaDevice/src/supla/channel.cpp b/lib/SuplaDevice/src/supla/channel.cpp index b6c9a658..c0c231b0 100644 --- a/lib/SuplaDevice/src/supla/channel.cpp +++ b/lib/SuplaDevice/src/supla/channel.cpp @@ -14,13 +14,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include + #include "supla/channel.h" #include "supla-common/log.h" #include "supla-common/srpc.h" #include "tools.h" namespace Supla { -Channel *Channel::firstPtr = nullptr; unsigned long Channel::lastCommunicationTimeMs = 0; TDS_SuplaRegisterDevice_E Channel::reg_dev; @@ -30,44 +31,20 @@ Channel::Channel() { channelNumber = -1; if (reg_dev.channel_count < SUPLA_CHANNELMAXCOUNT) { channelNumber = reg_dev.channel_count; + + memset(®_dev.channels[channelNumber], 0, sizeof(reg_dev.channels[channelNumber])); reg_dev.channels[channelNumber].Number = channelNumber; + reg_dev.channel_count++; } else { // TODO: add status CHANNEL_LIMIT_EXCEEDED } - if (firstPtr == nullptr) { - firstPtr = this; - } else { - last()->nextPtr = this; - } - nextPtr = nullptr; setFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); } -Channel *Channel::begin() { - return firstPtr; -} - -Channel *Channel::last() { - Channel *ptr = firstPtr; - while (ptr && ptr->nextPtr) { - ptr = ptr->nextPtr; - } - return ptr; -} - -int Channel::size() { - int count = 0; - Channel *ptr = firstPtr; - if (ptr) { - count++; - } - while (ptr->nextPtr) { - count++; - ptr = ptr->nextPtr; - } - return count; +Channel::~Channel() { + reg_dev.channel_count--; } void Channel::setNewValue(double dbl) { @@ -84,8 +61,8 @@ void Channel::setNewValue(double dbl) { void Channel::setNewValue(double temp, double humi) { char newValue[SUPLA_CHANNELVALUE_SIZE]; - long t = temp * 1000.00; - long h = humi * 1000.00; + _supla_int_t t = temp * 1000.00; + _supla_int_t h = humi * 1000.00; memcpy(newValue, &t, 4); memcpy(&(newValue[4]), &h, 4); @@ -111,12 +88,12 @@ void Channel::setNewValue(_supla_int64_t value) { } } -void Channel::setNewValue(int value) { +void Channel::setNewValue(_supla_int_t value) { char newValue[SUPLA_CHANNELVALUE_SIZE]; memset(newValue, 0, SUPLA_CHANNELVALUE_SIZE); - memcpy(newValue, &value, sizeof(int)); + memcpy(newValue, &value, sizeof(value)); if (setNewValue(newValue)) { supla_log( LOG_DEBUG, "Channel(%d) value changed to %d", channelNumber, value); @@ -235,18 +212,6 @@ TSuplaChannelExtendedValue *Channel::getExtValue() { return nullptr; } -void Channel::clearAllUpdateReady() { - for (auto channel = begin(); channel != nullptr; channel = channel->next()) { - if (!channel->isExtended()) { - channel->clearUpdateReady(); - } - } -} - -Channel *Channel::next() { - return nextPtr; -} - void Channel::setUpdateReady() { valueChanged = true; }; @@ -283,4 +248,66 @@ _supla_int_t Channel::getChannelType() { return -1; } +double Channel::getValueDouble() { + double value; + if (sizeof(double) == 8) { + memcpy(&value, reg_dev.channels[channelNumber].value, 8); + } else if (sizeof(double) == 4) { + value = doublePacked2float((uint8_t *)(reg_dev.channels[channelNumber].value)); + } + + return value; +} + +double Channel::getValueDoubleFirst() { + _supla_int_t value; + memcpy(&value, reg_dev.channels[channelNumber].value, 4); + + return value / 1000.0; +} + +double Channel::getValueDoubleSecond() { + _supla_int_t value; + memcpy(&value, &(reg_dev.channels[channelNumber].value[4]), 4); + + return value / 1000.0; +} + +_supla_int_t Channel::getValueInt32() { + _supla_int_t value; + memcpy(&value, reg_dev.channels[channelNumber].value, sizeof(value)); + return value; +} + +_supla_int64_t Channel::getValueInt64() { + _supla_int64_t value; + memcpy(&value, reg_dev.channels[channelNumber].value, sizeof(value)); + return value; +} + +bool Channel::getValueBool() { + return reg_dev.channels[channelNumber].value[0]; +} + +uint8_t Channel::getValueRed() { + return reg_dev.channels[channelNumber].value[4]; +} + +uint8_t Channel::getValueGreen() { + return reg_dev.channels[channelNumber].value[3]; +} + +uint8_t Channel::getValueBlue() { + return reg_dev.channels[channelNumber].value[2]; +} + +uint8_t Channel::getValueColorBrightness() { + return reg_dev.channels[channelNumber].value[1]; +} + +uint8_t Channel::getValueBrightness() { + return reg_dev.channels[channelNumber].value[0]; +} + + }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/channel.h b/lib/SuplaDevice/src/supla/channel.h index c27b68a5..a91d347f 100644 --- a/lib/SuplaDevice/src/supla/channel.h +++ b/lib/SuplaDevice/src/supla/channel.h @@ -26,14 +26,11 @@ namespace Supla { class Channel { public: Channel(); - - static Channel *begin(); - static Channel *last(); - static int size(); + ~Channel(); void setNewValue(double dbl); void setNewValue(double temp, double humi); - void setNewValue(int value); + void setNewValue(_supla_int_t value); void setNewValue(bool value); void setNewValue(TElectricityMeter_ExtendedValue_V2 &emValue); void setNewValue(uint8_t red, @@ -44,6 +41,18 @@ class Channel { void setNewValue(_supla_int64_t value); bool setNewValue(char *newValue); + double getValueDouble(); + double getValueDoubleFirst(); + double getValueDoubleSecond(); + _supla_int_t getValueInt32(); + _supla_int64_t getValueInt64(); + bool getValueBool(); + uint8_t getValueRed(); + uint8_t getValueGreen(); + uint8_t getValueBlue(); + uint8_t getValueColorBrightness(); + uint8_t getValueBrightness(); + virtual bool isExtended(); bool isUpdateReady(); int getChannelNumber(); @@ -57,8 +66,6 @@ class Channel { void clearUpdateReady(); void sendUpdate(void *srpc); virtual TSuplaChannelExtendedValue *getExtValue(); - static void clearAllUpdateReady(); - Channel *next(); static unsigned long lastCommunicationTimeMs; static TDS_SuplaRegisterDevice_E reg_dev; @@ -69,8 +76,6 @@ class Channel { bool valueChanged; int channelNumber; - Channel *nextPtr; - static Channel *firstPtr; }; }; // namespace Supla diff --git a/lib/SuplaDevice/src/supla/control/relay.h b/lib/SuplaDevice/src/supla/control/relay.h index f7a8813f..ce9effb9 100644 --- a/lib/SuplaDevice/src/supla/control/relay.h +++ b/lib/SuplaDevice/src/supla/control/relay.h @@ -66,8 +66,9 @@ class Relay : public Element, public Triggerable { void iterateAlways(); int handleNewValueFromServer(TSD_SuplaChannelNewValue *newValue); - protected: Channel *getChannel(); + + protected: Channel channel; int pin; bool highIsOn; diff --git a/lib/SuplaDevice/src/supla/control/rgbw_base.h b/lib/SuplaDevice/src/supla/control/rgbw_base.h index 452a66cf..17f733d8 100644 --- a/lib/SuplaDevice/src/supla/control/rgbw_base.h +++ b/lib/SuplaDevice/src/supla/control/rgbw_base.h @@ -20,10 +20,10 @@ #include #include +#include "../actions.h" #include "../channel.h" #include "../element.h" #include "../triggerable.h" -#include "../actions.h" namespace Supla { namespace Control { @@ -50,15 +50,16 @@ class RGBWBase : public Element, public Triggerable { void setFadeEffectTime(int timeMs); void onTimer(); -void onInit() { + void onInit() { + // Send to Supla server new values + channel.setNewValue( + curRed, curGreen, curBlue, curColorBrightness, curBrightness); + } + + Channel *getChannel(); - // Send to Supla server new values - channel.setNewValue( - curRed, curGreen, curBlue, curColorBrightness, curBrightness); -} protected: uint8_t addWithLimit(int value, int addition, int limit = 255); - Channel *getChannel(); void iterateDimmerRGBW(int rgbStep, int wStep); Channel channel; @@ -74,13 +75,12 @@ void onInit() { bool dimIterationDirection; int iterationDelayCounter; int fadeEffect; - int hwRed; // 0 - 255 - int hwGreen; // 0 - 255 - int hwBlue; // 0 - 255 - int hwColorBrightness; // 0 - 100 - int hwBrightness; // 0 - 100 + int hwRed; // 0 - 255 + int hwGreen; // 0 - 255 + int hwBlue; // 0 - 255 + int hwColorBrightness; // 0 - 100 + int hwBrightness; // 0 - 100 unsigned long lastTick; - }; }; // namespace Control diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp index 12e91b88..56102b77 100644 --- a/lib/SuplaDevice/src/supla/control/roller_shutter.cpp +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.cpp @@ -14,9 +14,10 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include "roller_shutter.h" #include +#include "roller_shutter.h" + namespace Supla { namespace Control { @@ -53,8 +54,10 @@ RollerShutter::RollerShutter(int pinUp, int pinDown, bool highIsOn) } void RollerShutter::onInit() { - Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); - Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); Supla::Io::pinMode(channel.getChannelNumber(), pinUp, OUTPUT); Supla::Io::pinMode(channel.getChannelNumber(), pinDown, OUTPUT); } @@ -273,19 +276,23 @@ void RollerShutter::stopMovement() { } void RollerShutter::relayDownOn() { - Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinDown, highIsOn ? HIGH : LOW); } void RollerShutter::relayUpOn() { - Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? HIGH : LOW); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinUp, highIsOn ? HIGH : LOW); } void RollerShutter::relayDownOff() { - Supla::Io::digitalWrite(channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinDown, highIsOn ? LOW : HIGH); } void RollerShutter::relayUpOff() { - Supla::Io::digitalWrite(channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); + Supla::Io::digitalWrite( + channel.getChannelNumber(), pinUp, highIsOn ? LOW : HIGH); } void RollerShutter::startClosing() { @@ -455,10 +462,10 @@ void RollerShutter::onTimer() { } // if (newCurrentPosition != currentPosition) { // currentPosition = newCurrentPosition; - channel.setNewValue( - currentPosition); // value set on channel will be send to server - // during iterateConnected() execution - // } + channel.setNewValue(static_cast<_supla_int_t>( + currentPosition)); // value set on channel will be send to server + // during iterateConnected() execution + // } } Channel *RollerShutter::getChannel() { diff --git a/lib/SuplaDevice/src/supla/control/roller_shutter.h b/lib/SuplaDevice/src/supla/control/roller_shutter.h index 26e46f81..716a121e 100644 --- a/lib/SuplaDevice/src/supla/control/roller_shutter.h +++ b/lib/SuplaDevice/src/supla/control/roller_shutter.h @@ -59,6 +59,8 @@ class RollerShutter : public Element, public Triggerable { void onLoadState(); void onSaveState(); + Channel *getChannel(); + protected: virtual void stopMovement(); virtual void relayDownOn(); @@ -74,8 +76,6 @@ class RollerShutter : public Element, public Triggerable { bool lastDirectionWasClose(); bool inMove(); - Channel *getChannel(); - Channel channel; uint32_t closingTimeMs; diff --git a/lib/SuplaDevice/src/supla/element.cpp b/lib/SuplaDevice/src/supla/element.cpp index b7f1b22c..ac879eb5 100644 --- a/lib/SuplaDevice/src/supla/element.cpp +++ b/lib/SuplaDevice/src/supla/element.cpp @@ -95,6 +95,10 @@ Channel *Element::getChannel() { return nullptr; } +Channel *Element::getSecondaryChannel() { + return nullptr; +} + void Element::handleGetChannelState(TDSC_ChannelState &channelState) { (void)(channelState); return; diff --git a/lib/SuplaDevice/src/supla/element.h b/lib/SuplaDevice/src/supla/element.h index f2ee9fdc..82f76341 100644 --- a/lib/SuplaDevice/src/supla/element.h +++ b/lib/SuplaDevice/src/supla/element.h @@ -76,11 +76,12 @@ class Element { virtual int handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request); int getChannelNumber(); + virtual Channel *getChannel(); + virtual Channel *getSecondaryChannel(); Element &disableChannelState(); protected: - virtual Channel *getChannel(); static Element *firstPtr; Element *nextPtr; }; diff --git a/lib/SuplaDevice/src/supla/sensor/binary.h b/lib/SuplaDevice/src/supla/sensor/binary.h index 31db9fff..4e5e41b9 100644 --- a/lib/SuplaDevice/src/supla/sensor/binary.h +++ b/lib/SuplaDevice/src/supla/sensor/binary.h @@ -30,9 +30,9 @@ class Binary : public Element { bool getValue(); void iterateAlways(); void onInit(); + Channel *getChannel(); protected: - Channel *getChannel(); Channel channel; int pin; diff --git a/lib/SuplaDevice/src/supla/sensor/distance.h b/lib/SuplaDevice/src/supla/sensor/distance.h index 953078a1..a24a8c74 100644 --- a/lib/SuplaDevice/src/supla/sensor/distance.h +++ b/lib/SuplaDevice/src/supla/sensor/distance.h @@ -20,7 +20,7 @@ #include "supla/channel.h" #include "supla/element.h" -#define DISTANCE_NOT_AVAILABLE -1 +#define DISTANCE_NOT_AVAILABLE -1.0 namespace Supla { namespace Sensor { @@ -43,10 +43,11 @@ class Distance : public Element { } } - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h index 713ea99c..e67bd517 100644 --- a/lib/SuplaDevice/src/supla/sensor/electricity_meter.h +++ b/lib/SuplaDevice/src/supla/sensor/electricity_meter.h @@ -92,9 +92,9 @@ class ElectricityMeter : public Element { void setResreshRate(unsigned int sec); - protected: Channel *getChannel(); + protected: TElectricityMeter_ExtendedValue_V2 emValue; ChannelExtended extChannel; unsigned _supla_int_t rawCurrent[MAX_PHASES]; diff --git a/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h b/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h index 6c494824..ec5e5661 100644 --- a/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h +++ b/lib/SuplaDevice/src/supla/sensor/general_purpose_measurement_base.h @@ -37,10 +37,11 @@ class GeneralPurposeMeasurementBase : public Element { } } - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/impulse_counter.h b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h index 6d6da313..eed631c3 100644 --- a/lib/SuplaDevice/src/supla/sensor/impulse_counter.h +++ b/lib/SuplaDevice/src/supla/sensor/impulse_counter.h @@ -46,6 +46,8 @@ class ImpulseCounter : public Element, public Triggerable { // Increment the counter by 1 void incCounter(); + Channel *getChannel(); + protected: int prevState; // Store previous state of pin (LOW/HIGH). It is used to track // changes on pin state. @@ -61,7 +63,6 @@ class ImpulseCounter : public Element, public Triggerable { unsigned _supla_int64_t counter; // Actual count of impulses - Channel *getChannel(); Channel channel; }; diff --git a/lib/SuplaDevice/src/supla/sensor/pressure.h b/lib/SuplaDevice/src/supla/sensor/pressure.h index d320fb33..cdb9a77d 100644 --- a/lib/SuplaDevice/src/supla/sensor/pressure.h +++ b/lib/SuplaDevice/src/supla/sensor/pressure.h @@ -43,11 +43,11 @@ class Pressure : public Element { } } - - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/rain.h b/lib/SuplaDevice/src/supla/sensor/rain.h index dd060d27..40300bc4 100644 --- a/lib/SuplaDevice/src/supla/sensor/rain.h +++ b/lib/SuplaDevice/src/supla/sensor/rain.h @@ -43,11 +43,11 @@ class Rain: public Element { } } - - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.cpp b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.cpp new file mode 100644 index 00000000..b9604cfd --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.cpp @@ -0,0 +1,37 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "therm_hygro_meter.h" + +Supla::Sensor::ThermHygroMeter::ThermHygroMeter() { + channel.setType(SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR); + channel.setDefault(SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE); +} + +double Supla::Sensor::ThermHygroMeter::getTemp() { + return TEMPERATURE_NOT_AVAILABLE; +} + +double Supla::Sensor::ThermHygroMeter::getHumi() { + return HUMIDITY_NOT_AVAILABLE; +} + +void Supla::Sensor::ThermHygroMeter::iterateAlways() { + if (millis() - lastReadTime > 10000) { + lastReadTime = millis(); + channel.setNewValue(getTemp(), getHumi()); + } +} diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h index 3e9f019a..ab79fea1 100644 --- a/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_meter.h @@ -25,27 +25,11 @@ namespace Supla { namespace Sensor { class ThermHygroMeter : public Thermometer { public: - ThermHygroMeter() { - channel.setType(SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR); - channel.setDefault(SUPLA_CHANNELFNC_HUMIDITYANDTEMPERATURE); - } - - virtual double getTemp() { - return TEMPERATURE_NOT_AVAILABLE; - } - - virtual double getHumi() { - return HUMIDITY_NOT_AVAILABLE; - } - - void iterateAlways() { - if (millis() - lastReadTime > 10000) { - lastReadTime = millis(); - channel.setNewValue(getTemp(), getHumi()); - } - } - - protected: + ThermHygroMeter(); + virtual double getTemp(); + virtual double getHumi(); + void iterateAlways(); + }; }; // namespace Sensor diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.cpp b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.cpp new file mode 100644 index 00000000..409fc14c --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.cpp @@ -0,0 +1,57 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "therm_hygro_press_meter.h" + +Supla::Sensor::ThermHygroPressMeter::ThermHygroPressMeter() { + pressureChannel.setType(SUPLA_CHANNELTYPE_PRESSURESENSOR); + pressureChannel.setDefault(SUPLA_CHANNELFNC_PRESSURESENSOR); +} + +double Supla::Sensor::ThermHygroPressMeter::getPressure() { + return PRESSURE_NOT_AVAILABLE; +} + +void Supla::Sensor::ThermHygroPressMeter::iterateAlways() { + if (millis() - lastReadTime > 10000) { + pressureChannel.setNewValue(getPressure()); + } + ThermHygroMeter::iterateAlways(); +} + +bool Supla::Sensor::ThermHygroPressMeter::iterateConnected(void *srpc) { + bool response = true; + if (pressureChannel.isUpdateReady() && + millis() - pressureChannel.lastCommunicationTimeMs > 100) { + pressureChannel.lastCommunicationTimeMs = millis(); + pressureChannel.sendUpdate(srpc); + response = false; + } + + if (!Element::iterateConnected(srpc)) { + response = false; + } + return response; +} + +Supla::Element &Supla::Sensor::ThermHygroPressMeter::disableChannelState() { + pressureChannel.unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); + return ThermHygroMeter::disableChannelState(); +} + +Supla::Channel *Supla::Sensor::ThermHygroPressMeter::getSecondaryChannel() { + return &pressureChannel; +} diff --git a/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h index 55441650..77d1156a 100644 --- a/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h +++ b/lib/SuplaDevice/src/supla/sensor/therm_hygro_press_meter.h @@ -25,41 +25,12 @@ namespace Supla { namespace Sensor { class ThermHygroPressMeter : public ThermHygroMeter { public: - ThermHygroPressMeter() { - pressureChannel.setType(SUPLA_CHANNELTYPE_PRESSURESENSOR); - pressureChannel.setDefault(SUPLA_CHANNELFNC_PRESSURESENSOR); - } - - virtual double getPressure() { - return PRESSURE_NOT_AVAILABLE; - } - - void iterateAlways() { - if (millis() - lastReadTime > 10000) { - pressureChannel.setNewValue(getPressure()); - } - ThermHygroMeter::iterateAlways(); - } - - bool iterateConnected(void *srpc) { - bool response = true; - if (pressureChannel.isUpdateReady() && - millis() - pressureChannel.lastCommunicationTimeMs > 100) { - pressureChannel.lastCommunicationTimeMs = millis(); - pressureChannel.sendUpdate(srpc); - response = false; - } - - if (!Element::iterateConnected(srpc)) { - response = false; - } - return response; - } - - Element &disableChannelState() { - pressureChannel.unsetFlag(SUPLA_CHANNEL_FLAG_CHANNELSTATE); - return ThermHygroMeter::disableChannelState(); - } + ThermHygroPressMeter(); + virtual double getPressure(); + void iterateAlways(); + bool iterateConnected(void *srpc); + Element &disableChannelState(); + Channel *getSecondaryChannel(); protected: Channel pressureChannel; diff --git a/lib/SuplaDevice/src/supla/sensor/thermometer.cpp b/lib/SuplaDevice/src/supla/sensor/thermometer.cpp new file mode 100644 index 00000000..9525fb91 --- /dev/null +++ b/lib/SuplaDevice/src/supla/sensor/thermometer.cpp @@ -0,0 +1,37 @@ +/* + Copyright (C) AC SOFTWARE SP. Z O.O. + + 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 2 + 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 should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "thermometer.h" + +Supla::Sensor::Thermometer::Thermometer() : lastReadTime(0) { + channel.setType(SUPLA_CHANNELTYPE_THERMOMETER); + channel.setDefault(SUPLA_CHANNELFNC_THERMOMETER); +} + +double Supla::Sensor::Thermometer::getValue() { + return TEMPERATURE_NOT_AVAILABLE; +} + +void Supla::Sensor::Thermometer::iterateAlways() { + if (lastReadTime + 10000 < millis()) { + lastReadTime = millis(); + channel.setNewValue(getValue()); + } +} + +Supla::Channel *Supla::Sensor::Thermometer::getChannel() { + return &channel; +} diff --git a/lib/SuplaDevice/src/supla/sensor/thermometer.h b/lib/SuplaDevice/src/supla/sensor/thermometer.h index 8dd11942..71f810fb 100644 --- a/lib/SuplaDevice/src/supla/sensor/thermometer.h +++ b/lib/SuplaDevice/src/supla/sensor/thermometer.h @@ -17,6 +17,7 @@ #ifndef _thermometer_h #define _thermometer_h +#include #include "supla/channel.h" #include "supla/element.h" @@ -26,26 +27,13 @@ namespace Supla { namespace Sensor { class Thermometer : public Element { public: - Thermometer() : lastReadTime(0) { - channel.setType(SUPLA_CHANNELTYPE_THERMOMETER); - channel.setDefault(SUPLA_CHANNELFNC_THERMOMETER); - } - - virtual double getValue() { - return TEMPERATURE_NOT_AVAILABLE; - } - - void iterateAlways() { - if (lastReadTime + 10000 < millis()) { - lastReadTime = millis(); - channel.setNewValue(getValue()); - } - } + Thermometer(); + virtual double getValue(); + void iterateAlways(); + + Channel *getChannel(); protected: - Channel *getChannel() { - return &channel; - } Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/virtual_binary.h b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h index 5c30c5f6..62d39b85 100644 --- a/lib/SuplaDevice/src/supla/sensor/virtual_binary.h +++ b/lib/SuplaDevice/src/supla/sensor/virtual_binary.h @@ -33,9 +33,9 @@ class VirtualBinary : public Element, public Triggerable { void iterateAlways(); void onInit(); void runAction(int event, int action); + Channel *getChannel(); protected: - Channel *getChannel(); Channel channel; bool state; unsigned long lastReadTime; diff --git a/lib/SuplaDevice/src/supla/sensor/weight.h b/lib/SuplaDevice/src/supla/sensor/weight.h index 8c318cbd..c5b8f8c4 100644 --- a/lib/SuplaDevice/src/supla/sensor/weight.h +++ b/lib/SuplaDevice/src/supla/sensor/weight.h @@ -43,11 +43,11 @@ class Weight : public Element { } } - - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/sensor/wind.h b/lib/SuplaDevice/src/supla/sensor/wind.h index 6869bc83..49b38afb 100644 --- a/lib/SuplaDevice/src/supla/sensor/wind.h +++ b/lib/SuplaDevice/src/supla/sensor/wind.h @@ -43,11 +43,11 @@ class Wind: public Element { } } - - protected: Channel *getChannel() { return &channel; } + + protected: Channel channel; unsigned long lastReadTime; }; diff --git a/lib/SuplaDevice/src/supla/tools.cpp b/lib/SuplaDevice/src/supla/tools.cpp index a4c9038c..f053ad41 100644 --- a/lib/SuplaDevice/src/supla/tools.cpp +++ b/lib/SuplaDevice/src/supla/tools.cpp @@ -41,3 +41,16 @@ void float2DoublePacked(float number, uint8_t *bar, int byteOrder) { } #endif } + +float doublePacked2float(uint8_t *bar) { + _FLOATCONV fl; + _DBLCONV dbl; + for (int i = 0; i < 8; i++) { + dbl.b[i] = bar[i]; + } + fl.p.s = dbl.p.s; + fl.p.m = dbl.p.m; + fl.p.e = dbl.p.e + 127 - 1023; // exponent adjust + + return fl.f; +} diff --git a/lib/SuplaDevice/src/supla/tools.h b/lib/SuplaDevice/src/supla/tools.h index 0a2c2a6d..ab2ac31f 100644 --- a/lib/SuplaDevice/src/supla/tools.h +++ b/lib/SuplaDevice/src/supla/tools.h @@ -19,9 +19,11 @@ #ifndef _tools_H_ #define _tools_H_ +#include #include "supla-common/IEEE754tools.h" void float2DoublePacked(float number, uint8_t *bar, int byteOrder = LSBFIRST); +float doublePacked2float(uint8_t *bar); #endif From 758093613b38a6e7aa6e9fabb7f0d0f76c9d3a51 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 14 Jan 2021 08:30:00 +0100 Subject: [PATCH 101/233] wsparcie dla MCP23017 --- platformio.ini | 16 +- src/GUI-Generic.ino | 33 +++-- src/GUI-Generic_Config.h | 1 + src/GUIGenericCommon.h | 2 +- src/Markup.cpp | 96 +++++++++--- src/Markup.h | 8 +- src/SuplaCommonPROGMEM.h | 36 ++++- src/SuplaConfigESP.cpp | 266 +++++++++++++++++++++++++++++++--- src/SuplaConfigESP.h | 32 +++- src/SuplaConfigManager.cpp | 33 ++--- src/SuplaConfigManager.h | 12 +- src/SuplaDeviceGUI.cpp | 2 +- src/SuplaDeviceGUI.h | 6 +- src/SuplaHTTPUpdateServer.cpp | 2 +- src/SuplaTemplateBoard.cpp | 4 + src/SuplaWebPageControl.cpp | 216 ++++++++++++--------------- src/SuplaWebPageControl.h | 31 ++-- src/SuplaWebPageRelay.cpp | 217 ++++++++++++--------------- src/SuplaWebPageRelay.h | 30 ++-- src/SuplaWebPageSensor.cpp | 88 ++++++----- src/SuplaWebPageSensor.h | 20 +-- src/SuplaWebServer.cpp | 126 ++++++++++++++-- src/SuplaWebServer.h | 33 ++++- 23 files changed, 883 insertions(+), 427 deletions(-) diff --git a/platformio.ini b/platformio.ini index 36bfbc41..9f97ff4b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.6"' +build_flags = -D BUILD_VERSION='"GUI 1.1.8"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -43,7 +43,8 @@ build_flags = -D BUILD_VERSION='"GUI 1.1.6"' -D SUPLA_HC_SR04 -D SUPLA_IMPULSE_COUNTER -D SUPLA_OLED - -D SUPLA_HLW8012 + -D SUPLA_HLW8012 + -D SUPLA_MCP23017 [env] framework = arduino @@ -52,6 +53,8 @@ upload_speed = 256000 monitor_speed = 74880 upload_resetmethod = nodemcu board_build.flash_mode = dout +; set frequency to 160MHz +board_build.f_cpu = 160000000L lib_deps = milesburton/DallasTemperature@^3.9.1 adafruit/DHT sensor library@^1.4.0 @@ -123,7 +126,7 @@ board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld build_flags = ${common.build_flags} -D DEBUG_MODE -build_unflags = -DUSE_CUSTOM +;build_unflags = -D SUPLA_IMPULSE_COUNTER [env:GUI_Generic_blank] board = nodemcuv2 @@ -145,4 +148,9 @@ build_unflags = -D SUPLA_OTA -D SUPLA_MAX6675 -D SUPLA_HC_SR04 -D SUPLA_IMPULSE_COUNTER - \ No newline at end of file + +[env:GUI_Generic_MCP23017] +board = esp8285 +board_build.ldscript = eagle.flash.1m64.ld +build_flags = ${common.build_flags} +build_unflags = -D SUPLA_IMPULSE_COUNTER \ No newline at end of file diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 70fd892b..10f3ae50 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -15,6 +15,8 @@ */ #include "SuplaDeviceGUI.h" +#include + #define DRD_TIMEOUT 5 // Number of seconds after reset during which a subseqent reset will be considered a double reset. #define DRD_ADDRESS 0 // RTC Memory Address for the DoubleResetDetector to use DoubleResetDetector drd(DRD_TIMEOUT, DRD_ADDRESS); @@ -32,8 +34,8 @@ void setup() { #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) uint8_t rollershutters = ConfigManager->get(KEY_MAX_ROLLERSHUTTER)->getValueInt(); - if (ConfigESP->getGpio(FUNCTION_RELAY) != OFF_GPIO && ConfigManager->get(KEY_MAX_RELAY)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { + if (ConfigESP->getGpio(nr, FUNCTION_RELAY) != OFF_GPIO) { #ifdef SUPLA_ROLLERSHUTTER if (rollershutters > 0) { #ifdef SUPLA_BUTTON @@ -65,8 +67,8 @@ void setup() { #endif #ifdef SUPLA_LIMIT_SWITCH - if (ConfigESP->getGpio(FUNCTION_LIMIT_SWITCH) != OFF_GPIO && ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { + if (ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH) != OFF_GPIO) { new Supla::Sensor::Binary(ConfigESP->getGpio(nr, FUNCTION_LIMIT_SWITCH), true); } } @@ -78,16 +80,16 @@ void setup() { #endif #ifdef SUPLA_DHT11 - if (ConfigESP->getGpio(FUNCTION_DHT11) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT11)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { + if (ConfigESP->getGpio(nr, FUNCTION_DHT11) != OFF_GPIO) { new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT11), DHT11); } } #endif #ifdef SUPLA_DHT22 - if (ConfigESP->getGpio(FUNCTION_DHT22) != OFF_GPIO && ConfigManager->get(KEY_MAX_DHT22)->getValueInt() > 0) { - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { + if (ConfigESP->getGpio(nr, FUNCTION_DHT22) != OFF_GPIO) { Supla::GUI::sensorDHT22.push_back(new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22)); } } @@ -112,7 +114,7 @@ void setup() { #endif #if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ - defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) + defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) || defined(SUPLA_MCP23017) if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); #ifdef SUPLA_BME280 @@ -157,6 +159,17 @@ void setup() { Supla::GUI::oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); } #endif + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt()) { + if (!mcp1.begin(0)) + Serial.println(F("MCP23017 1 not found!")); // begin(uint8_t address) "Pin 100 - 115" + if (!mcp2.begin(1)) + Serial.println(F("MCP23017 2 not found!")); // begin(uint8_t address) "Pin 116 - 131" + if (!mcp3.begin(2)) + Serial.println(F("MCP23017 3 not found!")); // begin(uint8_t address) "Pin 132 - 147" + if (!mcp4.begin(3)) + Serial.println(F("MCP23017 4 not found!")); // begin(uint8_t address) "Pin 148 - 163" + } } #endif @@ -170,7 +183,7 @@ void setup() { #ifdef SUPLA_IMPULSE_COUNTER if (ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt() > 0) { for (nr = 1; nr <= ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt(); nr++) { - if (ConfigESP->getGpio(FUNCTION_IMPULSE_COUNTER) != OFF_GPIO) { + if (ConfigESP->getGpio(nr, FUNCTION_IMPULSE_COUNTER) != OFF_GPIO) { Supla::GUI::addImpulseCounter(ConfigESP->getGpio(nr, FUNCTION_IMPULSE_COUNTER), ConfigESP->getLevel(nr, FUNCTION_IMPULSE_COUNTER), ConfigESP->getMemory(nr, FUNCTION_IMPULSE_COUNTER), ConfigManager->get(KEY_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT)->getValueInt()); diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index 70aa3273..c3d7cb17 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -36,6 +36,7 @@ #define SUPLA_SHT3x #define SUPLA_SI7021 #define SUPLA_OLED +//#define SUPLA_MCP23017 // #define SUPLA_HTU21D // 0x40 NOT SUPPORTED // #define SUPLA_SHT71 // 0x44 AND 0x45 NOT SUPPORTED // #define SUPLA_BH1750 // 0x23 AND 0x5C NOT SUPPORTED diff --git a/src/GUIGenericCommon.h b/src/GUIGenericCommon.h index 62672230..3ed890bc 100644 --- a/src/GUIGenericCommon.h +++ b/src/GUIGenericCommon.h @@ -6,7 +6,7 @@ #else #define QUOTE(x) QUOTE_1(x) #define QUOTE_1(x) #x -#define INCLUDE_FILE(x) QUOTE(language/x.h) +#define INCLUDE_FILE(x) QUOTE(language / x.h) #include INCLUDE_FILE(UI_LANGUAGE) #endif diff --git a/src/Markup.cpp b/src/Markup.cpp index afdc7cf2..fc52249e 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -138,7 +138,38 @@ void addListGPIOBox(String& html, const String& input_id, const String& name, ui html += F(" "); html += name; html += F(""); - html += addListGPIOSelect(input_id.c_str(), function, nr); + addListGPIOSelect(html, input_id, function, nr); + html += F("
"); +} + +void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const String& name, uint8_t function, const String& url, uint8_t nr) { + if (nr == 1) { + uint8_t address = ConfigESP->getAdressMCP23017(function); + addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 3, address); + } + + html += F(""); + html += F(""); + addListMCP23017GPIO(html, input_id, function, nr); html += F(""); } @@ -171,15 +202,22 @@ void addListGPIOLinkBox(String& html, const String& input_id, const String& name html += F(""); } html += F(""); - html += addListGPIOSelect(input_id.c_str(), function, nr); + addListGPIOSelect(html, input_id, function, nr); html += F("
"); } -void addListBox(String& html, const String& input_id, const String& name, const char* const* array_P, uint8_t size, uint8_t selected) { +void addListBox(String& html, const String& input_id, const String& name, const char* const* array_P, uint8_t size, uint8_t selected, uint8_t nr) { html += F(""); + html += F("'>"); uint8_t selected = ConfigESP->getGpio(nr, function); for (uint8_t suported = 0; suported < sizeof(GPIO_P) / sizeof(GPIO_P[0]); suported++) { - if (ConfigESP->checkBusyGpio(suported, function) == false || selected == suported) { - page += F("
https://forum.supla.org/\n"; -const char HTTP_RBT[] PROGMEM = - ""; +const char HTTP_FAVICON[] PROGMEM = "\n"; + +const char HTTP_RBT[] PROGMEM = "
"; const char GPIO0[] PROGMEM = "GPIO0-D3"; const char GPIO1[] PROGMEM = "GPIO1-TX"; @@ -77,6 +79,26 @@ const char GPIONULL[] PROGMEM = ""; const char* const GPIO_P[] PROGMEM = {GPIO0, GPIO1, GPIO2, GPIO3, GPIO4, GPIO5, GPIONULL, GPIONULL, GPIONULL, GPIO9, GPIO10, GPIONULL, GPIO12, GPIO13, GPIO14, GPIO15, GPIO16, OFF}; +const char GPIO_A0[] PROGMEM = "A0"; +const char GPIO_A1[] PROGMEM = "A1"; +const char GPIO_A2[] PROGMEM = "A2"; +const char GPIO_A3[] PROGMEM = "A3"; +const char GPIO_A4[] PROGMEM = "A4"; +const char GPIO_A5[] PROGMEM = "A5"; +const char GPIO_A6[] PROGMEM = "A6"; +const char GPIO_A7[] PROGMEM = "A7"; +const char GPIO_B0[] PROGMEM = "B0"; +const char GPIO_B1[] PROGMEM = "B1"; +const char GPIO_B2[] PROGMEM = "B2"; +const char GPIO_B3[] PROGMEM = "B3"; +const char GPIO_B4[] PROGMEM = "B4"; +const char GPIO_B5[] PROGMEM = "B5"; +const char GPIO_B6[] PROGMEM = "B6"; +const char GPIO_B7[] PROGMEM = "B7"; + +const char* const GPIO_MCP23017_P[] PROGMEM = {GPIO_A0, GPIO_A1, GPIO_A2, GPIO_A3, GPIO_A4, GPIO_A5, GPIO_A6, GPIO_A7, GPIO_B0, + GPIO_B1, GPIO_B2, GPIO_B3, GPIO_B4, GPIO_B5, GPIO_B6, GPIO_B7, GPIONULL, OFF}; + const char ADR44[] PROGMEM = "0x44"; const char ADR45[] PROGMEM = "0x45"; const char ADR44_ADR45[] PROGMEM = "0x44 & 0x45"; @@ -87,6 +109,10 @@ const char ADR76_ADR77[] PROGMEM = "0x76 & 0x77"; const char* const BME280_P[] PROGMEM = {OFF, ADR76, ADR77, ADR76_ADR77}; const char* const SHT3x_P[] PROGMEM = {OFF, ADR44, ADR45, ADR44_ADR45}; +const char ADR20[] PROGMEM = "0x20"; +const char ADR21[] PROGMEM = "0x21"; +const char* const MCP23017_P[] PROGMEM = {ADR20, ADR21, OFF}; + const char* const STATE_P[] PROGMEM = {OFF, ON}; const char LOW_STATE_CONTROL[] PROGMEM = S_LOW; @@ -112,7 +138,7 @@ const char* const CFG_MODE_P[] PROGMEM = {CFG_10_PRESSES, CFG_5SEK_HOLD}; const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; const char SH1106[] PROGMEM = "SH1106 - 1,3''"; const char SSD1306_WEMOS_SHIELD[] PROGMEM = "SSD1306 - 0,66'' WEMOS OLED shield"; -const char *const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106, SSD1306_WEMOS_SHIELD}; +const char* const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106, SSD1306_WEMOS_SHIELD}; #endif String StateString(uint8_t adr); diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 978c3738..fd04ca0c 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -20,6 +20,7 @@ #include "SuplaConfigManager.h" #include "SuplaDeviceGUI.h" #include "GUIGenericCommon.h" +#include "SuplaWebPageSensor.h" SuplaConfigESP::SuplaConfigESP() { configModeESP = NORMAL_MODE; @@ -95,11 +96,7 @@ void SuplaConfigESP::runAction(int event, int action) { } void SuplaConfigESP::rebootESP() { - delay(1000); - WiFi.forceSleepBegin(); - wdt_reset(); ESP.restart(); - while (1) wdt_reset(); } void SuplaConfigESP::configModeInit() { @@ -117,8 +114,9 @@ void SuplaConfigESP::iterateAlways() { } } -String SuplaConfigESP::getConfigNameAP() { - return "SUPLA-ESP8266-" + getMacAddress(false); +const String SuplaConfigESP::getConfigNameAP() { + String name = F("SUPLA-ESP8266-"); + return name += getMacAddress(false); } const char *SuplaConfigESP::getLastStatusSupla() { return supla_status.msg; @@ -251,6 +249,25 @@ int SuplaConfigESP::getGpio(int nr, int function) { return gpio; } } + // return OFF_GPIO; + //"Pin 100 - 115" + // Pin 116 - 131" + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt()) { + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function && + ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { + return gpio + 100; + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function && + ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { + return gpio + 100 + 16; + } + break; + } + } } return OFF_GPIO; } @@ -260,10 +277,28 @@ int SuplaConfigESP::getLevel(int nr, int function) { uint8_t key = KEY_GPIO + gpio; if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == function) { if (ConfigManager->get(key)->getElement(NR).toInt() == nr) { - uint8_t level = ConfigManager->get(key)->getElement(LEVEL).toInt(); - return level; + return ConfigManager->get(key)->getElement(LEVEL).toInt(); } } + + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { + return ConfigManager->get(key)->getElement(LEVEL).toInt(); + ; + } + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { + return ConfigManager->get(key)->getElement(LEVEL).toInt(); + ; + } + } + break; + } } return OFF_GPIO; } @@ -273,10 +308,28 @@ int SuplaConfigESP::getMemory(int nr, int function) { uint8_t key = KEY_GPIO + gpio; if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == function) { if (ConfigManager->get(key)->getElement(NR).toInt() == nr) { - uint8_t level = ConfigManager->get(key)->getElement(MEMORY).toInt(); - return level; + return ConfigManager->get(key)->getElement(MEMORY).toInt(); } } + + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { + return ConfigManager->get(key)->getElement(MEMORY).toInt(); + ; + } + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { + return ConfigManager->get(key)->getElement(MEMORY).toInt(); + ; + } + } + break; + } } return OFF_GPIO; } @@ -286,10 +339,29 @@ int SuplaConfigESP::getAction(int nr, int function) { uint8_t key = KEY_GPIO + gpio; if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == function) { if (ConfigManager->get(key)->getElement(NR).toInt() == nr) { - uint8_t action = ConfigManager->get(key)->getElement(ACTION).toInt(); - return action; + return ConfigManager->get(key)->getElement(ACTION).toInt(); + ; } } + + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { + return ConfigManager->get(key)->getElement(ACTION).toInt(); + ; + } + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { + return ConfigManager->get(key)->getElement(ACTION).toInt(); + ; + } + } + break; + } } return OFF_GPIO; } @@ -304,7 +376,7 @@ bool SuplaConfigESP::checkBusyCfg(int gpio) { int SuplaConfigESP::checkBusyGpio(int gpio, int function) { if (gpio == 6 || gpio == 7 || gpio == 8 || gpio == 11) { - return true; + return false; } else { uint8_t key = KEY_GPIO + gpio; @@ -316,15 +388,15 @@ int SuplaConfigESP::checkBusyGpio(int gpio, int function) { } if (checkBusyCfg(gpio)) { if (function != FUNCTION_BUTTON) { - return true; + return false; } } if (ConfigManager->get(key)->getElement(FUNCTION).toInt() != FUNCTION_OFF) { if (ConfigManager->get(key)->getElement(FUNCTION).toInt() != function) { - return true; + return false; } } - return false; + return true; } } @@ -356,7 +428,7 @@ void SuplaConfigESP::clearGpio(uint8_t gpio, uint8_t function) { ConfigManager->setElement(key, NR, 0); ConfigManager->setElement(key, FUNCTION, FUNCTION_OFF); ConfigManager->setElement(key, LEVEL, 0); - ConfigManager->setElement(key, MEMORY, 0); + ConfigManager->setElement(key, MEMORY, 2); ConfigManager->setElement(key, ACTION, Supla::TOGGLE); } @@ -374,10 +446,148 @@ uint8_t SuplaConfigESP::countFreeGpio(uint8_t exception) { return count; } +bool SuplaConfigESP::checkBusyGpioMCP23017(uint8_t gpio, uint8_t function) { + if (gpio == OFF_GPIO) { + return true; + } + else if (gpio == 16) { + return false; + } + else { + uint8_t key = KEY_GPIO + gpio; + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() != FUNCTION_OFF) { + return false; + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() != FUNCTION_OFF) { + return false; + } + break; + } + } + return true; +} + +uint8_t SuplaConfigESP::getGpioMCP23017(uint8_t nr, uint8_t function) { + for (uint8_t gpio = 0; gpio <= OFF_GPIO; gpio++) { + uint8_t key = KEY_GPIO + gpio; + + switch (getAdressMCP23017(function)) { + case 0: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { + return gpio; + } + } + break; + case 1: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { + return gpio; + } + } + break; + } + } + return OFF_GPIO; +} + +uint8_t SuplaConfigESP::getAdressMCP23017(uint8_t function) { + for (uint8_t gpio = 0; gpio <= OFF_GPIO; gpio++) { + uint8_t key = KEY_GPIO + gpio; + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() != 0) { + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { + return 0; + } + } + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() != 0) { + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { + return 1; + } + } + } + return 2; +} + +void SuplaConfigESP::setGpioMCP23017(uint8_t gpio, uint8_t adress, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory) { + uint8_t key = KEY_GPIO + gpio; + + uint8_t _gpio = ConfigESP->getGpioMCP23017(nr, function); + ConfigESP->clearGpioMCP23017(_gpio, function); + + if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == FUNCTION_OFF) { + ConfigManager->setElement(key, LEVEL, 0); + ConfigManager->setElement(key, MEMORY, 2); + ConfigManager->setElement(key, ACTION, Supla::TOGGLE); + } + else { + ConfigManager->setElement(key, LEVEL, level); + ConfigManager->setElement(key, MEMORY, memory); + ConfigManager->setElement(key, ACTION, Supla::TOGGLE); + } + + switch (adress) { + case 0: + ConfigManager->setElement(key, MCP23017_NR_1, nr); + ConfigManager->setElement(key, MCP23017_FUNCTION_1, function); + break; + case 1: + ConfigManager->setElement(key, MCP23017_NR_2, nr); + ConfigManager->setElement(key, MCP23017_FUNCTION_2, function); + break; + } +} + +void SuplaConfigESP::clearGpioMCP23017(uint8_t gpio, uint8_t function) { + uint8_t key = KEY_GPIO + gpio; + uint8_t adress = getAdressMCP23017(function); + + ConfigManager->setElement(key, getNrMCP23017(adress), 0); + ConfigManager->setElement(key, getFunctionMCP23017(adress), FUNCTION_OFF); +} + +void SuplaConfigESP::clearFunctionGpio(uint8_t function) { + for (uint8_t gpio = 0; gpio <= OFF_GPIO; gpio++) { + uint8_t key = KEY_GPIO + gpio; + + if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == function) { + ConfigManager->setElement(key, NR, 0); + ConfigManager->setElement(key, FUNCTION, FUNCTION_OFF); + } + } +} + +uint8_t SuplaConfigESP::getFunctionMCP23017(uint8_t adress) { + switch (adress) { + case 0: + return MCP23017_FUNCTION_1; + break; + case 1: + return MCP23017_FUNCTION_2; + break; + } + return OFF_GPIO; +} + +uint8_t SuplaConfigESP::getNrMCP23017(uint8_t adress) { + switch (adress) { + case 0: + return MCP23017_NR_1; + break; + case 1: + return MCP23017_NR_2; + break; + } + return OFF_GPIO; +} + void SuplaConfigESP::factoryReset(bool forceReset) { delay(1000); - pinMode(0, INPUT); - if (!digitalRead(0) || forceReset) { + pinMode(0, INPUT_PULLUP); + if (digitalRead(0) != HIGH || forceReset) { Serial.println(F("FACTORY RESET!!!")); EEPROM.begin(1024); @@ -426,3 +636,21 @@ void SuplaConfigESP::factoryReset(bool forceReset) { // rebootESP(); } } + +uint32_t lowestRAM = 0; +uint32_t lowestFreeStack = 0; + +void checkRAM() { + uint32_t freeRAM = ESP.getFreeHeap(); + Serial.print(F("freeRAM: ")); + Serial.println(freeRAM); + if (freeRAM <= lowestRAM) { + lowestRAM = freeRAM; + } + uint32_t freeStack = ESP.getFreeContStack(); + Serial.print(F("freeStack: ")); + Serial.println(freeStack); + if (freeStack <= lowestFreeStack) { + lowestFreeStack = freeStack; + } +} diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index b96df291..7a0c9ae9 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -22,8 +22,20 @@ #include #include "GUI-Generic_Config.h" -enum _configModeESP { NORMAL_MODE, CONFIG_MODE }; -enum _ConfigMode { CONFIG_MODE_10_ON_PRESSES, CONFIG_MODE_5SEK_HOLD, FACTORYRESET }; +#include +#include + +enum _configModeESP +{ + NORMAL_MODE, + CONFIG_MODE +}; +enum _ConfigMode +{ + CONFIG_MODE_10_ON_PRESSES, + CONFIG_MODE_5SEK_HOLD, + FACTORYRESET +}; typedef struct { int status; @@ -81,7 +93,16 @@ class SuplaConfigESP : public Supla::Triggerable, public Supla::Element { int getAction(int nr, int function); void factoryReset(bool forceReset = false); - String getConfigNameAP(); + const String getConfigNameAP(); + + bool checkBusyGpioMCP23017(uint8_t gpio, uint8_t function); + uint8_t getGpioMCP23017(uint8_t nr, uint8_t function); + uint8_t getAdressMCP23017(uint8_t function); + void setGpioMCP23017(uint8_t gpio, uint8_t adress, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory); + void clearGpioMCP23017(uint8_t gpio, uint8_t function); + void clearFunctionGpio(uint8_t function); + uint8_t getFunctionMCP23017(uint8_t adress); + uint8_t getNrMCP23017(uint8_t adress); private: void configModeInit(); @@ -97,4 +118,9 @@ class SuplaConfigESP : public Supla::Triggerable, public Supla::Element { void ledBlinking_func(void *timer_arg); void status_func(int status, const char *msg); + +uint32_t getFreeStackWatermark(); +unsigned long FreeMem(); +void checkRAM(); + #endif // SuplaConfigESP_h diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 4854a66b..1b999582 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -34,8 +34,10 @@ ConfigOption::ConfigOption(uint8_t key, const char *value, int maxLength) { strncpy(_key, key, size); _key[size - 1] = '\0'; */ - _key = key; + _key = key; _maxLength = maxLength + 1; + + _value = new char[_maxLength]; setValue(value); } @@ -82,8 +84,9 @@ int ConfigOption::getLength() { return _maxLength; } -String ConfigOption::getElement(int index) { +const String ConfigOption::getElement(int index) { String data = _value; + data.reserve(_maxLength); int found = 0; int strIndex[] = {0, -1}; int maxIndex = data.length() - 1; @@ -97,8 +100,9 @@ String ConfigOption::getElement(int index) { return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } -String ConfigOption::replaceElement(int index, int newvalue) { +const String ConfigOption::replaceElement(int index, int newvalue) { String data = _value; + data.reserve(_maxLength); int lenght = SETTINGSCOUNT; String table; for (int i = 0; i <= lenght; i++) { @@ -115,16 +119,8 @@ String ConfigOption::replaceElement(int index, int newvalue) { } void ConfigOption::setValue(const char *value) { - // size_t size = _maxLength + 1; - //_value = (char *)malloc(sizeof(char) * (size)); - - // if (value != NULL) { - // memcpy(_value, value, size - 1); - // _value[size - 1] = 0; - //} if (value != NULL) { size_t size = getLength(); - _value = new char[size]; strncpy(_value, value, size); _value[size - 1] = '\0'; } @@ -162,13 +158,13 @@ SuplaConfigManager::SuplaConfigManager() { this->addKey(KEY_MAX_IMPULSE_COUNTER, "0", 2); uint8_t nr, key; - + for (nr = 0; nr <= 17; nr++) { key = KEY_GPIO + nr; - this->addKey(key, "0,0,0,0,0,0", 16); + this->addKey(key, "0,0,0,0,0,0", 28); } - - this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 14); + + this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 16); this->addKey(KEY_BOARD, "0", 2); this->addKey(KEY_CFG_MODE, "0", 2); @@ -334,7 +330,8 @@ uint8_t SuplaConfigManager::save() { if (configFile) { uint8_t *content = (uint8_t *)malloc(sizeof(uint8_t) * length); for (i = 0; i < _optionCount; i++) { - Serial.println("Save key " + String(_options[i]->getKey()) + ": " + String(_options[i]->getValue())); + Serial.printf_P(PSTR("Save key=%d"), _options[i]->getKey()); + Serial.printf_P(PSTR(" value=%s\n"), _options[i]->getValue()); memcpy(content + offset, _options[i]->getValue(), _options[i]->getLength()); offset += _options[i]->getLength(); } @@ -355,7 +352,9 @@ uint8_t SuplaConfigManager::save() { void SuplaConfigManager::showAllValue() { for (int i = 0; i < _optionCount; i++) { - Serial.println("Key: " + String(_options[i]->getKey()) + " Value: " + String(_options[i]->getValue())); + // Serial.println("Key: " + String(_options[i]->getKey()) + " Value: " + String(_options[i]->getValue())); + Serial.printf_P(PSTR("Key=%d"), _options[i]->getKey()); + Serial.printf_P(PSTR(" value=%s\n"), _options[i]->getValue()); } } diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index 73b6a35b..10eb2dde 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -86,6 +86,14 @@ enum _settings MEMORY, CFG, ACTION, + MCP23017_NR_1, + MCP23017_FUNCTION_1, + MCP23017_NR_2, + MCP23017_FUNCTION_2, + MCP23017_NR_3, + MCP23017_FUNCTION_3, + MCP23017_NR_4, + MCP23017_FUNCTION_4, SETTINGSCOUNT }; @@ -138,8 +146,8 @@ class ConfigOption { int getLength(); void setValue(const char *value); - String getElement(int index); - String replaceElement(int index, int value); + const String getElement(int index); + const String replaceElement(int index, int value); private: uint8_t _key; diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index 6e2d5471..3bc9c2ff 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -27,6 +27,7 @@ Supla::Eeprom eeprom(STORAGE_OFFSET); namespace Supla { namespace GUI { void begin() { + #ifdef DEBUG_MODE new Supla::Sensor::EspFreeHeap(); #endif @@ -50,7 +51,6 @@ void begin() { (char *)ConfigManager->get(KEY_SUPLA_AUTHKEY)->getValue()); // Authorization key ConfigManager->showAllValue(); - WebServer->begin(); } diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index b6dde7a5..59f0d387 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -28,6 +28,7 @@ #include "SuplaConfigManager.h" #include "SuplaWebServer.h" #include "SuplaWebPageRelay.h" +#include "SuplaWebPageSensor.h" #include "SuplaWebPageDownload.h" #include "SuplaWebPageUpload.h" @@ -47,16 +48,15 @@ #include #include #include + #ifdef SUPLA_BME280 #include -#include "SuplaWebPageSensor.h" #endif #ifdef SUPLA_SI7021_SONOFF #include #endif #ifdef SUPLA_BME280 #include -#include "SuplaWebPageSensor.h" #endif #ifdef SUPLA_SHT3x #include @@ -76,6 +76,8 @@ #include +#include + namespace Supla { namespace GUI { diff --git a/src/SuplaHTTPUpdateServer.cpp b/src/SuplaHTTPUpdateServer.cpp index 7a68aaf8..593fd9cc 100644 --- a/src/SuplaHTTPUpdateServer.cpp +++ b/src/SuplaHTTPUpdateServer.cpp @@ -133,7 +133,7 @@ void ESP8266HTTPUpdateServer::setup(ESP8266WebServer* server, const String& path else if (_authenticated && upload.status == UPLOAD_FILE_ABORTED) { Update.end(); if (_serial_output) - Serial.println("Update was aborted"); + Serial.println(F("Update was aborted")); } delay(0); }); diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 474322b8..ebe6a4af 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -38,12 +38,14 @@ void addButtonCFG(uint8_t gpio) { ConfigESP->setGpio(gpio, FUNCTION_CFG_BUTTON); } +#ifdef SUPLA_HLW8012 void addHLW8012(int8_t pinCF, int8_t pinCF1, int8_t pinSEL) { ConfigESP->setGpio(pinCF, FUNCTION_CF); ConfigESP->setGpio(pinCF1, FUNCTION_CF1); ConfigESP->setGpio(pinSEL, FUNCTION_SEL); Supla::GUI::addHLW8012(ConfigESP->getGpio(FUNCTION_CF), ConfigESP->getGpio(FUNCTION_CF1), ConfigESP->getGpio(FUNCTION_SEL)); } +#endif void chooseTemplateBoard(uint8_t board) { ConfigManager->set(KEY_MAX_BUTTON, "0"); @@ -193,11 +195,13 @@ void chooseTemplateBoard(uint8_t board) { addButtonCFG(13); addButton(13, Supla::ON_RELEASE); addRelay(15); +#ifdef SUPLA_HLW8012 addHLW8012(5, 4, 12); Supla::GUI::counterHLW8012->setCurrentMultiplier(18388); Supla::GUI::counterHLW8012->setVoltageMultiplier(247704); Supla::GUI::counterHLW8012->setPowerMultiplier(2586583); Supla::GUI::counterHLW8012->saveState(); +#endif break; } } diff --git a/src/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp index 9033f567..28411956 100644 --- a/src/SuplaWebPageControl.cpp +++ b/src/SuplaWebPageControl.cpp @@ -35,7 +35,7 @@ void SuplaWebPageControl::handleControl() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_control(0)); + supla_webpage_control(0); } void SuplaWebPageControl::handleControlSave() { @@ -45,14 +45,22 @@ void SuplaWebPageControl::handleControlSave() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - String key, input; - uint8_t nr, current_value, last_value; + + uint8_t nr, last_value; #ifdef SUPLA_BUTTON last_value = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { - if (!WebServer->saveGPIO(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr, INPUT_MAX_BUTTON)) { - WebServer->sendContent(supla_webpage_control(6)); - return; + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + if (!WebServer->saveGpioMCP23017(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr, INPUT_MAX_BUTTON)) { + supla_webpage_control(6); + return; + } + } + else { + if (!WebServer->saveGPIO(INPUT_BUTTON_GPIO, FUNCTION_BUTTON, nr, INPUT_MAX_BUTTON)) { + supla_webpage_control(6); + return; + } } } @@ -65,7 +73,7 @@ void SuplaWebPageControl::handleControlSave() { last_value = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { if (!WebServer->saveGPIO(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr, INPUT_MAX_LIMIT_SWITCH)) { - WebServer->sendContent(supla_webpage_control(6)); + supla_webpage_control(6); return; } } @@ -77,55 +85,58 @@ void SuplaWebPageControl::handleControlSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_control(1)); + supla_webpage_control(1); break; case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_control(2)); + supla_webpage_control(2); break; } } -String SuplaWebPageControl::supla_webpage_control(int save) { - uint8_t nr, suported, selected; - String pagebutton, key; +void SuplaWebPageControl::supla_webpage_control(int save) { + uint8_t nr, countFreeGpio; - pagebutton += SuplaSaveResult(save); - pagebutton += SuplaJavaScript(PATH_CONTROL); - pagebutton += F("
"); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_CONTROL); + addForm(webContentBuffer, F("post"), PATH_SAVE_CONTROL); #if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_BUTTON) && defined(SUPLA_ROLLERSHUTTER)) - addFormHeader(pagebutton, String(S_GPIO_SETTINGS_FOR_BUTTONS)); - addNumberBox(pagebutton, INPUT_MAX_BUTTON, S_QUANTITY, KEY_MAX_BUTTON, ConfigESP->countFreeGpio(FUNCTION_BUTTON)); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR_BUTTONS)); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + countFreeGpio = 16; + } + else { + countFreeGpio = ConfigESP->countFreeGpio(FUNCTION_BUTTON); + } + + addNumberBox(webContentBuffer, INPUT_MAX_BUTTON, S_QUANTITY, KEY_MAX_BUTTON, countFreeGpio); + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); nr++) { - addListGPIOLinkBox(pagebutton, INPUT_BUTTON_GPIO, S_BUTTON, FUNCTION_BUTTON, PATH_BUTTON_SET, nr); + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + addListMCP23017GPIOLinkBox(webContentBuffer, INPUT_BUTTON_GPIO, S_BUTTON, FUNCTION_BUTTON, PATH_BUTTON_SET, nr); + } + else { + addListGPIOLinkBox(webContentBuffer, INPUT_BUTTON_GPIO, S_BUTTON, FUNCTION_BUTTON, PATH_BUTTON_SET, nr); + } } - addFormHeaderEnd(pagebutton); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_LIMIT_SWITCH - addFormHeader(pagebutton, String(S_GPIO_SETTINGS_FOR_LIMIT_SWITCH)); - addNumberBox(pagebutton, INPUT_MAX_LIMIT_SWITCH, S_QUANTITY, KEY_MAX_LIMIT_SWITCH, ConfigESP->countFreeGpio(FUNCTION_LIMIT_SWITCH)); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR_LIMIT_SWITCH)); + addNumberBox(webContentBuffer, INPUT_MAX_LIMIT_SWITCH, S_QUANTITY, KEY_MAX_LIMIT_SWITCH, ConfigESP->countFreeGpio(FUNCTION_LIMIT_SWITCH)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { - addListGPIOBox(pagebutton, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); + addListGPIOBox(webContentBuffer, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); } - addFormHeaderEnd(pagebutton); + addFormHeaderEnd(webContentBuffer); #endif - pagebutton += F("
"); - pagebutton += F("
"); - pagebutton += F("

"); - return pagebutton; + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); + + WebServer->sendContent(); } #if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_BUTTON) && defined(SUPLA_ROLLERSHUTTER)) @@ -134,7 +145,7 @@ void SuplaWebPageControl::handleButtonSet() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_button_set(0)); + supla_webpage_button_set(0); } void SuplaWebPageControl::handleButtonSaveSet() { @@ -144,16 +155,28 @@ void SuplaWebPageControl::handleButtonSaveSet() { return WebServer->httpServer.requestAuthentication(); } - String readUrl, nr_button, input; - uint8_t place; + String readUrl, nr_button, input, path; + uint8_t place, key, gpio; + + input.reserve(10); + readUrl.reserve(11); + nr_button.reserve(2); + path.reserve(14); - String path = PATH_START; + path = PATH_START; path += PATH_SAVE_BUTTON_SET; readUrl = WebServer->httpServer.uri(); place = readUrl.indexOf(path); nr_button = readUrl.substring(place + path.length(), place + path.length() + 3); - uint8_t key = KEY_GPIO + ConfigESP->getGpio(nr_button.toInt(), FUNCTION_BUTTON); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + gpio = ConfigESP->getGpioMCP23017(nr_button.toInt(), FUNCTION_BUTTON); + } + else { + gpio = ConfigESP->getGpio(nr_button.toInt(), FUNCTION_BUTTON); + } + key = KEY_GPIO + gpio; input = INPUT_BUTTON_LEVEL; input += nr_button; @@ -165,99 +188,46 @@ void SuplaWebPageControl::handleButtonSaveSet() { switch (ConfigManager->save()) { case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Dane zapisane")); - WebServer->sendContent(supla_webpage_control(1)); + supla_webpage_control(1); break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_control(2)); + supla_webpage_control(2); break; } } -String SuplaWebPageControl::supla_webpage_button_set(int save) { - String readUrl, nr_button, key; - uint8_t place, selected, suported; +void SuplaWebPageControl::supla_webpage_button_set(int save) { + String path, readUrl, nr_button; + uint8_t place, selected; + + path.reserve(10); + readUrl.reserve(11); + nr_button.reserve(2); - String path = PATH_START; + path = PATH_START; path += PATH_BUTTON_SET; readUrl = WebServer->httpServer.uri(); place = readUrl.indexOf(path); nr_button = readUrl.substring(place + path.length(), place + path.length() + 3); - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_CONTROL); - uint8_t buttons = ConfigManager->get(KEY_MAX_BUTTON)->getValueInt(); - if (nr_button.toInt() <= buttons && ConfigESP->getGpio(nr_button.toInt(), FUNCTION_BUTTON) != OFF_GPIO) { - page += F("

"); - page += S_BUTTON_NR_SETTINGS; - page += F(" "); - page += nr_button; - page += F("

"); - - page += F(""); - - page += F(""); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_CONTROL); - page += F("
"); - } - else { - page += F("

"); - page += S_NO_BUTTON_NR; - page += F("

"); - page += nr_button; - page += F(""); - } - page += F("
"); - page += F("

"); - - return page; + addForm(webContentBuffer, F("post"), PATH_SAVE_BUTTON_SET + nr_button); + addFormHeader(webContentBuffer, S_BUTTON_NR_SETTINGS + nr_button); + + selected = ConfigESP->getLevel(nr_button.toInt(), FUNCTION_BUTTON); + addListBox(webContentBuffer, INPUT_BUTTON_LEVEL + nr_button, S_REACTION_TO, TRIGGER_P, 3, selected); + + selected = ConfigESP->getAction(nr_button.toInt(), FUNCTION_BUTTON); + addListBox(webContentBuffer, INPUT_BUTTON_ACTION + nr_button, S_ACTION, ACTION_P, 3, selected); + + addFormHeaderEnd(webContentBuffer); + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_RELAY); + + WebServer->sendContent(); } #endif diff --git a/src/SuplaWebPageControl.h b/src/SuplaWebPageControl.h index 99ef91f1..c7043de2 100644 --- a/src/SuplaWebPageControl.h +++ b/src/SuplaWebPageControl.h @@ -4,18 +4,18 @@ #include "SuplaWebServer.h" #include "SuplaDeviceGUI.h" -#define PATH_CONTROL "control" -#define PATH_SAVE_CONTROL "savecontrol" -#define PATH_BUTTON_SET "setbutton" -#define PATH_SAVE_BUTTON_SET "savesetbutton" -#define INPUT_TRIGGER "trs" -#define INPUT_BUTTON_SET "bts" -#define INPUT_BUTTON_GPIO "btg" -#define INPUT_BUTTON_LEVEL "icl" -#define INPUT_BUTTON_ACTION "bta" -#define INPUT_LIMIT_SWITCH_GPIO "lsg" -#define INPUT_MAX_BUTTON "mbt" -#define INPUT_MAX_LIMIT_SWITCH "mls" +#define PATH_CONTROL "control" +#define PATH_SAVE_CONTROL "savecontrol" +#define PATH_BUTTON_SET "setbutton" +#define PATH_SAVE_BUTTON_SET "savesetbutton" +#define INPUT_TRIGGER "trs" +#define INPUT_BUTTON_SET "bts" +#define INPUT_BUTTON_GPIO "btg" +#define INPUT_BUTTON_LEVEL "icl" +#define INPUT_BUTTON_ACTION "bta" +#define INPUT_LIMIT_SWITCH_GPIO "lsg" +#define INPUT_MAX_BUTTON "mbt" +#define INPUT_MAX_LIMIT_SWITCH "mls" /*enum _trigger_button { TRIGGER_PRESS, @@ -32,15 +32,14 @@ class SuplaWebPageControl { #if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_RSUPLA_BUTTONELAY) || defined(SUPLA_ROLLERSHUTTER)) void handleButtonSet(); void handleButtonSaveSet(); -#endif private: - String supla_webpage_control(int save); + void supla_webpage_control(int save); +#endif #ifdef SUPLA_BUTTON - String supla_webpage_button_set(int save); + void supla_webpage_button_set(int save); #endif - }; extern SuplaWebPageControl* WebPageControl; diff --git a/src/SuplaWebPageRelay.cpp b/src/SuplaWebPageRelay.cpp index a2e44450..9d3115d7 100644 --- a/src/SuplaWebPageRelay.cpp +++ b/src/SuplaWebPageRelay.cpp @@ -4,6 +4,7 @@ #include "SuplaCommonPROGMEM.h" #include "GUIGenericCommon.h" #include "Markup.h" +#include "SuplaWebPageSensor.h" #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) SuplaWebPageRelay *WebPageRelay = new SuplaWebPageRelay(); @@ -13,23 +14,25 @@ SuplaWebPageRelay::SuplaWebPageRelay() { void SuplaWebPageRelay::createWebPageRelay() { String path; + path.reserve(11); + path += PATH_START; path += PATH_RELAY; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelay, this)); + WebServer->httpServer.on(path, HTTP_GET, std::bind(&SuplaWebPageRelay::handleRelay, this)); path = PATH_START; path += PATH_SAVE_RELAY; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySave, this)); + WebServer->httpServer.on(path, HTTP_POST, std::bind(&SuplaWebPageRelay::handleRelaySave, this)); - for (uint8_t i = 1; i <= MAX_GPIO; i++) { + for (uint8_t i = 1; i <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); i++) { path = PATH_START; path += PATH_RELAY_SET; path += i; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySet, this)); + WebServer->httpServer.on(path, HTTP_GET, std::bind(&SuplaWebPageRelay::handleRelaySet, this)); path = PATH_START; path += PATH_SAVE_RELAY_SET; path += i; - WebServer->httpServer.on(path, std::bind(&SuplaWebPageRelay::handleRelaySaveSet, this)); + WebServer->httpServer.on(path, HTTP_POST, std::bind(&SuplaWebPageRelay::handleRelaySaveSet, this)); } } @@ -38,12 +41,10 @@ void SuplaWebPageRelay::handleRelay() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_relay(0)); + supla_webpage_relay(0); } void SuplaWebPageRelay::handleRelaySave() { - // Serial.println(F("HTTP_POST - metoda handleRelaySave")); - if (ConfigESP->configModeESP == NORMAL_MODE) { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); @@ -53,9 +54,17 @@ void SuplaWebPageRelay::handleRelaySave() { last_value = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { - if (!WebServer->saveGPIO(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr, INPUT_MAX_RELAY)) { - WebServer->sendContent(supla_webpage_relay(6)); - return; + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + if (!WebServer->saveGpioMCP23017(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr, INPUT_MAX_RELAY)) { + supla_webpage_relay(6); + return; + } + } + else { + if (!WebServer->saveGPIO(INPUT_RELAY_GPIO, FUNCTION_RELAY, nr, INPUT_MAX_RELAY)) { + supla_webpage_relay(6); + return; + } } } @@ -65,42 +74,47 @@ void SuplaWebPageRelay::handleRelaySave() { switch (ConfigManager->save()) { case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_relay(1)); + supla_webpage_relay(1); break; case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_relay(2)); + supla_webpage_relay(2); break; } } -String SuplaWebPageRelay::supla_webpage_relay(int save) { - uint8_t selected, suported, nr; - - String pagerelay = ""; - pagerelay += SuplaSaveResult(save); - pagerelay += SuplaJavaScript(PATH_RELAY); - pagerelay += F("
"); - addFormHeader(pagerelay, String(S_GPIO_SETTINGS_FOR_RELAYS)); - addNumberBox(pagerelay, INPUT_MAX_RELAY, S_QUANTITY, KEY_MAX_RELAY, ConfigESP->countFreeGpio(FUNCTION_RELAY)); +void SuplaWebPageRelay::supla_webpage_relay(int save) { + uint8_t nr, countFreeGpio; + + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_RELAY); + + addForm(webContentBuffer, F("post"), PATH_SAVE_RELAY); + addFormHeader(webContentBuffer, S_GPIO_SETTINGS_FOR_RELAYS); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + countFreeGpio = 16; + } + else { + countFreeGpio = ConfigESP->countFreeGpio(FUNCTION_RELAY); + } + + addNumberBox(webContentBuffer, INPUT_MAX_RELAY, S_QUANTITY, KEY_MAX_RELAY, countFreeGpio); + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { - addListGPIOLinkBox(pagerelay, INPUT_RELAY_GPIO, S_RELAY, FUNCTION_RELAY, PATH_RELAY_SET, nr); + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + addListMCP23017GPIOLinkBox(webContentBuffer, INPUT_RELAY_GPIO, S_RELAY, FUNCTION_RELAY, PATH_RELAY_SET, nr); + } + else { + addListGPIOLinkBox(webContentBuffer, INPUT_RELAY_GPIO, S_RELAY, FUNCTION_RELAY, PATH_RELAY_SET, nr); + } } - addFormHeaderEnd(pagerelay); - pagerelay += F("
"); - pagerelay += F("
"); - pagerelay += F("

"); - return pagerelay; + addFormHeaderEnd(webContentBuffer); + + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); + + WebServer->sendContent(); } void SuplaWebPageRelay::handleRelaySet() { @@ -108,18 +122,21 @@ void SuplaWebPageRelay::handleRelaySet() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_relay_set(0)); + supla_webpage_relay_set(0); } void SuplaWebPageRelay::handleRelaySaveSet() { - // Serial.println(F("HTTP_POST - metoda handleRelaySaveSet")); if (ConfigESP->configModeESP == NORMAL_MODE) { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } String readUrl, nr_relay, input; - uint8_t place; + uint8_t place, key, gpio; + + input.reserve(9); + readUrl.reserve(11); + nr_relay.reserve(2); String path = PATH_START; path += PATH_SAVE_RELAY_SET; @@ -127,7 +144,15 @@ void SuplaWebPageRelay::handleRelaySaveSet() { place = readUrl.indexOf(path); nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); - uint8_t key = KEY_GPIO + ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + gpio = ConfigESP->getGpioMCP23017(nr_relay.toInt(), FUNCTION_RELAY); + } + else { + gpio = ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY); + } + + key = KEY_GPIO + gpio; input = INPUT_RELAY_MEMORY; input += nr_relay; @@ -139,96 +164,46 @@ void SuplaWebPageRelay::handleRelaySaveSet() { switch (ConfigManager->save()) { case E_CONFIG_OK: - // Serial.println(F("E_CONFIG_OK: Dane zapisane")); - WebServer->sendContent(supla_webpage_relay(1)); + supla_webpage_relay(1); break; - case E_CONFIG_FILE_OPEN: - // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_relay(2)); + supla_webpage_relay(2); break; } } -String SuplaWebPageRelay::supla_webpage_relay_set(int save) { - String readUrl, nr_relay, key; - uint8_t place, selected, suported; +void SuplaWebPageRelay::supla_webpage_relay_set(int save) { + String path, readUrl, nr_relay; + uint8_t place, selected; - String path = PATH_START; + path.reserve(9); + readUrl.reserve(11); + nr_relay.reserve(2); + + path = PATH_START; path += PATH_RELAY_SET; readUrl = WebServer->httpServer.uri(); place = readUrl.indexOf(path); nr_relay = readUrl.substring(place + path.length(), place + path.length() + 3); - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_RELAY); - uint8_t relays = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); - if (nr_relay.toInt() <= relays && ConfigESP->getGpio(nr_relay.toInt(), FUNCTION_RELAY) != OFF_GPIO) { - page += F("

"); - page += S_RELAY_NR_SETTINGS; - page += F(" "); - page += nr_relay; - page += F("

"); - page += F(""); - page += F(""); - page += F("
"); - } - else { - page += F("

"); - page += S_NO_RELAY_NR; - page += F(" "); - page += nr_relay; - page += F("

"); - } - page += F("
"); - page += F("

"); - - return page; + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_RELAY); + + addForm(webContentBuffer, F("post"), PATH_SAVE_RELAY_SET + nr_relay); + addFormHeader(webContentBuffer, S_RELAY_NR_SETTINGS + nr_relay); + + selected = ConfigESP->getLevel(nr_relay.toInt(), FUNCTION_RELAY); + addListBox(webContentBuffer, INPUT_RELAY_LEVEL + nr_relay, S_STATE_CONTROL, LEVEL_P, 2, selected); + + selected = ConfigESP->getMemory(nr_relay.toInt(), FUNCTION_RELAY); + addListBox(webContentBuffer, INPUT_RELAY_MEMORY + nr_relay, S_REACTION_AFTER_RESET, MEMORY_P, 3, selected); + + addFormHeaderEnd(webContentBuffer); + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_RELAY); + + WebServer->sendContent(); } #endif diff --git a/src/SuplaWebPageRelay.h b/src/SuplaWebPageRelay.h index 88c71aa5..50b65fc1 100644 --- a/src/SuplaWebPageRelay.h +++ b/src/SuplaWebPageRelay.h @@ -4,23 +4,25 @@ #include "SuplaDeviceGUI.h" #include "SuplaWebServer.h" -#define PATH_RELAY "relay" -#define PATH_SAVE_RELAY "saverelay" -#define PATH_RELAY_SET "setrelay" -#define PATH_SAVE_RELAY_SET "savesetrelay" -#define INPUT_MAX_RELAY "mrl" -#define INPUT_RELAY_GPIO "rlg" -#define INPUT_RELAY_LEVEL "irl" -#define INPUT_RELAY_MEMORY "irm" -#define INPUT_RELAY_DURATION "ird" -#define INPUT_ROLLERSHUTTER "irsr" +#define PATH_RELAY "relay" +#define PATH_SAVE_RELAY "saverelay" +#define PATH_RELAY_SET "setrelay" +#define PATH_SAVE_RELAY_SET "savesetrelay" +#define INPUT_MAX_RELAY "mrl" +#define INPUT_RELAY_GPIO "rlg" +#define INPUT_ADRESS_MCP23017 "iam" +#define INPUT_RELAY_LEVEL "irl" +#define INPUT_RELAY_MEMORY "irm" +#define INPUT_RELAY_DURATION "ird" +#define INPUT_ROLLERSHUTTER "irsr" -enum _memory_relay { +enum _memory_relay +{ MEMORY_RELAY_OFF, MEMORY_RELAY_ON, MEMORY_RELAY_RESTORE }; - + #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) class SuplaWebPageRelay { @@ -33,8 +35,8 @@ class SuplaWebPageRelay { void handleRelaySaveSet(); private: - String supla_webpage_relay_set(int save); - String supla_webpage_relay(int save); + void supla_webpage_relay_set(int save); + void supla_webpage_relay(int save); }; extern SuplaWebPageRelay* WebPageRelay; diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 7e683f54..3234d52e 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -31,7 +31,7 @@ void SuplaWebPageSensor::createWebPageSensor() { #endif #endif -#if defined(SUPLA_BME280) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_SHT3x) || defined(SUPLA_SI7021) || defined(SUPLA_MCP23017) path = PATH_START; path += PATH_I2C; WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handlei2c, this)); @@ -80,9 +80,9 @@ void SuplaWebPageSensor::createWebPageSensor() { path += PATH_SAVE_HLW8012_CALIBRATE; WebServer->httpServer.on(path, std::bind(&SuplaWebPageSensor::handleHLW8012CalibrateSave, this)); #endif - #endif } + #ifdef SUPLA_DS18B20 void SuplaWebPageSensor::handleSearchDS() { if (ConfigESP->configModeESP == NORMAL_MODE) { @@ -273,8 +273,7 @@ void SuplaWebPageSensor::handle1WireSave() { return WebServer->httpServer.requestAuthentication(); } - String key, input; - uint8_t nr, current_value, last_value; + uint8_t nr, last_value; #ifdef SUPLA_DHT11 last_value = ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); @@ -333,9 +332,8 @@ void SuplaWebPageSensor::handle1WireSave() { } String SuplaWebPageSensor::supla_webpage_1wire(int save) { - uint8_t nr, suported, selected; - uint16_t max; - String page, key; + uint8_t nr, max; + String page; page += SuplaSaveResult(save); page += SuplaJavaScript(PATH_1WIRE); @@ -392,7 +390,7 @@ String SuplaWebPageSensor::supla_webpage_1wire(int save) { } #endif -#if defined(SUPLA_BME280) || defined(SUPLA_SHT30) || defined(SUPLA_SI7021) +#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_OLED) || defined(SUPLA_MCP23017) void SuplaWebPageSensor::handlei2c() { if (ConfigESP->configModeESP == NORMAL_MODE) { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) @@ -408,9 +406,8 @@ void SuplaWebPageSensor::handlei2cSave() { } String input; - uint8_t key, nr, current_value, last_value; + uint8_t key; -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) if (!WebServer->saveGPIO(INPUT_SDA_GPIO, FUNCTION_SDA)) { WebServer->sendContent(supla_webpage_i2c(6)); return; @@ -419,7 +416,6 @@ void SuplaWebPageSensor::handlei2cSave() { WebServer->sendContent(supla_webpage_i2c(6)); return; } -#endif #ifdef SUPLA_BME280 key = KEY_ACTIVE_SENSOR; @@ -459,6 +455,19 @@ void SuplaWebPageSensor::handlei2cSave() { } #endif +#ifdef SUPLA_MCP23017 + key = KEY_ACTIVE_SENSOR; + input = INPUT_MCP23017; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_MCP23017, WebServer->httpServer.arg(input).toInt()); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt()) { + ConfigESP->clearFunctionGpio(FUNCTION_RELAY); + ConfigESP->clearFunctionGpio(FUNCTION_BUTTON); + } + } +#endif + switch (ConfigManager->save()) { case E_CONFIG_OK: WebServer->sendContent(supla_webpage_i2c(1)); @@ -471,23 +480,22 @@ void SuplaWebPageSensor::handlei2cSave() { } String SuplaWebPageSensor::supla_webpage_i2c(int save) { - uint8_t nr, suported, selected, size; - String page, key; + uint8_t selected; + String page = ""; page += SuplaSaveResult(save); page += SuplaJavaScript(PATH_I2C); addForm(page, F("post"), PATH_SAVE_I2C); -#if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_OLED) - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " i2c"); - addListGPIOBox(page, INPUT_SDA_GPIO, "SDA", FUNCTION_SDA); - addListGPIOBox(page, INPUT_SCL_GPIO, "SCL", FUNCTION_SCL); + addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + F(" i2c")); + addListGPIOBox(page, INPUT_SDA_GPIO, F("SDA"), FUNCTION_SDA); + addListGPIOBox(page, INPUT_SCL_GPIO, F("SCL"), FUNCTION_SCL); addFormHeaderEnd(page); if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { #ifdef SUPLA_BME280 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt(); addFormHeader(page); - addListBox(page, INPUT_BME280, "BME280 adres", BME280_P, 4, selected); + addListBox(page, INPUT_BME280, F("BME280 adres"), BME280_P, 4, selected); addNumberBox(page, INPUT_ALTITUDE_BME280, S_ALTITUDE_ABOVE_SEA_LEVEL, KEY_ALTITUDE_BME280, 1500); addFormHeaderEnd(page); #endif @@ -495,25 +503,31 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { #ifdef SUPLA_SHT3x selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT3x).toInt(); addFormHeader(page); - addListBox(page, INPUT_SHT3x, "SHT3x", SHT3x_P, 4, selected); + addListBox(page, INPUT_SHT3x, F("SHT3x"), SHT3x_P, 4, selected); addFormHeaderEnd(page); #endif #ifdef SUPLA_SI7021 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt(); addFormHeader(page); - addListBox(page, INPUT_SI7021, "SI7021", STATE_P, 2, selected); + addListBox(page, INPUT_SI7021, F("SI7021"), STATE_P, 2, selected); addFormHeaderEnd(page); #endif #ifdef SUPLA_OLED selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); addFormHeader(page); - addListBox(page, INPUT_OLED, "OLED", OLED_P, 4, selected); + addListBox(page, INPUT_OLED, F("OLED"), OLED_P, 4, selected); addFormHeaderEnd(page); #endif - } + +#ifdef SUPLA_MCP23017 + selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt(); + addFormHeader(page); + addListBox(page, INPUT_MCP23017, F("MCP23017"), STATE_P, 2, selected); + addFormHeaderEnd(page); #endif + } addButtonSubmit(page, S_SAVE); addFormEnd(page); @@ -539,8 +553,8 @@ void SuplaWebPageSensor::handleSpiSave() { return WebServer->httpServer.requestAuthentication(); } - String key, input; - uint8_t nr, current_value, last_value; + String input; + uint8_t key; #if defined(SUPLA_MAX6675) if (!WebServer->saveGPIO(INPUT_CLK_GPIO, FUNCTION_CLK)) { @@ -578,7 +592,7 @@ void SuplaWebPageSensor::handleSpiSave() { String SuplaWebPageSensor::supla_webpage_spi(int save) { uint8_t nr, suported, selected; - String page, key; + String page; page += SuplaSaveResult(save); page += SuplaJavaScript(PATH_SPI); page += F("
"); } -void SuplaWebServer::sendContent(const String content) { +void SuplaWebServer::sendContent(const String& content) { // httpServer.send(200, "text/html", ""); - const int bufferSize = 1000; - String _buffer; - int bufferCounter = 0; + // const int bufferSize = 1000; + // String _buffer; + //_buffer.reserve(bufferSize); + // int bufferCounter = 0; + int fileSize = content.length(); #ifdef DEBUG_MODE - Serial.print("Content size: "); + Serial.print(F("Content size: ")); Serial.println(fileSize); + checkRAM(); #endif - httpServer.setContentLength(fileSize); + httpServer.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + httpServer.sendHeader("Pragma", "no-cache"); + httpServer.sendHeader("Expires", "-1"); + httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); httpServer.chunkedResponseModeStart(200, "text/html"); httpServer.sendContent_P(HTTP_META); + httpServer.sendContent_P(HTTP_FAVICON); httpServer.sendContent_P(HTTP_STYLE); httpServer.sendContent_P(HTTP_LOGO); @@ -317,7 +321,7 @@ void SuplaWebServer::sendContent(const String content) { httpServer.sendContent_P(HTTP_COPYRIGHT); // httpServer.send(200, "text/html", ""); - for (int i = 0; i < fileSize; i++) { + /*for (int i = 0; i < fileSize; i++) { _buffer += content[i]; bufferCounter++; @@ -333,9 +337,72 @@ void SuplaWebServer::sendContent(const String content) { yield(); bufferCounter = 0; _buffer = ""; + }*/ + + httpServer.sendContent(content); + httpServer.sendContent_P(HTTP_RBT); + httpServer.chunkedResponseFinalize(); +} + +String webContentBuffer; + +void SuplaWebServer::sendContent() { + // httpServer.send(200, "text/html", ""); + // const int bufferSize = 1000; + // String _buffer; + //_buffer.reserve(bufferSize); + // int bufferCounter = 0; + + httpServer.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + httpServer.sendHeader("Pragma", "no-cache"); + httpServer.sendHeader("Expires", "-1"); + httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); + httpServer.chunkedResponseModeStart(200, "text/html"); + + httpServer.sendContent_P(HTTP_META); + httpServer.sendContent_P(HTTP_FAVICON); + httpServer.sendContent_P(HTTP_STYLE); + httpServer.sendContent_P(HTTP_LOGO); + + String summary = FPSTR(HTTP_SUMMARY); + + summary.replace("{h}", ConfigManager->get(KEY_HOST_NAME)->getValue()); + summary.replace("{s}", ConfigESP->getLastStatusSupla()); + summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); + summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); + summary.replace("{m}", ConfigESP->getMacAddress(true)); + httpServer.sendContent(summary); + httpServer.sendContent_P(HTTP_COPYRIGHT); + + // httpServer.send(200, "text/html", ""); + /*for (int i = 0; i < fileSize; i++) { + _buffer += content[i]; + bufferCounter++; + + if (bufferCounter >= bufferSize) { + httpServer.sendContent(_buffer); + yield(); + bufferCounter = 0; + _buffer = ""; + } } + if (bufferCounter > 0) { + httpServer.sendContent(_buffer); + yield(); + bufferCounter = 0; + _buffer = ""; + }*/ + + httpServer.sendContent(webContentBuffer); httpServer.sendContent_P(HTTP_RBT); httpServer.chunkedResponseFinalize(); + +#ifdef DEBUG_MODE + Serial.printf_P(PSTR("Content size=%d\n"), webContentBuffer.length()); + Serial.printf_P(PSTR("Sent INDEX...Free mem=%d\n"), ESP.getFreeHeap()); + checkRAM(); +#endif + webContentBuffer = ""; } void SuplaWebServer::handleNotFound() { @@ -347,6 +414,7 @@ bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr uint8_t current_value, key; String input; input = _input; + if (nr != 0) { input += nr; } @@ -354,6 +422,10 @@ bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr nr = 1; } + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") == 0) { + return true; + } + key = KEY_GPIO + WebServer->httpServer.arg(input).toInt(); if (ConfigESP->getGpio(nr, function) != WebServer->httpServer.arg(input).toInt() || WebServer->httpServer.arg(input).toInt() == OFF_GPIO) { @@ -383,4 +455,32 @@ bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr } } return true; +} + +bool SuplaWebServer::saveGpioMCP23017(const String& input, uint8_t function, uint8_t nr, const String& input_max) { + uint8_t key, address, addressInput, gpio, gpioInput, functionElementInput; + String _input = input + nr; + + if (strcmp(WebServer->httpServer.arg(_input).c_str(), "") == 0) { + return true; + } + + addressInput = WebServer->httpServer.arg(INPUT_ADRESS_MCP23017).toInt(); + functionElementInput = ConfigManager->get(key)->getElement(ConfigESP->getFunctionMCP23017(addressInput)).toInt(); + gpioInput = WebServer->httpServer.arg(_input).toInt(); + + key = KEY_GPIO + gpioInput; + gpio = ConfigESP->getGpioMCP23017(nr, function); + address = ConfigESP->getAdressMCP23017(function); + + if (functionElementInput == FUNCTION_OFF) { + ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, 1, 0); + } + else if (gpio == gpioInput && functionElementInput == function) { + ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, ConfigESP->getLevel(nr, function), ConfigESP->getMemory(nr, function)); + } + else { + return false; + } + return true; } \ No newline at end of file diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 51a4219e..333c7fc4 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -53,6 +53,25 @@ #define INPUT_ROLLERSHUTTER "irsr" #define INPUT_BOARD "board" + +extern String webContentBuffer; + +//https://www.esp8266.com/viewtopic.php?p=84249#p84249 +class MyWebServer : public ESP8266WebServer { + public: + virtual ~MyWebServer() { + if (this->_currentArgs) { + delete[] this->_currentArgs; + this->_currentArgs = nullptr; + } + + if (this->_postArgs) { + delete[] this->_postArgs; + this->_postArgs = nullptr; + } + } +}; + class SuplaWebServer : public Supla::Element { public: SuplaWebServer(); @@ -61,19 +80,20 @@ class SuplaWebServer : public Supla::Element { char www_username[MAX_MLOGIN]; char www_password[MAX_MPASSWORD]; - const String SuplaFavicon(); - const String SuplaIconEdit(); + const String& SuplaIconEdit(); String supla_webpage_start(int save); - void sendContent(const String content); + void sendContent(const String& content); + void sendContent(); + + MyWebServer httpServer; - ESP8266WebServer httpServer = {80}; - #ifdef SUPLA_OTA - ESP8266HTTPUpdateServer httpUpdater; + ESP8266HTTPUpdateServer httpUpdater; #endif bool saveGPIO(const String& input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); + bool saveGpioMCP23017(const String& input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); private: void iterateAlways(); @@ -92,5 +112,4 @@ class SuplaWebServer : public Supla::Element { void handleNotFound(); }; - #endif // SuplaWebServer_h From 5996f7308759a08de3cb3dcadfbf7e9abc6c7409 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Fri, 15 Jan 2021 13:26:32 +0100 Subject: [PATCH 102/233] =?UTF-8?q?nowy=20spos=C3=B3b=20pobierania=20warto?= =?UTF-8?q?=C5=9Bci=20dla=20OLEDa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/GUI-Generic.ino | 19 ++-- src/SuplaDeviceGUI.cpp | 20 ---- src/SuplaDeviceGUI.h | 20 ---- src/SuplaOled.cpp | 212 ++++++++++++++++++++++++----------------- src/SuplaOled.h | 33 ++++--- 5 files changed, 147 insertions(+), 157 deletions(-) diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index 10f3ae50..fbd59496 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -90,7 +90,7 @@ void setup() { #ifdef SUPLA_DHT22 for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { if (ConfigESP->getGpio(nr, FUNCTION_DHT22) != OFF_GPIO) { - Supla::GUI::sensorDHT22.push_back(new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22)); + new Supla::Sensor::DHT(ConfigESP->getGpio(nr, FUNCTION_DHT22), DHT22); } } #endif @@ -103,7 +103,7 @@ void setup() { #ifdef SUPLA_SI7021_SONOFF if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != OFF_GPIO) { - Supla::GUI::sensorSi7021Sonoff.push_back(new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF))); + new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); } #endif @@ -121,14 +121,14 @@ void setup() { switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt()) { case BME280_ADDRESS_0X76: - Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); + new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); break; case BME280_ADDRESS_0X77: - Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); + new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); break; case BME280_ADDRESS_0X76_AND_0X77: - Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); - Supla::GUI::sensorBme280.push_back(new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt())); + new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); + new Supla::Sensor::BME280(0x77, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); break; } #endif @@ -155,8 +155,8 @@ void setup() { #endif #ifdef SUPLA_OLED if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { - Supla::GUI::oled = new SuplaOled(); - Supla::GUI::oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); + SuplaOled *oled = new SuplaOled(); + oled->addButtonOled(ConfigESP->getGpio(FUNCTION_CFG_BUTTON)); } #endif @@ -175,8 +175,7 @@ void setup() { #ifdef SUPLA_MAX6675 if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { - Supla::GUI::sensorMAX6675_K.push_back( - new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0))); + new Supla::Sensor::MAX6675_K(ConfigESP->getGpio(FUNCTION_CLK), ConfigESP->getGpio(FUNCTION_CS), ConfigESP->getGpio(FUNCTION_D0)); } #endif diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index 3bc9c2ff..9f1cf5c1 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -164,26 +164,6 @@ std::vector button; std::vector sensorDS; #endif -#ifdef SUPLA_BME280 -std::vector sensorBme280; -#endif - -#ifdef SUPLA_SI7021_SONOFF -std::vector sensorSi7021Sonoff; -#endif - -#ifdef SUPLA_DHT22 -std::vector sensorDHT22; -#endif - -#ifdef SUPLA_MAX6675 -std::vector sensorMAX6675_K; -#endif - -#ifdef SUPLA_OLED -SuplaOled *oled; -#endif - #ifdef SUPLA_HLW8012 Supla::Sensor::HJ101 *counterHLW8012; diff --git a/src/SuplaDeviceGUI.h b/src/SuplaDeviceGUI.h index 59f0d387..02399296 100644 --- a/src/SuplaDeviceGUI.h +++ b/src/SuplaDeviceGUI.h @@ -114,26 +114,6 @@ extern std::vector impulseCounter; void addImpulseCounter(int pin, bool lowToHigh, bool inputPullup, unsigned int debounceDelay); #endif -#ifdef SUPLA_BME280 -extern std::vector sensorBme280; -#endif - -#ifdef SUPLA_SI7021_SONOFF -extern std::vector sensorSi7021Sonoff; -#endif - -#ifdef SUPLA_DHT22 -extern std::vector sensorDHT22; -#endif - -#ifdef SUPLA_MAX6675 -extern std::vector sensorMAX6675_K; -#endif - -#ifdef SUPLA_OLED -extern SuplaOled *oled; -#endif - #ifdef SUPLA_HLW8012 extern Supla::Sensor::HJ101 *counterHLW8012; void addHLW8012(int8_t pinCF, int8_t pinCF1, int8_t pinSEL); diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index 5f792999..842d200e 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -2,7 +2,9 @@ #include "SuplaDeviceGUI.h" #ifdef SUPLA_OLED + uint8_t* framesCountSensor; +uint8_t* chanelSensor; String getTempString(double temperature) { if (temperature == -275) { @@ -49,7 +51,7 @@ int32_t readRssi(void) { return (2 * (rssi + 100)); } -void displaySignal(OLEDDisplay* display) { +void displayUiSignal(OLEDDisplay* display) { int x = display->getWidth() - 17; int y = 0; int value = readRssi(); @@ -84,7 +86,7 @@ void displaySignal(OLEDDisplay* display) { } } -void displayRelayState(OLEDDisplay* display) { +void displayUiRelayState(OLEDDisplay* display) { int y = 0; int x = 0; @@ -108,18 +110,18 @@ void displayRelayState(OLEDDisplay* display) { } void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state) { - displaySignal(display); + displayUiSignal(display); if (Supla::GUI::relay.size()) { - displayRelayState(display); + displayUiRelayState(display); } } -void displaySuplaStatus(OLEDDisplay* display) { +void displayUiSuplaStatus(OLEDDisplay* display) { int x = 0; int y = display->getHeight() / 3; display->clear(); - displaySignal(display); + displayUiSignal(display); display->setFont(ArialMT_Plain_10); display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -140,14 +142,14 @@ void displayConfigMode(OLEDDisplay* display) { display->display(); } -void displayBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { +void displayUiBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { // display->drawXbm(10, 17, supla_logo_width, supla_logo_height, supla_logo_bits); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(ArialMT_Plain_16); display->drawString(10, display->getHeight() / 2, F("SUPLA")); } -void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name) { +void displayUiTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name) { uint8_t temp_width, temp_height; int drawHeightIcon = display->getHeight() / 2 - 10; @@ -177,7 +179,7 @@ void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int display->drawString(x + temp_width + (getTempString(temp).length() * 12), y + drawStringIcon, "ºC"); } -void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { +void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { uint8_t humidity_width, humidity_height; int drawHeightIcon = display->getHeight() / 2 - 10; @@ -202,7 +204,7 @@ void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, display->drawString(x + humidity_width + (getHumidityString(humidity).length() * 12), y + drawStringIcon, "%"); } -void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { +void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { uint8_t pressure_width, pressure_height; int drawHeightIcon = display->getHeight() / 2 - 10; @@ -227,41 +229,58 @@ void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, display->drawString(x + pressure_width + (getPressureString(pressure).length() * 14), y + drawStringIcon, "hPa"); } -void displayDs18b20(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayTemp(display, state, x, y, Supla::GUI::sensorDS[getFramesCountSensor(state)]->getValue(), - String(ConfigManager->get(KEY_DS_NAME + getFramesCountSensor(state))->getValue())); -} - -void displayBme280Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayTemp(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getTemp()); -} - -void displayBme280Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displaHumidity(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getHumi()); -} - -void displayBme280Pressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayPressure(display, state, x, y, Supla::GUI::sensorBme280[getFramesCountSensor(state)]->getPressure()); -} - -void displaySi7021SonoffTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayTemp(display, state, x, y, Supla::GUI::sensorSi7021Sonoff[getFramesCountSensor(state)]->getTemp()); -} - -void displaySi7021SonoffHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displaHumidity(display, state, x, y, Supla::GUI::sensorSi7021Sonoff[getFramesCountSensor(state)]->getHumi()); +void displayTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { + double lastTemperature = channel->getValueDouble(); + + if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() >= state->currentFrame) { + displayUiTemperature(display, state, x, y, lastTemperature, ConfigManager->get(KEY_DS_NAME + state->currentFrame)->getValue()); + } + else { + displayUiTemperature(display, state, x, y, lastTemperature); + } + } + } + } } -void displayDHT22Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayTemp(display, state, x, y, Supla::GUI::sensorDHT22[getFramesCountSensor(state)]->getTemp()); +void displayDoubleTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { + double lastTemperature = channel->getValueDoubleFirst(); + displayUiTemperature(display, state, x, y, lastTemperature); + } + } + } } -void displayDHT22Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displaHumidity(display, state, x, y, Supla::GUI::sensorDHT22[getFramesCountSensor(state)]->getHumi()); +void displayDoubleHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { + double lastHumidit = channel->getValueDoubleSecond(); + displaUiHumidity(display, state, x, y, lastHumidit); + } + } + } } -void displayMAX6675Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { - displayTemp(display, state, x, y, Supla::GUI::sensorMAX6675_K[getFramesCountSensor(state)]->getValue()); +void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getSecondaryChannel()) { + auto channel = element->getSecondaryChannel(); + if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { + double lastPressure = channel->getValueDouble(); + displayUiPressure(display, state, x, y, lastPressure); + } + } + } } SuplaOled::SuplaOled() { @@ -281,60 +300,43 @@ SuplaOled::SuplaOled() { ui = new OLEDDisplayUi(display); overlays[0] = {msOverlay}; - - int maxFrame = Supla::GUI::sensorDS.size() + (Supla::GUI::sensorBme280.size() * 3) + Supla::GUI::sensorSi7021Sonoff.size() + - (Supla::GUI::sensorDHT22.size() * 2) + Supla::GUI::sensorMAX6675_K.size(); - - if (maxFrame == 0) - maxFrame = 1; + int maxFrame = getMaxFrame(); frames = (FrameCallback*)malloc(sizeof(FrameCallback) * maxFrame); - framesCountSensor = (uint8_t*)malloc(sizeof(uint8_t) * maxFrame); - - for (int i = 0; i < Supla::GUI::sensorDS.size(); i++) { - frames[frameCount] = {displayDs18b20}; - framesCountSensor[frameCount] = i; - frameCount += 1; - } - - for (int i = 0; i < Supla::GUI::sensorBme280.size(); i++) { - frames[frameCount] = {displayBme280Temp}; - framesCountSensor[frameCount] = i; - frameCount += 1; - frames[frameCount] = {displayBme280Humidity}; - framesCountSensor[frameCount] = i; - frameCount += 1; - frames[frameCount] = {displayBme280Pressure}; - framesCountSensor[frameCount] = i; - frameCount += 1; - } - - for (int i = 0; i < Supla::GUI::sensorSi7021Sonoff.size(); i++) { - frames[frameCount] = {displaySi7021SonoffTemp}; - framesCountSensor[frameCount] = i; - frameCount += 1; - frames[frameCount] = {displaySi7021SonoffHumidity}; - framesCountSensor[frameCount] = i; - frameCount += 1; - } - - for (int i = 0; i < Supla::GUI::sensorDHT22.size(); i++) { - frames[frameCount] = {displayDHT22Temp}; - framesCountSensor[frameCount] = i; - frameCount += 1; - frames[frameCount] = {displayDHT22Humidity}; - framesCountSensor[frameCount] = i; - frameCount += 1; - } - - for (int i = 0; i < Supla::GUI::sensorMAX6675_K.size(); i++) { - frames[frameCount] = {displayMAX6675Temp}; - framesCountSensor[frameCount] = i; - frameCount += 1; + chanelSensor = (uint8_t*)malloc(sizeof(uint8_t) * maxFrame); + + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + Serial.println(channel->getChannelType()); + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_THERMOMETER) { + frames[frameCount] = {displayTemperature}; + chanelSensor[frameCount] = channel->getChannelNumber(); + frameCount += 1; + } + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR) { + frames[frameCount] = {displayDoubleTemperature}; + chanelSensor[frameCount] = channel->getChannelNumber(); + frameCount += 1; + frames[frameCount] = {displayDoubleHumidity}; + chanelSensor[frameCount] = channel->getChannelNumber(); + frameCount += 1; + } + } + if (element->getSecondaryChannel()) { + auto channel = element->getSecondaryChannel(); + if (channel->getChannelType() == SUPLA_CHANNELTYPE_PRESSURESENSOR) { + frames[frameCount] = {displayPressure}; + chanelSensor[frameCount] = channel->getChannelNumber(); + frameCount += 1; + } + } } if (frameCount == 0) { - frames[frameCount] = {displayBlank}; + frames[frameCount] = {displayUiBlank}; frameCount += 1; } @@ -359,7 +361,7 @@ SuplaOled::SuplaOled() { void SuplaOled::iterateAlways() { if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { if (ConfigESP->supla_status.status != STATUS_REGISTERED_AND_READY) { - displaySuplaStatus(display); + displayUiSuplaStatus(display); return; } @@ -384,7 +386,7 @@ void SuplaOled::iterateAlways() { void SuplaOled::addButtonOled(int pin) { if (pin != OFF_GPIO) { Supla::Control::Button* button = new Supla::Control::Button(pin, true, true); - button->addAction(TURN_ON_OLED, Supla::GUI::oled, Supla::ON_PRESS); + button->addAction(TURN_ON_OLED, this, Supla::ON_PRESS); } } @@ -395,4 +397,34 @@ void SuplaOled::runAction(int event, int action) { oledON = true; } } + +int SuplaOled::getMaxFrame() { + int maxFrame = 0; + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + Serial.println(channel->getChannelType()); + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_THERMOMETER) { + maxFrame += 1; + } + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR) { + maxFrame += 2; + } + } + + if (element->getSecondaryChannel()) { + auto channel = element->getSecondaryChannel(); + if (channel->getChannelType() == SUPLA_CHANNELTYPE_PRESSURESENSOR) { + maxFrame += 1; + } + } + } + + if (maxFrame == 0) + maxFrame = 1; + + return maxFrame; +} #endif \ No newline at end of file diff --git a/src/SuplaOled.h b/src/SuplaOled.h index dc3b6c4d..2f9987e6 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -29,24 +29,22 @@ String getHumidityString(double humidity); String getPressureString(double pressure); uint8_t getFramesCountSensor(OLEDDisplayUiState* state); int32_t readRssi(void); -void displaySignal(OLEDDisplay* display); -void displayRelayState(OLEDDisplay* display); + void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state); -void displaySuplaStatus(OLEDDisplay* display); -void displayConfigMode(OLEDDisplay* display); -void displayBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name = "\n"); -void displaHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity); -void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure); -void displayDs18b20(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayBme280Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayBme280Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayBme280Pressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displaySi7021SonoffTemp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displaySi7021SonoffHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayDHT22Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayDHT22Humidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); -void displayMAX6675Temp(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); + +void displayUiSignal(OLEDDisplay* display); +void displayUiRelayState(OLEDDisplay* display); +void displayUiSuplaStatus(OLEDDisplay* display); +void displayUiConfigMode(OLEDDisplay* display); +void displayUiBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); +void displayUiTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name = "\n"); +void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity); +void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure); + +void displayTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); +void displayDoubleTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); +void displayDoubleHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); +void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); class SuplaOled : public Supla::Triggerable, public Supla::Element { public: @@ -56,6 +54,7 @@ class SuplaOled : public Supla::Triggerable, public Supla::Element { private: void iterateAlways(); void runAction(int event, int action); + int getMaxFrame(); OLEDDisplay* display; OLEDDisplayUi* ui; From 513f90c7f026aadc8820e918d29497871c81baea Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sat, 16 Jan 2021 10:20:24 +0100 Subject: [PATCH 103/233] =?UTF-8?q?mo=C5=BCliwo=C5=9B=C4=87=20okre=C5=9Ble?= =?UTF-8?q?nia=20nazwy=20dla=20ka=C5=BCdego=20ekranu=20dla=20OLEDa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformio.ini | 6 +- src/GUI-Generic.ino | 17 +++--- src/GUIGenericCommon.cpp | 38 ++++++++++++ src/GUIGenericCommon.h | 5 ++ src/Markup.cpp | 22 ++++++- src/Markup.h | 16 ++++++ src/SuplaConfigESP.cpp | 4 +- src/SuplaConfigManager.cpp | 47 +++++++++------ src/SuplaConfigManager.h | 14 +++-- src/SuplaDeviceGUI.cpp | 6 +- src/SuplaOled.cpp | 115 +++++++++++++------------------------ src/SuplaOled.h | 10 +--- src/SuplaWebPageSensor.cpp | 56 ++++++++++++------ src/SuplaWebPageSensor.h | 3 + 14 files changed, 219 insertions(+), 140 deletions(-) create mode 100644 src/GUIGenericCommon.cpp diff --git a/platformio.ini b/platformio.ini index 9f97ff4b..ffe3d704 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.8"' +build_flags = -D BUILD_VERSION='"GUI 1.1.10"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -41,7 +41,7 @@ build_flags = -D BUILD_VERSION='"GUI 1.1.8"' -D SUPLA_SI7021 -D SUPLA_MAX6675 -D SUPLA_HC_SR04 - -D SUPLA_IMPULSE_COUNTER + ;-D SUPLA_IMPULSE_COUNTER -D SUPLA_OLED -D SUPLA_HLW8012 -D SUPLA_MCP23017 @@ -126,7 +126,7 @@ board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld build_flags = ${common.build_flags} -D DEBUG_MODE -;build_unflags = -D SUPLA_IMPULSE_COUNTER +build_unflags = -D SUPLA_IMPULSE_COUNTER [env:GUI_Generic_blank] board = nodemcuv2 diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index fbd59496..1d47261d 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -79,6 +79,12 @@ void setup() { ConfigManager->get(KEY_CFG_MODE)->getValueInt(), ConfigESP->getLevel(FUNCTION_CFG_LED)); #endif +#ifdef SUPLA_DS18B20 + if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { + Supla::GUI::addDS18B20MultiThermometer(ConfigESP->getGpio(FUNCTION_DS18B20)); + } +#endif + #ifdef SUPLA_DHT11 for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { if (ConfigESP->getGpio(nr, FUNCTION_DHT11) != OFF_GPIO) { @@ -95,12 +101,6 @@ void setup() { } #endif -#ifdef SUPLA_DS18B20 - if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { - Supla::GUI::addDS18B20MultiThermometer(ConfigESP->getGpio(FUNCTION_DS18B20)); - } -#endif - #ifdef SUPLA_SI7021_SONOFF if (ConfigESP->getGpio(FUNCTION_SI7021_SONOFF) != OFF_GPIO) { new Supla::Sensor::Si7021Sonoff(ConfigESP->getGpio(FUNCTION_SI7021_SONOFF)); @@ -114,11 +114,11 @@ void setup() { #endif #if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_HTU21D) || defined(SUPLA_SHT71) || \ - defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) || defined(SUPLA_MCP23017) + defined(SUPLA_BH1750) || defined(SUPLA_MAX44009) || defined(SUPLA_OLED) || defined(SUPLA_MCP23017) if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { Wire.begin(ConfigESP->getGpio(FUNCTION_SDA), ConfigESP->getGpio(FUNCTION_SCL)); -#ifdef SUPLA_BME280 +#ifdef SUPLA_BME280 switch (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt()) { case BME280_ADDRESS_0X76: new Supla::Sensor::BME280(0x76, ConfigManager->get(KEY_ALTITUDE_BME280)->getValueInt()); @@ -153,6 +153,7 @@ void setup() { new Supla::Sensor::Si7021(); } #endif + #ifdef SUPLA_OLED if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { SuplaOled *oled = new SuplaOled(); diff --git a/src/GUIGenericCommon.cpp b/src/GUIGenericCommon.cpp new file mode 100644 index 00000000..86e92967 --- /dev/null +++ b/src/GUIGenericCommon.cpp @@ -0,0 +1,38 @@ +#include "GUIGenericCommon.h" +#include "SuplaDeviceGUI.h" + +uint8_t *HexToBytes(String _value) { + int size = 16; + uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t) * (size)); + + for (int i = 0; i < size; i++) { + sscanf(&_value[i * 2], "%2hhx", &buffer[i]); + } + return buffer; +} + +int getNumberChannels() { + int maxFrame = 0; + for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { + if (element->getChannel()) { + auto channel = element->getChannel(); + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_THERMOMETER) { + maxFrame += 1; + } + + if (channel->getChannelType() == SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR) { + maxFrame += 2; + } + } + + if (element->getSecondaryChannel()) { + auto channel = element->getSecondaryChannel(); + if (channel->getChannelType() == SUPLA_CHANNELTYPE_PRESSURESENSOR) { + maxFrame += 1; + } + } + } + + return maxFrame; +} \ No newline at end of file diff --git a/src/GUIGenericCommon.h b/src/GUIGenericCommon.h index 3ed890bc..7fcd07c8 100644 --- a/src/GUIGenericCommon.h +++ b/src/GUIGenericCommon.h @@ -10,4 +10,9 @@ #include INCLUDE_FILE(UI_LANGUAGE) #endif +#include "SuplaDeviceGUI.h" + +uint8_t *HexToBytes(String _value); +int getNumberChannels(); + #endif // GUI_GENERIC_COMMON_H diff --git a/src/Markup.cpp b/src/Markup.cpp index fc52249e..0ba76b4a 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -25,7 +25,7 @@ void addFormHeaderEnd(String& html) { void addTextBox(String& html, const String& input_id, const String& name, - uint8_t value_key, + const String& value, const String& placeholder, int minlength, int maxlength, @@ -46,7 +46,6 @@ void addTextBox(String& html, } html += F("' value='"); - String value = String(ConfigManager->get(value_key)->getValue()); if (value != placeholder) { html += value; } @@ -73,11 +72,30 @@ void addTextBox(String& html, html += F(" "); } +void addTextBox(String& html, + const String& input_id, + const String& name, + uint8_t value_key, + const String& placeholder, + int minlength, + int maxlength, + bool required, + bool readonly, + bool password) { + String value = String(ConfigManager->get(value_key)->getValue()); + return addTextBox(html, input_id, name, value, "", minlength, maxlength, required, readonly, password); +} + void addTextBox( String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required, bool readonly) { return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, readonly, false); } +void addTextBox( + String& html, const String& input_id, const String& name, const String& value, int minlength, int maxlength, bool required, bool readonly) { + return addTextBox(html, input_id, name, value, "", minlength, maxlength, required, readonly, false); +} + void addTextBoxPassword(String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required) { return addTextBox(html, input_id, name, value_key, "", minlength, maxlength, required, false, true); } diff --git a/src/Markup.h b/src/Markup.h index 582d59fa..b06c2556 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -9,6 +9,17 @@ void addFormEnd(String& html); void addFormHeader(String& html, const String& name = "\n"); void addFormHeaderEnd(String& html); +void addTextBox(String& html, + const String& input_id, + const String& name, + const String& value, + const String& placeholder, + int minlength, + int maxlength, + bool required, + bool readonly = false, + bool password = false); + void addTextBox(String& html, const String& input_id, const String& name, @@ -19,8 +30,13 @@ void addTextBox(String& html, bool required, bool readonly = false, bool password = false); + void addTextBox( String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required, bool readonly = false); + +void addTextBox( + String& html, const String& input_id, const String& name, const String& value, int minlength, int maxlength, bool required, bool readonly = false); + void addTextBoxPassword(String& html, const String& input_id, const String& name, uint8_t value_key, int minlength, int maxlength, bool required); void addNumberBox(String& html, const String& input_id, const String& name, uint8_t value_key, uint16_t max); diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index fd04ca0c..143de405 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -624,12 +624,12 @@ void SuplaConfigESP::factoryReset(bool forceReset) { ConfigManager->set(key, "0,0,0,0,0"); } - for (nr = 0; nr <= MAX_DS18B20; nr++) { + /* for (nr = 0; nr <= MAX_DS18B20; nr++) { key = KEY_DS + nr; ConfigManager->set(key, ""); key = KEY_DS_NAME + nr; ConfigManager->set(key, ""); - } + }*/ ConfigManager->save(); diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 1b999582..77b3f46a 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -53,15 +53,6 @@ int ConfigOption::getValueInt() { return atoi(_value); } -uint8_t *ConfigOption::getValueBin(size_t size) { - uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t) * (size)); - - for (int i = 0; i < size; i++) { - sscanf(&_value[i * 2], "%2hhx", &buffer[i]); - } - return buffer; -} - const char *ConfigOption::getValueHex(size_t size) { char *buffer = (char *)malloc(sizeof(char) * (size * 2)); int a, b; @@ -118,6 +109,22 @@ const String ConfigOption::replaceElement(int index, int newvalue) { return table; } +const String ConfigOption::replaceElement(int index, const char *newvalue) { + int lenght = SETTINGSCOUNT; + String table; + for (int i = 0; i <= lenght; i++) { + if (i == index) { + table += newvalue; + } + else { + table += this->getElement(i); + } + if (i < lenght - 1) + table += SEPARATOR; + } + return table; +} + void ConfigOption::setValue(const char *value) { if (value != NULL) { size_t size = getLength(); @@ -161,19 +168,14 @@ SuplaConfigManager::SuplaConfigManager() { for (nr = 0; nr <= 17; nr++) { key = KEY_GPIO + nr; - this->addKey(key, "0,0,0,0,0,0", 28); + this->addKey(key, "0,0,0,0,0,0", SETTINGSCOUNT * 2); } this->addKey(KEY_ACTIVE_SENSOR, "0,0,0,0,0", 16); this->addKey(KEY_BOARD, "0", 2); this->addKey(KEY_CFG_MODE, "0", 2); - - for (nr = 0; nr <= MAX_DS18B20; nr++) { - key = KEY_DS + nr; - this->addKey(key, MAX_DS18B20_ADDRESS_HEX); - key = KEY_DS_NAME + nr; - this->addKey(key, MAX_DS18B20_NAME); - } + this->addKey(KEY_ADDR_DS18B20, MAX_DS18B20_ADDRESS_HEX * MAX_DS18B20); + this->addKey(KEY_NAME_SENSOR, MAX_DS18B20_NAME * MAX_DS18B20); this->load(); // switch (this->load()) { @@ -394,6 +396,17 @@ bool SuplaConfigManager::setElement(uint8_t key, int index, int newvalue) { return false; } +bool SuplaConfigManager::setElement(uint8_t key, int index, const char *newvalue) { + for (int i = 0; i < _optionCount; i++) { + if (key == _options[i]->getKey()) { + String data = _options[i]->replaceElement(index, newvalue); + _options[i]->setValue(data.c_str()); + return true; + } + } + return false; +} + void SuplaConfigManager::setGUIDandAUTHKEY() { if (strcmp(this->get(KEY_SUPLA_GUID)->getValue(), "") != 0 || strcmp(this->get(KEY_SUPLA_AUTHKEY)->getValue(), "") != 0) { return; diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index 10eb2dde..db8f15b9 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -43,7 +43,8 @@ #define MAX_TYPE_BUTTON 4 #define MAX_MONOSTABLE_TRIGGER 1 #define MAX_FUNCTION 1 -#define MAX_DS18B20 10 + +#define MAX_DS18B20 20 #define MAX_GPIO 17 enum _key @@ -70,9 +71,12 @@ enum _key KEY_ACTIVE_SENSOR, KEY_BOARD, KEY_CFG_MODE, + KEY_ADDR_DS18B20, + KEY_NAME_SENSOR, + KEY_GPIO, - KEY_DS = KEY_GPIO + MAX_GPIO + MAX_DS18B20, - KEY_DS_NAME = KEY_DS + MAX_DS18B20 + //KEY_DS = KEY_GPIO + MAX_GPIO + MAX_DS18B20, + //KEY_DS_NAME = KEY_DS + MAX_DS18B20 }; //#define GPIO "GPIO" @@ -140,14 +144,15 @@ class ConfigOption { uint8_t getKey(); const char *getValue(); int getValueInt(); - uint8_t *getValueBin(size_t size); const char *getValueHex(size_t size); int getValueElement(int element); int getLength(); void setValue(const char *value); const String getElement(int index); + uint8_t getElement(int index, size_t size); const String replaceElement(int index, int value); + const String replaceElement(int index, const char *newvalue); private: uint8_t _key; @@ -170,6 +175,7 @@ class SuplaConfigManager { ConfigOption *get(uint8_t key); bool set(uint8_t key, const char *value); bool setElement(uint8_t key, int index, int newvalue); + bool setElement(uint8_t key, int index, const char *newvalue); bool isDeviceConfigured(); void setGUIDandAUTHKEY(); diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index 9f1cf5c1..bcfa68e7 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -27,7 +27,6 @@ Supla::Eeprom eeprom(STORAGE_OFFSET); namespace Supla { namespace GUI { void begin() { - #ifdef DEBUG_MODE new Supla::Sensor::EspFreeHeap(); #endif @@ -89,9 +88,8 @@ void addRelayButton(int pinRelay, int pinButton, bool highIsOn) { void addDS18B20MultiThermometer(int pinNumber) { if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { for (int i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); ++i) { - uint8_t ds_key = KEY_DS + i; - sensorDS.push_back(new DS18B20(pinNumber, ConfigManager->get(ds_key)->getValueBin(MAX_DS18B20_ADDRESS))); - supla_log(LOG_DEBUG, "Index %d - address %s", i, ConfigManager->get(ds_key)->getValue()); + sensorDS.push_back(new DS18B20(pinNumber, HexToBytes(ConfigManager->get(KEY_ADDR_DS18B20)->getElement(i)))); + supla_log(LOG_DEBUG, "Index %d - address %s", i, ConfigManager->get(KEY_ADDR_DS18B20)->getElement(i).c_str()); } } else { diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index 842d200e..66340988 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -3,7 +3,6 @@ #ifdef SUPLA_OLED -uint8_t* framesCountSensor; uint8_t* chanelSensor; String getTempString(double temperature) { @@ -33,29 +32,22 @@ String getPressureString(double pressure) { } } -uint8_t getFramesCountSensor(OLEDDisplayUiState* state) { - if (state->frameState) { - return framesCountSensor[state->currentFrame]; - } - return 0; -} - -int32_t readRssi(void) { - int32_t rssi = WiFi.RSSI(); +int getQuality() { if (WiFi.status() != WL_CONNECTED) return -1; - if (rssi <= -100) + int dBm = WiFi.RSSI(); + if (dBm <= -100) return 0; - if (rssi >= -50) + if (dBm >= -50) return 100; - return (2 * (rssi + 100)); + return 2 * (dBm + 100); } void displayUiSignal(OLEDDisplay* display) { int x = display->getWidth() - 17; int y = 0; - int value = readRssi(); - // clear area only + int value = getQuality(); + display->setColor(BLACK); display->fillRect(x, y, x + 46, 16); display->setColor(WHITE); @@ -66,23 +58,15 @@ void displayUiSignal(OLEDDisplay* display) { else { if (value > 0) display->fillRect(x, y + 6, 3, 4); - else - display->drawRect(x, y + 6, 3, 4); if (value >= 25) display->fillRect(x + 4, y + 4, 3, 6); - else - display->drawRect(x + 4, y + 4, 3, 6); if (value >= 50) display->fillRect(x + 8, y + 2, 3, 8); - else - display->drawRect(x + 8, y + 2, 3, 8); if (value >= 75) display->fillRect(x + 12, y, 3, 10); - else - display->drawRect(x + 12, y, 3, 10); } } @@ -164,22 +148,23 @@ void displayUiTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16 } else { display->drawXbm(x + 0, y + drawHeightIcon, TEMP_WIDTH, TEMP_HEIGHT, temp_bits); - display->setFont(ArialMT_Plain_10); - if (name != NULL) { - display->drawString(x + TEMP_WIDTH + 20, y + display->getHeight() / 2 - 15, name); - } temp_width = TEMP_WIDTH + 10; temp_height = TEMP_HEIGHT; } + if (name != NULL) { + display->setFont(ArialMT_Plain_10); + display->drawString(x + TEMP_WIDTH + 20, y + display->getHeight() / 2 - 15, name); + } + display->setFont(ArialMT_Plain_24); display->drawString(x + temp_width, y + drawStringIcon, getTempString(temp)); display->setFont(ArialMT_Plain_16); display->drawString(x + temp_width + (getTempString(temp).length() * 12), y + drawStringIcon, "ºC"); } -void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity) { +void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity, const String& name) { uint8_t humidity_width, humidity_height; int drawHeightIcon = display->getHeight() / 2 - 10; @@ -198,13 +183,18 @@ void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x humidity_height = HUMIDITY_HEIGHT; } + if (name != NULL) { + display->setFont(ArialMT_Plain_10); + display->drawString(x + TEMP_WIDTH + 20, y + display->getHeight() / 2 - 15, name); + } + display->setFont(ArialMT_Plain_24); display->drawString(x + humidity_width, y + drawStringIcon, getHumidityString(humidity)); display->setFont(ArialMT_Plain_16); display->drawString(x + humidity_width + (getHumidityString(humidity).length() * 12), y + drawStringIcon, "%"); } -void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure) { +void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure, const String& name) { uint8_t pressure_width, pressure_height; int drawHeightIcon = display->getHeight() / 2 - 10; @@ -219,13 +209,18 @@ void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t } else { display->drawXbm(x + 0, y + drawHeightIcon, PRESSURE_WIDTH, PRESSURE_HEIGHT, pressure_bits); - pressure_width = PRESSURE_WIDTH + 15; + pressure_width = PRESSURE_WIDTH + 10; pressure_height = PRESSURE_HEIGHT; } + if (name != NULL) { + display->setFont(ArialMT_Plain_10); + display->drawString(x + TEMP_WIDTH + 20, y + display->getHeight() / 2 - 15, name); + } + display->setFont(ArialMT_Plain_24); display->drawString(x + pressure_width, y + drawStringIcon, getPressureString(pressure)); - display->setFont(ArialMT_Plain_10); + display->setFont(ArialMT_Plain_16); display->drawString(x + pressure_width + (getPressureString(pressure).length() * 14), y + drawStringIcon, "hPa"); } @@ -235,13 +230,8 @@ void displayTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t auto channel = element->getChannel(); if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { double lastTemperature = channel->getValueDouble(); - - if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() >= state->currentFrame) { - displayUiTemperature(display, state, x, y, lastTemperature, ConfigManager->get(KEY_DS_NAME + state->currentFrame)->getValue()); - } - else { - displayUiTemperature(display, state, x, y, lastTemperature); - } + String name = ConfigManager->get(KEY_NAME_SENSOR)->getElement(state->currentFrame); + displayUiTemperature(display, state, x, y, lastTemperature, name); } } } @@ -253,7 +243,8 @@ void displayDoubleTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, i auto channel = element->getChannel(); if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { double lastTemperature = channel->getValueDoubleFirst(); - displayUiTemperature(display, state, x, y, lastTemperature); + String name = ConfigManager->get(KEY_NAME_SENSOR)->getElement(state->currentFrame); + displayUiTemperature(display, state, x, y, lastTemperature, name); } } } @@ -265,7 +256,8 @@ void displayDoubleHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int1 auto channel = element->getChannel(); if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { double lastHumidit = channel->getValueDoubleSecond(); - displaUiHumidity(display, state, x, y, lastHumidit); + String name = ConfigManager->get(KEY_NAME_SENSOR)->getElement(state->currentFrame); + displaUiHumidity(display, state, x, y, lastHumidit, name); } } } @@ -277,7 +269,8 @@ void displayPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, auto channel = element->getSecondaryChannel(); if (channel->getChannelNumber() == chanelSensor[state->currentFrame]) { double lastPressure = channel->getValueDouble(); - displayUiPressure(display, state, x, y, lastPressure); + String name = ConfigManager->get(KEY_NAME_SENSOR)->getElement(state->currentFrame); + displayUiPressure(display, state, x, y, lastPressure, name); } } } @@ -300,15 +293,17 @@ SuplaOled::SuplaOled() { ui = new OLEDDisplayUi(display); overlays[0] = {msOverlay}; - int maxFrame = getMaxFrame(); + int maxFrame = getNumberChannels(); - frames = (FrameCallback*)malloc(sizeof(FrameCallback) * maxFrame); - chanelSensor = (uint8_t*)malloc(sizeof(uint8_t) * maxFrame); + if (maxFrame == 0) + maxFrame = 1; + + frames = new FrameCallback[maxFrame]; + chanelSensor = new uint8_t[maxFrame]; for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { if (element->getChannel()) { auto channel = element->getChannel(); - Serial.println(channel->getChannelType()); if (channel->getChannelType() == SUPLA_CHANNELTYPE_THERMOMETER) { frames[frameCount] = {displayTemperature}; @@ -397,34 +392,4 @@ void SuplaOled::runAction(int event, int action) { oledON = true; } } - -int SuplaOled::getMaxFrame() { - int maxFrame = 0; - for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { - if (element->getChannel()) { - auto channel = element->getChannel(); - Serial.println(channel->getChannelType()); - - if (channel->getChannelType() == SUPLA_CHANNELTYPE_THERMOMETER) { - maxFrame += 1; - } - - if (channel->getChannelType() == SUPLA_CHANNELTYPE_HUMIDITYANDTEMPSENSOR) { - maxFrame += 2; - } - } - - if (element->getSecondaryChannel()) { - auto channel = element->getSecondaryChannel(); - if (channel->getChannelType() == SUPLA_CHANNELTYPE_PRESSURESENSOR) { - maxFrame += 1; - } - } - } - - if (maxFrame == 0) - maxFrame = 1; - - return maxFrame; -} #endif \ No newline at end of file diff --git a/src/SuplaOled.h b/src/SuplaOled.h index 2f9987e6..4d7532e1 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -27,8 +27,7 @@ enum _OLED String getTempString(double temperature); String getHumidityString(double humidity); String getPressureString(double pressure); -uint8_t getFramesCountSensor(OLEDDisplayUiState* state); -int32_t readRssi(void); +int32_t getQuality(); void msOverlay(OLEDDisplay* display, OLEDDisplayUiState* state); @@ -38,8 +37,8 @@ void displayUiSuplaStatus(OLEDDisplay* display); void displayUiConfigMode(OLEDDisplay* display); void displayUiBlank(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); void displayUiTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double temp, const String& name = "\n"); -void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity); -void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure); +void displaUiHumidity(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double humidity, const String& name = "\n"); +void displayUiPressure(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y, double pressure, const String& name = "\n"); void displayTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); void displayDoubleTemperature(OLEDDisplay* display, OLEDDisplayUiState* state, int16_t x, int16_t y); @@ -54,7 +53,6 @@ class SuplaOled : public Supla::Triggerable, public Supla::Element { private: void iterateAlways(); void runAction(int event, int action); - int getMaxFrame(); OLEDDisplay* display; OLEDDisplayUi* ui; @@ -64,8 +62,6 @@ class SuplaOled : public Supla::Triggerable, public Supla::Element { OverlayCallback overlays[1]; int overlaysCount = 1; - int count = 0; - unsigned long timeLastChangeOled = millis(); bool oledON = true; }; diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 3234d52e..43cbfcb6 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -98,18 +98,18 @@ void SuplaWebPageSensor::handleDSSave() { return WebServer->httpServer.requestAuthentication(); } for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { - uint8_t ds_key = KEY_DS + i; - uint8_t ds_name_key = KEY_DS_NAME + i; + String dsAddr = INPUT_DS18B20_ADDR; + String dsName = INPUT_DS18B20_NAME; + dsAddr += i; + dsName += i; - String ds = F("dschlid"); - String ds_name = F("dsnameid"); - ds += i; - ds_name += i; + ConfigManager->setElement(KEY_ADDR_DS18B20, i, WebServer->httpServer.arg(dsAddr).c_str()); - ConfigManager->set(ds_key, WebServer->httpServer.arg(ds).c_str()); - ConfigManager->set(ds_name_key, WebServer->httpServer.arg(ds_name).c_str()); + if (strcmp(WebServer->httpServer.arg(dsName).c_str(), "") != 0) { + ConfigManager->setElement(KEY_NAME_SENSOR, i, WebServer->httpServer.arg(dsName).c_str()); + } - Supla::GUI::sensorDS[i]->setDeviceAddress(ConfigManager->get(ds_key)->getValueBin(MAX_DS18B20_ADDRESS)); + Supla::GUI::sensorDS[i]->setDeviceAddress(HexToBytes(ConfigManager->get(KEY_ADDR_DS18B20)->getElement(i))); } switch (ConfigManager->save()) { @@ -139,7 +139,7 @@ String SuplaWebPageSensor::supla_webpage_search(int save) { content += SuplaSaveResult(save); content += SuplaJavaScript(PATH_MULTI_DS); content += F("
"); - if (ConfigESP->getGpio(FUNCTION_DS18B20) < OFF_GPIO || !Supla::GUI::sensorDS.empty()) { + if (ConfigESP->getGpio(FUNCTION_DS18B20) < OFF_GPIO) { content += F(""); @@ -174,7 +174,8 @@ String SuplaWebPageSensor::supla_webpage_search(int save) { address[7]); supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr); - content += F(""); - content += F("setElement(KEY_ACTIVE_SENSOR, SENSOR_OLED, WebServer->httpServer.arg(input).toInt()); } + + for (uint8_t i = 0; i < getNumberChannels(); i++) { + input = INPUT_DS18B20_NAME; + input += i; + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") != 0) { + ConfigManager->setElement(KEY_NAME_SENSOR, i, WebServer->httpServer.arg(input).c_str()); + } + } #endif #ifdef SUPLA_MCP23017 @@ -518,6 +526,18 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); addFormHeader(page); addListBox(page, INPUT_OLED, F("OLED"), OLED_P, 4, selected); + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { + String name, sensorName, input; + + for (uint8_t i = 0; i < getNumberChannels(); i++) { + sensorName = String(ConfigManager->get(KEY_NAME_SENSOR)->getElement(i)); + input = INPUT_DS18B20_NAME; + input += i; + name = F("Ekran "); + name += i + 1; + addTextBox(page, input, name, sensorName, 0, MAX_DS18B20_NAME, false); + } + } addFormHeaderEnd(page); #endif diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 362c6df7..152ccab2 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -81,6 +81,9 @@ class SuplaWebPageSensor { void createWebPageSensor(); #ifdef SUPLA_DS18B20 +#define INPUT_DS18B20_ADDR "dsaddr" +#define INPUT_DS18B20_NAME "dsname" + void handleSearchDS(); void handleDSSave(); void showDS18B20(String& content, bool readonly = false); From 79e22cba7e42f715a07d261ce04e40c98a95639c Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sun, 17 Jan 2021 11:05:50 +0100 Subject: [PATCH 104/233] MCP23017 dla SUPLA_LIMIT_SWITCH --- platformio.ini | 2 - src/Markup.cpp | 25 ++++++-- src/Markup.h | 2 + src/SuplaCommonPROGMEM.cpp | 22 ------- src/SuplaCommonPROGMEM.h | 12 ++-- src/SuplaConfigESP.cpp | 113 ++++++++++++++++++++++++---------- src/SuplaConfigESP.h | 6 +- src/SuplaWebPageControl.cpp | 117 ++++++++++++++++++++++++++++-------- src/SuplaWebPageControl.h | 8 +++ src/SuplaWebServer.cpp | 39 +++++++----- src/SuplaWebServer.h | 4 +- 11 files changed, 239 insertions(+), 111 deletions(-) delete mode 100644 src/SuplaCommonPROGMEM.cpp diff --git a/platformio.ini b/platformio.ini index ffe3d704..bd7f22d3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -63,7 +63,6 @@ lib_deps = datacute/DoubleResetDetector@^1.0.3 closedcube/ClosedCube SHT31D@^1.5.1 adafruit/Adafruit Si7021 Library@^1.3.0 - ;thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.1.0 xoseperez/HLW8012 @ ^1.1.1 extra_scripts = tools/copy_files.py @@ -126,7 +125,6 @@ board = nodemcuv2 board_build.ldscript = eagle.flash.4m1m.ld build_flags = ${common.build_flags} -D DEBUG_MODE -build_unflags = -D SUPLA_IMPULSE_COUNTER [env:GUI_Generic_blank] board = nodemcuv2 diff --git a/src/Markup.cpp b/src/Markup.cpp index 0ba76b4a..703e2e5d 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -83,7 +83,7 @@ void addTextBox(String& html, bool readonly, bool password) { String value = String(ConfigManager->get(value_key)->getValue()); - return addTextBox(html, input_id, name, value, "", minlength, maxlength, required, readonly, password); + return addTextBox(html, input_id, name, value, placeholder, minlength, maxlength, required, readonly, password); } void addTextBox( @@ -160,10 +160,28 @@ void addListGPIOBox(String& html, const String& input_id, const String& name, ui html += F(""); } +void addListMCP23017GPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { + if (nr == 1) { + uint8_t address = ConfigESP->getAdressMCP23017(nr, function); + addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 4, address); + } + + html += F(""); + addListMCP23017GPIO(html, input_id, function, nr); + html += F(""); +} + void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const String& name, uint8_t function, const String& url, uint8_t nr) { if (nr == 1) { - uint8_t address = ConfigESP->getAdressMCP23017(function); - addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 3, address); + uint8_t address = ConfigESP->getAdressMCP23017(nr, function); + addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 4, address); } html += F(""); @@ -305,7 +323,6 @@ void addListMCP23017GPIO(String& html, const String& input_id, uint8_t function, html += F("'>"); uint8_t selected = ConfigESP->getGpioMCP23017(nr, function); - uint8_t address = ConfigESP->getAdressMCP23017(function); for (uint8_t suported = 0; suported < sizeof(GPIO_MCP23017_P) / sizeof(GPIO_MCP23017_P[0]); suported++) { if (ConfigESP->checkBusyGpioMCP23017(suported, function) || selected == suported) { diff --git a/src/Markup.h b/src/Markup.h index b06c2556..f04c9200 100644 --- a/src/Markup.h +++ b/src/Markup.h @@ -47,6 +47,8 @@ void addLinkBox(String& html, const String& name, const String& url); void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr = 0); +void addListMCP23017GPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr); + void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const String& name, uint8_t function, const String& url, uint8_t nr = 0); void addListGPIOLinkBox(String& html, const String& input_id, const String& name, uint8_t function, const String& url, uint8_t nr = 0); diff --git a/src/SuplaCommonPROGMEM.cpp b/src/SuplaCommonPROGMEM.cpp deleted file mode 100644 index 25415725..00000000 --- a/src/SuplaCommonPROGMEM.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "SuplaCommonPROGMEM.h" -#include "SuplaTemplateBoard.h" - -String StateString(uint8_t adr) { - return PGMT(STATE_P[adr]); -} - -String LevelString(uint8_t nr) { - return PGMT(LEVEL_P[nr]); -} - -String MemoryString(uint8_t nr) { - return PGMT(MEMORY_P[nr]); -} - -String TriggerString(uint8_t nr) { - return PGMT(TRIGGER_P[nr]); -} - -String BoardString(uint8_t board) { - return PGMT(BOARD_P[board]); -} diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index ffab8c05..6ce2578e 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -50,7 +50,7 @@ const char HTTP_LOGO[] PROGMEM = "102.1,188.6z " "M167.7,88.5c-1,0-2.1,0.1-3.1,0.3c-9,1.7-14.2,10.6-10.8,18.6c2.9,6.8,11.4,10.3,19,7.8c7.1-2.3,11.1-9.1,9.6-15.9C180.9,93,174.8,88.5,167.7,88.5z'/" ">"; -const char HTTP_SUMMARY[] PROGMEM = "

{h}

LAST STATE: {s}
Firmware: SuplaDevice {v}
GUID: {g}
MAC: {m}
\n"; +const char HTTP_SUMMARY[] PROGMEM = "

{h}

LAST STATE: {s}
Firmware: SuplaDevice {v}
GUID: {g}
MAC: {m}
Free Mem: {f}KB
\n"; const char HTTP_COPYRIGHT[] PROGMEM = "https://forum.supla.org/\n"; @@ -111,7 +111,9 @@ const char* const SHT3x_P[] PROGMEM = {OFF, ADR44, ADR45, ADR44_ADR45}; const char ADR20[] PROGMEM = "0x20"; const char ADR21[] PROGMEM = "0x21"; -const char* const MCP23017_P[] PROGMEM = {ADR20, ADR21, OFF}; +const char ADR22[] PROGMEM = "0x22"; +const char ADR23[] PROGMEM = "0x23"; +const char* const MCP23017_P[] PROGMEM = {ADR20, ADR21, ADR22, OFF}; const char* const STATE_P[] PROGMEM = {OFF, ON}; @@ -141,10 +143,4 @@ const char SSD1306_WEMOS_SHIELD[] PROGMEM = "SSD1306 - 0,66'' WEMOS OLED shield" const char* const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106, SSD1306_WEMOS_SHIELD}; #endif -String StateString(uint8_t adr); -String LevelString(uint8_t nr); -String MemoryString(uint8_t nr); -String TriggerString(uint8_t nr); -String BoardString(uint8_t board); - #endif // SuplaCommonPROGMEM_h diff --git a/src/SuplaConfigESP.cpp b/src/SuplaConfigESP.cpp index 143de405..1ceba4e9 100644 --- a/src/SuplaConfigESP.cpp +++ b/src/SuplaConfigESP.cpp @@ -253,7 +253,7 @@ int SuplaConfigESP::getGpio(int nr, int function) { //"Pin 100 - 115" // Pin 116 - 131" if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt()) { - switch (getAdressMCP23017(function)) { + switch (getAdressMCP23017(nr, function)) { case 0: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function && ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { @@ -266,6 +266,12 @@ int SuplaConfigESP::getGpio(int nr, int function) { return gpio + 100 + 16; } break; + case 2: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function && + ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + return gpio + 100 + 16 + 16; + } + break; } } } @@ -281,7 +287,7 @@ int SuplaConfigESP::getLevel(int nr, int function) { } } - switch (getAdressMCP23017(function)) { + switch (getAdressMCP23017(nr, function)) { case 0: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { @@ -298,6 +304,14 @@ int SuplaConfigESP::getLevel(int nr, int function) { } } break; + case 2: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + return ConfigManager->get(key)->getElement(LEVEL).toInt(); + ; + } + } + break; } } return OFF_GPIO; @@ -312,7 +326,7 @@ int SuplaConfigESP::getMemory(int nr, int function) { } } - switch (getAdressMCP23017(function)) { + switch (getAdressMCP23017(nr, function)) { case 0: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { @@ -329,6 +343,14 @@ int SuplaConfigESP::getMemory(int nr, int function) { } } break; + case 2: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + return ConfigManager->get(key)->getElement(MEMORY).toInt(); + ; + } + } + break; } } return OFF_GPIO; @@ -344,7 +366,7 @@ int SuplaConfigESP::getAction(int nr, int function) { } } - switch (getAdressMCP23017(function)) { + switch (getAdressMCP23017(nr, function)) { case 0: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { @@ -361,6 +383,14 @@ int SuplaConfigESP::getAction(int nr, int function) { } } break; + case 2: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + return ConfigManager->get(key)->getElement(ACTION).toInt(); + ; + } + } + break; } } return OFF_GPIO; @@ -455,17 +485,10 @@ bool SuplaConfigESP::checkBusyGpioMCP23017(uint8_t gpio, uint8_t function) { } else { uint8_t key = KEY_GPIO + gpio; - switch (getAdressMCP23017(function)) { - case 0: - if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() != FUNCTION_OFF) { - return false; - } - break; - case 1: - if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() != FUNCTION_OFF) { - return false; - } - break; + uint8_t address = ConfigESP->getAdressMCP23017(1, function); + + if (ConfigManager->get(key)->getElement(getFunctionMCP23017(address)).toInt() != FUNCTION_OFF) { + return false; } } return true; @@ -475,7 +498,7 @@ uint8_t SuplaConfigESP::getGpioMCP23017(uint8_t nr, uint8_t function) { for (uint8_t gpio = 0; gpio <= OFF_GPIO; gpio++) { uint8_t key = KEY_GPIO + gpio; - switch (getAdressMCP23017(function)) { + switch (getAdressMCP23017(nr, function)) { case 0: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { @@ -490,33 +513,45 @@ uint8_t SuplaConfigESP::getGpioMCP23017(uint8_t nr, uint8_t function) { } } break; + case 2: + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + return gpio; + } + } + break; } } return OFF_GPIO; } -uint8_t SuplaConfigESP::getAdressMCP23017(uint8_t function) { +uint8_t SuplaConfigESP::getAdressMCP23017(uint8_t nr, uint8_t function) { for (uint8_t gpio = 0; gpio <= OFF_GPIO; gpio++) { uint8_t key = KEY_GPIO + gpio; - if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() != 0) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { return 0; } } - if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() != 0) { + if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { return 1; } } + if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { + if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { + return 2; + } + } } - return 2; + return OFF_MCP23017; } void SuplaConfigESP::setGpioMCP23017(uint8_t gpio, uint8_t adress, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory) { uint8_t key = KEY_GPIO + gpio; uint8_t _gpio = ConfigESP->getGpioMCP23017(nr, function); - ConfigESP->clearGpioMCP23017(_gpio, function); + ConfigESP->clearGpioMCP23017(_gpio, nr, function); if (ConfigManager->get(key)->getElement(FUNCTION).toInt() == FUNCTION_OFF) { ConfigManager->setElement(key, LEVEL, 0); @@ -538,15 +573,21 @@ void SuplaConfigESP::setGpioMCP23017(uint8_t gpio, uint8_t adress, uint8_t nr, u ConfigManager->setElement(key, MCP23017_NR_2, nr); ConfigManager->setElement(key, MCP23017_FUNCTION_2, function); break; + case 2: + ConfigManager->setElement(key, MCP23017_NR_3, nr); + ConfigManager->setElement(key, MCP23017_FUNCTION_3, function); + break; } } -void SuplaConfigESP::clearGpioMCP23017(uint8_t gpio, uint8_t function) { +void SuplaConfigESP::clearGpioMCP23017(uint8_t gpio, uint8_t nr, uint8_t function) { uint8_t key = KEY_GPIO + gpio; - uint8_t adress = getAdressMCP23017(function); + uint8_t adress = getAdressMCP23017(nr, function); - ConfigManager->setElement(key, getNrMCP23017(adress), 0); - ConfigManager->setElement(key, getFunctionMCP23017(adress), FUNCTION_OFF); + if (adress != OFF_MCP23017) { + ConfigManager->setElement(key, getNrMCP23017(adress), 0); + ConfigManager->setElement(key, getFunctionMCP23017(adress), FUNCTION_OFF); + } } void SuplaConfigESP::clearFunctionGpio(uint8_t function) { @@ -568,8 +609,11 @@ uint8_t SuplaConfigESP::getFunctionMCP23017(uint8_t adress) { case 1: return MCP23017_FUNCTION_2; break; + case 2: + return MCP23017_FUNCTION_3; + break; } - return OFF_GPIO; + return FUNCTION_OFF; } uint8_t SuplaConfigESP::getNrMCP23017(uint8_t adress) { @@ -580,8 +624,11 @@ uint8_t SuplaConfigESP::getNrMCP23017(uint8_t adress) { case 1: return MCP23017_NR_2; break; + case 2: + return MCP23017_NR_3; + break; } - return OFF_GPIO; + return FUNCTION_OFF; } void SuplaConfigESP::factoryReset(bool forceReset) { @@ -624,12 +671,12 @@ void SuplaConfigESP::factoryReset(bool forceReset) { ConfigManager->set(key, "0,0,0,0,0"); } - /* for (nr = 0; nr <= MAX_DS18B20; nr++) { - key = KEY_DS + nr; - ConfigManager->set(key, ""); - key = KEY_DS_NAME + nr; - ConfigManager->set(key, ""); - }*/ + /* for (nr = 0; nr <= MAX_DS18B20; nr++) { + key = KEY_DS + nr; + ConfigManager->set(key, ""); + key = KEY_DS_NAME + nr; + ConfigManager->set(key, ""); + }*/ ConfigManager->save(); diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index 7a0c9ae9..9a526026 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -37,6 +37,8 @@ enum _ConfigMode FACTORYRESET }; +#define OFF_MCP23017 3 + typedef struct { int status; const char *msg; @@ -97,9 +99,9 @@ class SuplaConfigESP : public Supla::Triggerable, public Supla::Element { bool checkBusyGpioMCP23017(uint8_t gpio, uint8_t function); uint8_t getGpioMCP23017(uint8_t nr, uint8_t function); - uint8_t getAdressMCP23017(uint8_t function); + uint8_t getAdressMCP23017(uint8_t nr, uint8_t function); void setGpioMCP23017(uint8_t gpio, uint8_t adress, uint8_t nr, uint8_t function, uint8_t level, uint8_t memory); - void clearGpioMCP23017(uint8_t gpio, uint8_t function); + void clearGpioMCP23017(uint8_t gpio, uint8_t nr, uint8_t function); void clearFunctionGpio(uint8_t function); uint8_t getFunctionMCP23017(uint8_t adress); uint8_t getNrMCP23017(uint8_t adress); diff --git a/src/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp index 28411956..18131c29 100644 --- a/src/SuplaWebPageControl.cpp +++ b/src/SuplaWebPageControl.cpp @@ -28,6 +28,15 @@ void SuplaWebPageControl::createWebPageControl() { WebServer->httpServer.on(path, std::bind(&SuplaWebPageControl::handleButtonSaveSet, this)); } #endif + +#ifdef SUPLA_LIMIT_SWITCH + path = PATH_START; + path += PATH_SWITCH; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageControl::handleLimitSwitch, this)); + path = PATH_START; + path += PATH_SAVE_SWITCH; + WebServer->httpServer.on(path, std::bind(&SuplaWebPageControl::handleLimitSwitchSave, this)); +#endif } void SuplaWebPageControl::handleControl() { @@ -69,20 +78,6 @@ void SuplaWebPageControl::handleControlSave() { } #endif -#ifdef SUPLA_LIMIT_SWITCH - last_value = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); - for (nr = 1; nr <= last_value; nr++) { - if (!WebServer->saveGPIO(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr, INPUT_MAX_LIMIT_SWITCH)) { - supla_webpage_control(6); - return; - } - } - - if (strcmp(WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str(), "") != 0) { - ConfigManager->set(KEY_MAX_LIMIT_SWITCH, WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str()); - } -#endif - switch (ConfigManager->save()) { case E_CONFIG_OK: supla_webpage_control(1); @@ -123,15 +118,6 @@ void SuplaWebPageControl::supla_webpage_control(int save) { addFormHeaderEnd(webContentBuffer); #endif -#ifdef SUPLA_LIMIT_SWITCH - addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR_LIMIT_SWITCH)); - addNumberBox(webContentBuffer, INPUT_MAX_LIMIT_SWITCH, S_QUANTITY, KEY_MAX_LIMIT_SWITCH, ConfigESP->countFreeGpio(FUNCTION_LIMIT_SWITCH)); - for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { - addListGPIOBox(webContentBuffer, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); - } - addFormHeaderEnd(webContentBuffer); -#endif - addButtonSubmit(webContentBuffer, S_SAVE); addFormEnd(webContentBuffer); addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); @@ -226,7 +212,90 @@ void SuplaWebPageControl::supla_webpage_button_set(int save) { addFormHeaderEnd(webContentBuffer); addButtonSubmit(webContentBuffer, S_SAVE); addFormEnd(webContentBuffer); - addButton(webContentBuffer, S_RETURN, PATH_RELAY); + addButton(webContentBuffer, S_RETURN, PATH_CONTROL); + + WebServer->sendContent(); +} +#endif + +#ifdef SUPLA_LIMIT_SWITCH +void SuplaWebPageControl::handleLimitSwitch() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + suplaWebpageLimitSwitch(0); +} + +void SuplaWebPageControl::handleLimitSwitchSave() { + if (ConfigESP->configModeESP == NORMAL_MODE) { + if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) + return WebServer->httpServer.requestAuthentication(); + } + + uint8_t nr, last_value; + + last_value = ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); + for (nr = 1; nr <= last_value; nr++) { + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + if (!WebServer->saveGpioMCP23017(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr, INPUT_MAX_LIMIT_SWITCH)) { + suplaWebpageLimitSwitch(6); + return; + } + } + else { + if (!WebServer->saveGPIO(INPUT_LIMIT_SWITCH_GPIO, FUNCTION_LIMIT_SWITCH, nr, INPUT_MAX_LIMIT_SWITCH)) { + suplaWebpageLimitSwitch(6); + return; + } + } + } + + if (strcmp(WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str(), "") != 0) { + ConfigManager->set(KEY_MAX_LIMIT_SWITCH, WebServer->httpServer.arg(INPUT_MAX_LIMIT_SWITCH).c_str()); + } + + switch (ConfigManager->save()) { + case E_CONFIG_OK: + suplaWebpageLimitSwitch(1); + break; + case E_CONFIG_FILE_OPEN: + suplaWebpageLimitSwitch(2); + break; + } +} + +void SuplaWebPageControl::suplaWebpageLimitSwitch(int save) { + uint8_t nr, countFreeGpio; + + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_SWITCH); + addForm(webContentBuffer, F("post"), PATH_SAVE_SWITCH); + + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR_LIMIT_SWITCH)); + + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + countFreeGpio = 16; + } + else { + countFreeGpio = ConfigESP->countFreeGpio(FUNCTION_LIMIT_SWITCH); + } + + addNumberBox(webContentBuffer, INPUT_MAX_LIMIT_SWITCH, S_QUANTITY, KEY_MAX_LIMIT_SWITCH, countFreeGpio); + + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_LIMIT_SWITCH)->getValueInt(); nr++) { + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt() != FUNCTION_OFF) { + addListMCP23017GPIOBox(webContentBuffer, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); + } + else { + addListGPIOBox(webContentBuffer, INPUT_LIMIT_SWITCH_GPIO, S_LIMIT_SWITCH, FUNCTION_LIMIT_SWITCH, nr); + } + } + addFormHeaderEnd(webContentBuffer); + + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); WebServer->sendContent(); } diff --git a/src/SuplaWebPageControl.h b/src/SuplaWebPageControl.h index c7043de2..c2443a5e 100644 --- a/src/SuplaWebPageControl.h +++ b/src/SuplaWebPageControl.h @@ -5,6 +5,8 @@ #include "SuplaDeviceGUI.h" #define PATH_CONTROL "control" +#define PATH_SWITCH "switch" +#define PATH_SAVE_SWITCH "saveswitch" #define PATH_SAVE_CONTROL "savecontrol" #define PATH_BUTTON_SET "setbutton" #define PATH_SAVE_BUTTON_SET "savesetbutton" @@ -29,6 +31,12 @@ class SuplaWebPageControl { void handleControl(); void handleControlSave(); +#ifdef SUPLA_LIMIT_SWITCH + void handleLimitSwitch(); + void handleLimitSwitchSave(); + void suplaWebpageLimitSwitch(int save); +#endif + #if (defined(SUPLA_BUTTON) && defined(SUPLA_RELAY)) || (defined(SUPLA_RSUPLA_BUTTONELAY) || defined(SUPLA_ROLLERSHUTTER)) void handleButtonSet(); void handleButtonSaveSet(); diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 242d0402..38163f35 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -218,6 +218,10 @@ String SuplaWebServer::deviceSettings(int save) { addButton(content, S_BUTTONS, PATH_CONTROL); #endif +#ifdef SUPLA_LIMIT_SWITCH + addButton(content, F("KONTAKTRON"), PATH_SWITCH); +#endif + #if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) addButton(content, S_SENSORS_1WIRE, PATH_1WIRE); #endif @@ -317,6 +321,7 @@ void SuplaWebServer::sendContent(const String& content) { summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); summary.replace("{m}", ConfigESP->getMacAddress(true)); + summary.replace("{f}", String(ESP.getFreeHeap() / 1024.0)); httpServer.sendContent(summary); httpServer.sendContent_P(HTTP_COPYRIGHT); @@ -371,6 +376,8 @@ void SuplaWebServer::sendContent() { summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); summary.replace("{m}", ConfigESP->getMacAddress(true)); + summary.replace("{f}", String(ESP.getFreeHeap() / 1024.0)); + httpServer.sendContent(summary); httpServer.sendContent_P(HTTP_COPYRIGHT); @@ -457,30 +464,34 @@ bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr return true; } -bool SuplaWebServer::saveGpioMCP23017(const String& input, uint8_t function, uint8_t nr, const String& input_max) { +bool SuplaWebServer::saveGpioMCP23017(const String& _input, uint8_t function, uint8_t nr, const String& input_max) { uint8_t key, address, addressInput, gpio, gpioInput, functionElementInput; - String _input = input + nr; + String input = _input + nr; - if (strcmp(WebServer->httpServer.arg(_input).c_str(), "") == 0) { + if (strcmp(WebServer->httpServer.arg(input).c_str(), "") == 0) { return true; } addressInput = WebServer->httpServer.arg(INPUT_ADRESS_MCP23017).toInt(); - functionElementInput = ConfigManager->get(key)->getElement(ConfigESP->getFunctionMCP23017(addressInput)).toInt(); - gpioInput = WebServer->httpServer.arg(_input).toInt(); + gpioInput = WebServer->httpServer.arg(input).toInt(); key = KEY_GPIO + gpioInput; + functionElementInput = ConfigManager->get(key)->getElement(ConfigESP->getFunctionMCP23017(addressInput)).toInt(); gpio = ConfigESP->getGpioMCP23017(nr, function); - address = ConfigESP->getAdressMCP23017(function); - - if (functionElementInput == FUNCTION_OFF) { - ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, 1, 0); - } - else if (gpio == gpioInput && functionElementInput == function) { - ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, ConfigESP->getLevel(nr, function), ConfigESP->getMemory(nr, function)); + if (addressInput == OFF_MCP23017) { + ConfigESP->clearGpioMCP23017(gpio, nr, function); } - else { - return false; + + if (gpioInput != OFF_GPIO) { + if (functionElementInput == FUNCTION_OFF) { + ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, 1, 0); + } + else if (gpio == gpioInput && functionElementInput == function) { + ConfigESP->setGpioMCP23017(gpioInput, addressInput, nr, function, ConfigESP->getLevel(nr, function), ConfigESP->getMemory(nr, function)); + } + else { + return false; + } } return true; } \ No newline at end of file diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 333c7fc4..958b0e2e 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -92,8 +92,8 @@ class SuplaWebServer : public Supla::Element { ESP8266HTTPUpdateServer httpUpdater; #endif - bool saveGPIO(const String& input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); - bool saveGpioMCP23017(const String& input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); + bool saveGPIO(const String& _input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); + bool saveGpioMCP23017(const String& _input, uint8_t function, uint8_t nr = 0, const String& input_max = "\n"); private: void iterateAlways(); From a97faf317fb3d552204a17c319b25ef800fbf80a Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sun, 17 Jan 2021 12:48:55 +0100 Subject: [PATCH 105/233] =?UTF-8?q?nowy=20spos=C3=B3b=20wysy=C5=82ania=20s?= =?UTF-8?q?endContent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Markup.cpp | 6 +- src/SuplaCommonPROGMEM.h | 9 + src/SuplaHTTPUpdateServer.cpp | 47 ++- src/SuplaHTTPUpdateServer.h | 2 +- src/SuplaWebPageConfig.cpp | 40 +-- src/SuplaWebPageConfig.h | 2 +- src/SuplaWebPageSensor.cpp | 636 +++++++++++++++++----------------- src/SuplaWebPageSensor.h | 16 +- src/SuplaWebPageTools.cpp | 19 +- src/SuplaWebPageUpload.cpp | 31 +- src/SuplaWebServer.cpp | 195 ++++------- src/SuplaWebServer.h | 8 +- 12 files changed, 465 insertions(+), 546 deletions(-) diff --git a/src/Markup.cpp b/src/Markup.cpp index 703e2e5d..d58d9739 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -141,7 +141,7 @@ void addLinkBox(String& html, const String& name, const String& url) { html += url; html += F("'>"); html += name; - html += WebServer->SuplaIconEdit(); + html += PGMT(ICON_EDIT); html += F(""); html += F(""); html += F("
"); @@ -201,7 +201,7 @@ void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const Stri } html += name; if (ConfigESP->getGpioMCP23017(nr, function) != OFF_GPIO) { - html += WebServer->SuplaIconEdit(); + html += PGMT(ICON_EDIT); html += F(""); } html += F(""); @@ -234,7 +234,7 @@ void addListGPIOLinkBox(String& html, const String& input_id, const String& name } html += name; if (ConfigESP->getGpio(_nr, function) != OFF_GPIO) { - html += WebServer->SuplaIconEdit(); + html += PGMT(ICON_EDIT); html += F(""); } html += F(""); diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 6ce2578e..325f5c40 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -59,6 +59,15 @@ const char HTTP_FAVICON[] PROGMEM = ""; +const char ICON_EDIT[] PROGMEM = + ""; + const char GPIO0[] PROGMEM = "GPIO0-D3"; const char GPIO1[] PROGMEM = "GPIO1-TX"; const char GPIO2[] PROGMEM = "GPIO2-D4"; diff --git a/src/SuplaHTTPUpdateServer.cpp b/src/SuplaHTTPUpdateServer.cpp index 593fd9cc..501242e5 100644 --- a/src/SuplaHTTPUpdateServer.cpp +++ b/src/SuplaHTTPUpdateServer.cpp @@ -17,7 +17,7 @@ static const char serverIndex[] PROGMEM = - +
Flash Size: {f}kB
@@ -29,9 +29,9 @@ static const char serverIndex[] PROGMEM = )"; -static const char successResponse[] PROGMEM = "Update Success! Rebooting..."; +static const char successResponse[] PROGMEM = "Update Success! Rebooting..."; static const char twoStepResponse[] PROGMEM = - "WARNING only use 2-step OTA update. Use GUI-GenericUpdater.bin"; + "WARNING only use 2-step OTA update. Use GUI-GenericUpdater.bin"; ESP8266HTTPUpdateServer::ESP8266HTTPUpdateServer(bool serial_debug) { _serial_output = serial_debug; @@ -144,30 +144,27 @@ void ESP8266HTTPUpdateServer::handleFirmwareUp() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(suplaWebPageUpddate()); + suplaWebPageUpddate(); } -String ESP8266HTTPUpdateServer::suplaWebPageUpddate() { - String content = ""; - content += SuplaJavaScript(); - content += F("
"); - content += F("

"); - content += S_SOFTWARE_UPDATE; - content += F("

"); - content += F("
"); - content += F("
"); - content += F(""); - content += F("
"); - content += F("
"); - content += F("

"); - - return content; +void ESP8266HTTPUpdateServer::suplaWebPageUpddate() { + webContentBuffer += SuplaJavaScript(); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + webContentBuffer += S_SOFTWARE_UPDATE; + webContentBuffer += F("

"); + webContentBuffer += F("
"); + webContentBuffer += F("
"); + webContentBuffer += F(""); + webContentBuffer += F("
"); + webContentBuffer += F("
"); + webContentBuffer += F("

"); } void ESP8266HTTPUpdateServer::_setUpdaterError() { diff --git a/src/SuplaHTTPUpdateServer.h b/src/SuplaHTTPUpdateServer.h index 5d76d0ef..0466f93e 100644 --- a/src/SuplaHTTPUpdateServer.h +++ b/src/SuplaHTTPUpdateServer.h @@ -46,7 +46,7 @@ class ESP8266HTTPUpdateServer String _updaterError; void handleFirmwareUp(); - String suplaWebPageUpddate(); + void suplaWebPageUpddate(); }; #endif diff --git a/src/SuplaWebPageConfig.cpp b/src/SuplaWebPageConfig.cpp index 1a28163b..def78b14 100644 --- a/src/SuplaWebPageConfig.cpp +++ b/src/SuplaWebPageConfig.cpp @@ -25,7 +25,7 @@ void SuplaWebPageConfig::handleConfig() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_config(0)); + supla_webpage_config(0); } void SuplaWebPageConfig::handleConfigSave() { @@ -35,7 +35,7 @@ void SuplaWebPageConfig::handleConfigSave() { } if (!WebServer->saveGPIO(INPUT_CFG_LED_GPIO, FUNCTION_CFG_LED)) { - WebServer->sendContent(supla_webpage_config(6)); + supla_webpage_config(6); return; } @@ -44,7 +44,7 @@ void SuplaWebPageConfig::handleConfigSave() { ConfigManager->setElement(key, LEVEL, WebServer->httpServer.arg(input).toInt()); if (!WebServer->saveGPIO(INPUT_CFG_BTN_GPIO, FUNCTION_CFG_BUTTON)) { - WebServer->sendContent(supla_webpage_config(6)); + supla_webpage_config(6); return; } @@ -55,38 +55,36 @@ void SuplaWebPageConfig::handleConfigSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_config(1)); + supla_webpage_config(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_config(2)); + supla_webpage_config(2); break; } } -String SuplaWebPageConfig::supla_webpage_config(int save) { +void SuplaWebPageConfig::supla_webpage_config(int save) { uint8_t selected, suported; - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_CONFIG); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_CONFIG); - addForm(page, F("post"), PATH_SAVE_CONFIG); - addFormHeader(page, S_GPIO_SETTINGS_FOR_CONFIG); - addListGPIOBox(page, INPUT_CFG_LED_GPIO, F("LED"), FUNCTION_CFG_LED); + addForm(webContentBuffer, F("post"), PATH_SAVE_CONFIG); + addFormHeader(webContentBuffer, S_GPIO_SETTINGS_FOR_CONFIG); + addListGPIOBox(webContentBuffer, INPUT_CFG_LED_GPIO, F("LED"), FUNCTION_CFG_LED); selected = ConfigESP->getLevel(FUNCTION_CFG_LED); - addListBox(page, INPUT_CFG_LED_LEVEL, S_STATE_CONTROL, LEVEL_P, 2, selected); - addListGPIOBox(page, INPUT_CFG_BTN_GPIO, S_BUTTON, FUNCTION_CFG_BUTTON); + addListBox(webContentBuffer, INPUT_CFG_LED_LEVEL, S_STATE_CONTROL, LEVEL_P, 2, selected); + addListGPIOBox(webContentBuffer, INPUT_CFG_BTN_GPIO, S_BUTTON, FUNCTION_CFG_BUTTON); selected = ConfigManager->get(KEY_CFG_MODE)->getValueInt(); - addListBox(page, INPUT_CFG_MODE, S_CFG_MODE, CFG_MODE_P, 2, selected); + addListBox(webContentBuffer, INPUT_CFG_MODE, S_CFG_MODE, CFG_MODE_P, 2, selected); - addFormHeaderEnd(page); - addButtonSubmit(page, S_SAVE); - addFormEnd(page); + addFormHeaderEnd(webContentBuffer); + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); - addButton(page, S_RETURN, PATH_DEVICE_SETTINGS); - - return page; + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); + WebServer->sendContent(); } diff --git a/src/SuplaWebPageConfig.h b/src/SuplaWebPageConfig.h index 7733bb33..c2d55089 100644 --- a/src/SuplaWebPageConfig.h +++ b/src/SuplaWebPageConfig.h @@ -19,7 +19,7 @@ class SuplaWebPageConfig { void handleConfigSave(); private: - String supla_webpage_config(int save); + void supla_webpage_config(int save); }; extern SuplaWebPageConfig *WebPageConfig; diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 43cbfcb6..112347b0 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -89,7 +89,7 @@ void SuplaWebPageSensor::handleSearchDS() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_search(0)); + supla_webpage_search(0); } void SuplaWebPageSensor::handleDSSave() { @@ -115,18 +115,17 @@ void SuplaWebPageSensor::handleDSSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: // Serial.println(F("E_CONFIG_OK: Config save")); - WebServer->sendContent(supla_webpage_search(1)); + supla_webpage_search(1); // WebServer->rebootESP(); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_search(2)); + supla_webpage_search(2); break; } } -String SuplaWebPageSensor::supla_webpage_search(int save) { - String content = ""; +void SuplaWebPageSensor::supla_webpage_search(int save) { uint8_t count = 0; uint8_t pin = ConfigESP->getGpio(FUNCTION_DS18B20); @@ -136,26 +135,26 @@ String SuplaWebPageSensor::supla_webpage_search(int save) { char strAddr[64]; uint8_t i; - content += SuplaSaveResult(save); - content += SuplaJavaScript(PATH_MULTI_DS); - content += F("
"); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_MULTI_DS); + webContentBuffer += F("
"); if (ConfigESP->getGpio(FUNCTION_DS18B20) < OFF_GPIO) { - content += F("
"); - this->showDS18B20(content); - content += F("
"); - content += F("
"); - } - content += F("
"); - content += F("
"); - content += F("

"); - content += S_FOUND; - content += F(" DS18b20

"); + webContentBuffer += F(""); + this->showDS18B20(); + webContentBuffer += F(""); + webContentBuffer += F("
"); + } + webContentBuffer += F("
"); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + webContentBuffer += S_FOUND; + webContentBuffer += F(" DS18b20

"); sensors.setOneWire(&ow); sensors.begin(); if (sensors.isParasitePowerMode()) { @@ -174,15 +173,15 @@ String SuplaWebPageSensor::supla_webpage_search(int save) { address[7]); supla_log(LOG_DEBUG, "Index %d - address %s", i, strAddr); - content += F(""); + webContentBuffer += F("' value='"); + webContentBuffer += String(strAddr); + webContentBuffer += F("' maxlength="); + webContentBuffer += MAX_DS18B20_ADDRESS_HEX; + webContentBuffer += F(" readonly>
"); - content += F("
"); - content += F(""); - content += F("

"); - content += F("

"); - - return content; + webContentBuffer += F(""); + } + webContentBuffer += F("
"); + webContentBuffer += F(""); + webContentBuffer += F(""); + webContentBuffer += F("

"); + webContentBuffer += F("

"); + WebServer->sendContent(); } -void SuplaWebPageSensor::showDS18B20(String &content, bool readonly) { +void SuplaWebPageSensor::showDS18B20(bool readonly) { if (ConfigESP->getGpio(FUNCTION_DS18B20) != OFF_GPIO) { - content += F("
"); - content += F("

"); - content += S_TEMPERATURE; - content += F("

"); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + webContentBuffer += S_TEMPERATURE; + webContentBuffer += F("

"); for (uint8_t i = 0; i < ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt(); i++) { double temp = Supla::GUI::sensorDS[i]->getValue(); - content += F(""); - content += F(""); + webContentBuffer += F(""); + webContentBuffer += F(" °C "); + webContentBuffer += F(""); delay(0); } - content += F("
"); + webContentBuffer += F("
"); } } #endif @@ -264,7 +262,7 @@ void SuplaWebPageSensor::handle1Wire() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_1wire(0)); + supla_webpage_1wire(0); } void SuplaWebPageSensor::handle1WireSave() { @@ -279,7 +277,7 @@ void SuplaWebPageSensor::handle1WireSave() { last_value = ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { if (!WebServer->saveGPIO(INPUT_DHT11_GPIO, FUNCTION_DHT11, nr, INPUT_MAX_DHT11)) { - WebServer->sendContent(supla_webpage_1wire(6)); + supla_webpage_1wire(6); return; } } @@ -293,7 +291,7 @@ void SuplaWebPageSensor::handle1WireSave() { last_value = ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { if (!WebServer->saveGPIO(INPUT_DHT22_GPIO, FUNCTION_DHT22, nr, INPUT_MAX_DHT22)) { - WebServer->sendContent(supla_webpage_1wire(6)); + supla_webpage_1wire(6); return; } } @@ -305,7 +303,7 @@ void SuplaWebPageSensor::handle1WireSave() { #ifdef SUPLA_DS18B20 if (!WebServer->saveGPIO(INPUT_MULTI_DS_GPIO, FUNCTION_DS18B20)) { - WebServer->sendContent(supla_webpage_1wire(6)); + supla_webpage_1wire(6); return; } if (strcmp(WebServer->httpServer.arg(INPUT_MAX_DS18B20).c_str(), "") > 0) { @@ -315,78 +313,78 @@ void SuplaWebPageSensor::handle1WireSave() { #ifdef SUPLA_SI7021_SONOFF if (!WebServer->saveGPIO(INPUT_SI7021_SONOFF, FUNCTION_SI7021_SONOFF)) { - WebServer->sendContent(supla_webpage_1wire(6)); + supla_webpage_1wire(6); return; } #endif switch (ConfigManager->save()) { case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_1wire(1)); + supla_webpage_1wire(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_1wire(2)); + supla_webpage_1wire(2); break; } } -String SuplaWebPageSensor::supla_webpage_1wire(int save) { +void SuplaWebPageSensor::supla_webpage_1wire(int save) { uint8_t nr, max; - String page; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_1WIRE); - page += F("
"); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_1WIRE); + webContentBuffer += F(""); #ifdef SUPLA_DHT11 - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " DHT11"); - addNumberBox(page, INPUT_MAX_DHT11, S_QUANTITY, KEY_MAX_DHT11, ConfigESP->countFreeGpio(FUNCTION_DHT11)); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " DHT11"); + addNumberBox(webContentBuffer, INPUT_MAX_DHT11, S_QUANTITY, KEY_MAX_DHT11, ConfigESP->countFreeGpio(FUNCTION_DHT11)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT11)->getValueInt(); nr++) { - addListGPIOBox(page, INPUT_DHT11_GPIO, "DHT11", FUNCTION_DHT11, nr); + addListGPIOBox(webContentBuffer, INPUT_DHT11_GPIO, "DHT11", FUNCTION_DHT11, nr); } - addFormHeaderEnd(page); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_DHT22 - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " DHT22"); - addNumberBox(page, INPUT_MAX_DHT22, S_QUANTITY, KEY_MAX_DHT22, ConfigESP->countFreeGpio(FUNCTION_DHT22)); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " DHT22"); + addNumberBox(webContentBuffer, INPUT_MAX_DHT22, S_QUANTITY, KEY_MAX_DHT22, ConfigESP->countFreeGpio(FUNCTION_DHT22)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_DHT22)->getValueInt(); nr++) { - addListGPIOBox(page, INPUT_DHT22_GPIO, "DHT22", FUNCTION_DHT22, nr); + addListGPIOBox(webContentBuffer, INPUT_DHT22_GPIO, "DHT22", FUNCTION_DHT22, nr); } - addFormHeaderEnd(page); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_SI7021_SONOFF - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " Si7021 Sonoff"); - addListGPIOBox(page, INPUT_SI7021_SONOFF, "Si7021 Sonoff", FUNCTION_SI7021_SONOFF); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " Si7021 Sonoff"); + addListGPIOBox(webContentBuffer, INPUT_SI7021_SONOFF, "Si7021 Sonoff", FUNCTION_SI7021_SONOFF); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_DS18B20 - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " Multi DS18B20"); - addNumberBox(page, INPUT_MAX_DS18B20, S_QUANTITY, KEY_MULTI_MAX_DS18B20, MAX_DS18B20); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " Multi DS18B20"); + addNumberBox(webContentBuffer, INPUT_MAX_DS18B20, S_QUANTITY, KEY_MULTI_MAX_DS18B20, MAX_DS18B20); if (ConfigManager->get(KEY_MULTI_MAX_DS18B20)->getValueInt() > 1) { - addListGPIOLinkBox(page, INPUT_MULTI_DS_GPIO, "MULTI DS18B20", FUNCTION_DS18B20, PATH_MULTI_DS); + addListGPIOLinkBox(webContentBuffer, INPUT_MULTI_DS_GPIO, "MULTI DS18B20", FUNCTION_DS18B20, PATH_MULTI_DS); } else { - addListGPIOBox(page, INPUT_MULTI_DS_GPIO, "MULTI DS18B20", FUNCTION_DS18B20); - } - addFormHeaderEnd(page); -#endif - - page += F("
"); - page += F("
"); - page += F("

"); - return page; + addListGPIOBox(webContentBuffer, INPUT_MULTI_DS_GPIO, "MULTI DS18B20", FUNCTION_DS18B20); + } + addFormHeaderEnd(webContentBuffer); +#endif + + webContentBuffer += F(""); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + + WebServer->sendContent(); } #endif @@ -396,7 +394,7 @@ void SuplaWebPageSensor::handlei2c() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_i2c(0)); + supla_webpage_i2c(0); } void SuplaWebPageSensor::handlei2cSave() { @@ -409,11 +407,11 @@ void SuplaWebPageSensor::handlei2cSave() { uint8_t key; if (!WebServer->saveGPIO(INPUT_SDA_GPIO, FUNCTION_SDA)) { - WebServer->sendContent(supla_webpage_i2c(6)); + supla_webpage_i2c(6); return; } if (!WebServer->saveGPIO(INPUT_SCL_GPIO, FUNCTION_SCL)) { - WebServer->sendContent(supla_webpage_i2c(6)); + supla_webpage_i2c(6); return; } @@ -478,54 +476,54 @@ void SuplaWebPageSensor::handlei2cSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_i2c(1)); + supla_webpage_i2c(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_i2c(2)); + supla_webpage_i2c(2); break; } } -String SuplaWebPageSensor::supla_webpage_i2c(int save) { +void SuplaWebPageSensor::supla_webpage_i2c(int save) { uint8_t selected; - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_I2C); - addForm(page, F("post"), PATH_SAVE_I2C); - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + F(" i2c")); - addListGPIOBox(page, INPUT_SDA_GPIO, F("SDA"), FUNCTION_SDA); - addListGPIOBox(page, INPUT_SCL_GPIO, F("SCL"), FUNCTION_SCL); - addFormHeaderEnd(page); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_I2C); + + addForm(webContentBuffer, F("post"), PATH_SAVE_I2C); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + F(" i2c")); + addListGPIOBox(webContentBuffer, INPUT_SDA_GPIO, F("SDA"), FUNCTION_SDA); + addListGPIOBox(webContentBuffer, INPUT_SCL_GPIO, F("SCL"), FUNCTION_SCL); + addFormHeaderEnd(webContentBuffer); if (ConfigESP->getGpio(FUNCTION_SDA) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SCL) != OFF_GPIO) { #ifdef SUPLA_BME280 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_BME280).toInt(); - addFormHeader(page); - addListBox(page, INPUT_BME280, F("BME280 adres"), BME280_P, 4, selected); - addNumberBox(page, INPUT_ALTITUDE_BME280, S_ALTITUDE_ABOVE_SEA_LEVEL, KEY_ALTITUDE_BME280, 1500); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer); + addListBox(webContentBuffer, INPUT_BME280, F("BME280 adres"), BME280_P, 4, selected); + addNumberBox(webContentBuffer, INPUT_ALTITUDE_BME280, S_ALTITUDE_ABOVE_SEA_LEVEL, KEY_ALTITUDE_BME280, 1500); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_SHT3x selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SHT3x).toInt(); - addFormHeader(page); - addListBox(page, INPUT_SHT3x, F("SHT3x"), SHT3x_P, 4, selected); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer); + addListBox(webContentBuffer, INPUT_SHT3x, F("SHT3x"), SHT3x_P, 4, selected); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_SI7021 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_SI7021).toInt(); - addFormHeader(page); - addListBox(page, INPUT_SI7021, F("SI7021"), STATE_P, 2, selected); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer); + addListBox(webContentBuffer, INPUT_SI7021, F("SI7021"), STATE_P, 2, selected); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_OLED selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); - addFormHeader(page); - addListBox(page, INPUT_OLED, F("OLED"), OLED_P, 4, selected); + addFormHeader(webContentBuffer); + addListBox(webContentBuffer, INPUT_OLED, F("OLED"), OLED_P, 4, selected); if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { String name, sensorName, input; @@ -535,26 +533,25 @@ String SuplaWebPageSensor::supla_webpage_i2c(int save) { input += i; name = F("Ekran "); name += i + 1; - addTextBox(page, input, name, sensorName, 0, MAX_DS18B20_NAME, false); + addTextBox(webContentBuffer, input, name, sensorName, 0, MAX_DS18B20_NAME, false); } } - addFormHeaderEnd(page); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_MCP23017 selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MCP23017).toInt(); - addFormHeader(page); - addListBox(page, INPUT_MCP23017, F("MCP23017"), STATE_P, 2, selected); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer); + addListBox(webContentBuffer, INPUT_MCP23017, F("MCP23017"), STATE_P, 2, selected); + addFormHeaderEnd(webContentBuffer); #endif } - addButtonSubmit(page, S_SAVE); - addFormEnd(page); - - addButton(page, S_RETURN, PATH_DEVICE_SETTINGS); + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); - return page; + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); + WebServer->sendContent(); } #endif @@ -564,7 +561,7 @@ void SuplaWebPageSensor::handleSpi() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_spi(0)); + supla_webpage_spi(0); } void SuplaWebPageSensor::handleSpiSave() { @@ -578,15 +575,15 @@ void SuplaWebPageSensor::handleSpiSave() { #if defined(SUPLA_MAX6675) if (!WebServer->saveGPIO(INPUT_CLK_GPIO, FUNCTION_CLK)) { - WebServer->sendContent(supla_webpage_spi(6)); + supla_webpage_spi(6); return; } if (!WebServer->saveGPIO(INPUT_CS_GPIO, FUNCTION_CS)) { - WebServer->sendContent(supla_webpage_spi(6)); + supla_webpage_spi(6); return; } if (!WebServer->saveGPIO(INPUT_D0_GPIO, FUNCTION_D0)) { - WebServer->sendContent(supla_webpage_spi(6)); + supla_webpage_spi(6); return; } #endif @@ -601,47 +598,47 @@ void SuplaWebPageSensor::handleSpiSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_spi(1)); + supla_webpage_spi(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_spi(2)); + supla_webpage_spi(2); break; } } -String SuplaWebPageSensor::supla_webpage_spi(int save) { +void SuplaWebPageSensor::supla_webpage_spi(int save) { uint8_t nr, suported, selected; - String page; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_SPI); - page += F("
"); + + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_SPI); + webContentBuffer += F(""); #if defined(SUPLA_MAX6675) - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " SPI"); - addListGPIOBox(page, INPUT_CLK_GPIO, "CLK", FUNCTION_CLK); - addListGPIOBox(page, INPUT_CS_GPIO, "CS", FUNCTION_CS); - addListGPIOBox(page, INPUT_D0_GPIO, "D0", FUNCTION_D0); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " SPI"); + addListGPIOBox(webContentBuffer, INPUT_CLK_GPIO, "CLK", FUNCTION_CLK); + addListGPIOBox(webContentBuffer, INPUT_CS_GPIO, "CS", FUNCTION_CS); + addListGPIOBox(webContentBuffer, INPUT_D0_GPIO, "D0", FUNCTION_D0); if (ConfigESP->getGpio(FUNCTION_CLK) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CS) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_D0) != OFF_GPIO) { selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_MAX6675).toInt(); - addListBox(page, INPUT_MAX6675, "MAX6675", STATE_P, 2, selected); - } - addFormHeaderEnd(page); -#endif - page += F("
"); - page += F("
"); - page += F("

"); - return page; + addListBox(webContentBuffer, INPUT_MAX6675, "MAX6675", STATE_P, 2, selected); + } + addFormHeaderEnd(webContentBuffer); +#endif + webContentBuffer += F(""); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + WebServer->sendContent(); } #endif @@ -651,7 +648,7 @@ void SuplaWebPageSensor::handleOther() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_webpage_other(0)); + supla_webpage_other(0); } void SuplaWebPageSensor::handleOtherSave() { @@ -664,11 +661,11 @@ void SuplaWebPageSensor::handleOtherSave() { #ifdef SUPLA_HC_SR04 if (!WebServer->saveGPIO(INPUT_TRIG_GPIO, FUNCTION_TRIG)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } if (!WebServer->saveGPIO(INPUT_ECHO_GPIO, FUNCTION_ECHO)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } #endif @@ -679,7 +676,7 @@ void SuplaWebPageSensor::handleOtherSave() { last_value = ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt(); for (nr = 1; nr <= last_value; nr++) { if (!WebServer->saveGPIO(INPUT_IMPULSE_COUNTER_GPIO, FUNCTION_IMPULSE_COUNTER, nr, INPUT_MAX_IMPULSE_COUNTER)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } } @@ -691,69 +688,68 @@ void SuplaWebPageSensor::handleOtherSave() { #ifdef SUPLA_HLW8012 if (!WebServer->saveGPIO(INPUT_CF, FUNCTION_CF)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } if (!WebServer->saveGPIO(INPUT_CF1, FUNCTION_CF1)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } if (!WebServer->saveGPIO(INPUT_SEL, FUNCTION_SEL)) { - WebServer->sendContent(supla_webpage_other(6)); + supla_webpage_other(6); return; } #endif switch (ConfigManager->save()) { case E_CONFIG_OK: - WebServer->sendContent(supla_webpage_other(1)); + supla_webpage_other(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_other(2)); + supla_webpage_other(2); break; } } -String SuplaWebPageSensor::supla_webpage_other(int save) { +void SuplaWebPageSensor::supla_webpage_other(int save) { uint8_t nr, suported, selected; - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_OTHER); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_OTHER); - addForm(page, F("post"), PATH_SAVE_OTHER); + addForm(webContentBuffer, F("post"), PATH_SAVE_OTHER); #ifdef SUPLA_HC_SR04 - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + F(" HC-SR04")); - addListGPIOBox(page, INPUT_TRIG_GPIO, F("TRIG"), FUNCTION_TRIG); - addListGPIOBox(page, INPUT_ECHO_GPIO, F("ECHO"), FUNCTION_ECHO); - addFormHeaderEnd(page); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + F(" HC-SR04")); + addListGPIOBox(webContentBuffer, INPUT_TRIG_GPIO, F("TRIG"), FUNCTION_TRIG); + addListGPIOBox(webContentBuffer, INPUT_ECHO_GPIO, F("ECHO"), FUNCTION_ECHO); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_IMPULSE_COUNTER - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + " " + S_IMPULSE_COUNTER); - addNumberBox(page, INPUT_MAX_IMPULSE_COUNTER, S_QUANTITY, KEY_MAX_IMPULSE_COUNTER, ConfigESP->countFreeGpio(FUNCTION_IMPULSE_COUNTER)); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + " " + S_IMPULSE_COUNTER); + addNumberBox(webContentBuffer, INPUT_MAX_IMPULSE_COUNTER, S_QUANTITY, KEY_MAX_IMPULSE_COUNTER, ConfigESP->countFreeGpio(FUNCTION_IMPULSE_COUNTER)); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt(); nr++) { - addListGPIOLinkBox(page, INPUT_IMPULSE_COUNTER_GPIO, F("IC GPIO"), FUNCTION_IMPULSE_COUNTER, PATH_IMPULSE_COUNTER_SET, nr); + addListGPIOLinkBox(webContentBuffer, INPUT_IMPULSE_COUNTER_GPIO, F("IC GPIO"), FUNCTION_IMPULSE_COUNTER, PATH_IMPULSE_COUNTER_SET, nr); } - addFormHeaderEnd(page); + addFormHeaderEnd(webContentBuffer); #endif #ifdef SUPLA_HLW8012 - addFormHeader(page, String(S_GPIO_SETTINGS_FOR) + F(" HLW8012")); - addListGPIOBox(page, INPUT_CF, F("CF"), FUNCTION_CF); - addListGPIOBox(page, INPUT_CF1, F("CF1"), FUNCTION_CF1); - addListGPIOBox(page, INPUT_SEL, F("SELi"), FUNCTION_SEL); + addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + F(" HLW8012")); + addListGPIOBox(webContentBuffer, INPUT_CF, F("CF"), FUNCTION_CF); + addListGPIOBox(webContentBuffer, INPUT_CF1, F("CF1"), FUNCTION_CF1); + addListGPIOBox(webContentBuffer, INPUT_SEL, F("SELi"), FUNCTION_SEL); if (ConfigESP->getGpio(FUNCTION_CF) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_CF1) != OFF_GPIO && ConfigESP->getGpio(FUNCTION_SEL) != OFF_GPIO) { - addLinkBox(page, F("Kalibracja"), PATH_HLW8012_CALIBRATE); + addLinkBox(webContentBuffer, F("Kalibracja"), PATH_HLW8012_CALIBRATE); } - addFormHeaderEnd(page); + addFormHeaderEnd(webContentBuffer); #endif - addButtonSubmit(page, S_SAVE); - addFormEnd(page); - addButton(page, S_RETURN, PATH_DEVICE_SETTINGS); - return page; + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, PATH_DEVICE_SETTINGS); + WebServer->sendContent(); } #endif @@ -763,7 +759,7 @@ void SuplaWebPageSensor::handleImpulseCounterSet() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(supla_impulse_counter_set(0)); + supla_impulse_counter_set(0); } void SuplaWebPageSensor::handleImpulseCounterSaveSet() { @@ -797,17 +793,17 @@ void SuplaWebPageSensor::handleImpulseCounterSaveSet() { switch (ConfigManager->save()) { case E_CONFIG_OK: // Serial.println(F("E_CONFIG_OK: Dane zapisane")); - WebServer->sendContent(supla_webpage_other(1)); + supla_webpage_other(1); break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - WebServer->sendContent(supla_webpage_other(2)); + supla_webpage_other(2); break; } } -String SuplaWebPageSensor::supla_impulse_counter_set(int save) { +void SuplaWebPageSensor::supla_impulse_counter_set(int save) { String readUrl, nr; uint8_t place, selected, suported; @@ -818,87 +814,86 @@ String SuplaWebPageSensor::supla_impulse_counter_set(int save) { place = readUrl.indexOf(path); nr = readUrl.substring(place + path.length(), place + path.length() + 3); - String page = ""; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_OTHER); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_OTHER); uint8_t relays = ConfigManager->get(KEY_MAX_IMPULSE_COUNTER)->getValueInt(); if (nr.toInt() <= relays && ConfigESP->getGpio(nr.toInt(), FUNCTION_IMPULSE_COUNTER) != OFF_GPIO) { - page += F("

"); - page += S_IMPULSE_COUNTER_SETTINGS_NR; - page += F(" "); - page += nr; - page += F("

"); - page += F(""); selected = ConfigESP->getMemory(nr.toInt(), FUNCTION_IMPULSE_COUNTER); for (suported = 0; suported < 2; suported++) { - page += F(""); - page += F(""); + webContentBuffer += F(""); - addNumberBox(page, INPUT_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, S_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, KEY_IMPULSE_COUNTER_DEBOUNCE_TIMEOUT, 99999999); - page += F(""); + webContentBuffer += count; + webContentBuffer += F("'>"); - page += F("
"); + webContentBuffer += F("
"); } else { - page += F("

"); - page += S_NO_IMPULSE_COUNTER_NR; - page += F(" "); - page += nr; - page += F("

"); - } - page += F("
"); - page += F("

"); - - return page; + webContentBuffer += F("

"); + webContentBuffer += S_NO_IMPULSE_COUNTER_NR; + webContentBuffer += F(" "); + webContentBuffer += nr; + webContentBuffer += F("

"); + } + webContentBuffer += F("
"); + webContentBuffer += F("

"); + WebServer->sendContent(); } #endif @@ -908,7 +903,7 @@ void SuplaWebPageSensor::handleHLW8012Calibrate() { if (!WebServer->httpServer.authenticate(WebServer->www_username, WebServer->www_password)) return WebServer->httpServer.requestAuthentication(); } - WebServer->sendContent(suplaWebpageHLW8012Calibrate(0)); + suplaWebpageHLW8012Calibrate(0); } void SuplaWebPageSensor::handleHLW8012CalibrateSave() { @@ -930,38 +925,37 @@ void SuplaWebPageSensor::handleHLW8012CalibrateSave() { if (calibPower && calibVoltage) { Supla::GUI::counterHLW8012->calibrate(calibPower, calibVoltage); - WebServer->sendContent(suplaWebpageHLW8012Calibrate(1)); + suplaWebpageHLW8012Calibrate(1); } else { - WebServer->sendContent(suplaWebpageHLW8012Calibrate(6)); + suplaWebpageHLW8012Calibrate(6); } } -String SuplaWebPageSensor::suplaWebpageHLW8012Calibrate(uint8_t save) { - String page; - page += SuplaSaveResult(save); - page += SuplaJavaScript(PATH_HLW8012_CALIBRATE); - - addFormHeader(page); - page += F("

Current Multi: "); - page += Supla::GUI::counterHLW8012->getCurrentMultiplier(); - page += F("
Voltage Multi: "); - page += Supla::GUI::counterHLW8012->getVoltageMultiplier(); - page += F("
Power Multi: "); - page += Supla::GUI::counterHLW8012->getPowerMultiplier(); - page += F("

"); - addFormHeaderEnd(page); - - addForm(page, F("post"), PATH_SAVE_HLW8012_CALIBRATE); - addFormHeader(page, "Ustawienia kalibracji"); - addNumberBox(page, INPUT_CALIB_POWER, "Moc żarówki [W]", "25", true); - addNumberBox(page, INPUT_CALIB_VOLTAGE, "Napięcie [V]", "230", true); - addFormHeaderEnd(page); - - addButtonSubmit(page, "Kalibracja"); - addFormEnd(page); - - addButton(page, S_RETURN, PATH_OTHER); - return page; +void SuplaWebPageSensor::suplaWebpageHLW8012Calibrate(uint8_t save) { + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_HLW8012_CALIBRATE); + + addFormHeader(webContentBuffer); + webContentBuffer += F("

Current Multi: "); + webContentBuffer += Supla::GUI::counterHLW8012->getCurrentMultiplier(); + webContentBuffer += F("
Voltage Multi: "); + webContentBuffer += Supla::GUI::counterHLW8012->getVoltageMultiplier(); + webContentBuffer += F("
Power Multi: "); + webContentBuffer += Supla::GUI::counterHLW8012->getPowerMultiplier(); + webContentBuffer += F("

"); + addFormHeaderEnd(webContentBuffer); + + addForm(webContentBuffer, F("post"), PATH_SAVE_HLW8012_CALIBRATE); + addFormHeader(webContentBuffer, "Ustawienia kalibracji"); + addNumberBox(webContentBuffer, INPUT_CALIB_POWER, "Moc żarówki [W]", "25", true); + addNumberBox(webContentBuffer, INPUT_CALIB_VOLTAGE, "Napięcie [V]", "230", true); + addFormHeaderEnd(webContentBuffer); + + addButtonSubmit(webContentBuffer, "Kalibracja"); + addFormEnd(webContentBuffer); + + addButton(webContentBuffer, S_RETURN, PATH_OTHER); + WebServer->sendContent(); } #endif \ No newline at end of file diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index 152ccab2..c0b98eb1 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -86,7 +86,7 @@ class SuplaWebPageSensor { void handleSearchDS(); void handleDSSave(); - void showDS18B20(String& content, bool readonly = false); + void showDS18B20(bool readonly = false); #endif #if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_OLED) || defined(SUPLA_MCP23017) @@ -94,7 +94,7 @@ class SuplaWebPageSensor { void handle1WireSave(); void handlei2c(); void handlei2cSave(); - String supla_webpage_i2c(int save); + void supla_webpage_i2c(int save); #endif #if defined(SUPLA_MAX6675) @@ -108,23 +108,23 @@ class SuplaWebPageSensor { void handleImpulseCounterSet(); void handleImpulseCounterSaveSet(); - String supla_impulse_counter_set(int save); + void supla_impulse_counter_set(int save); #endif private: #if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - String supla_webpage_1wire(int save); + void supla_webpage_1wire(int save); #ifdef SUPLA_DS18B20 - String supla_webpage_search(int save); + void supla_webpage_search(int save); #endif #endif #if defined(SUPLA_MAX6675) - String supla_webpage_spi(int save); + void supla_webpage_spi(int save); #endif #if defined(SUPLA_HC_SR04) || defined(SUPLA_IMPULSE_COUNTER) - String supla_webpage_other(int save); + void supla_webpage_other(int save); #endif #if defined(SUPLA_HLW8012) @@ -140,7 +140,7 @@ class SuplaWebPageSensor { void handleHLW8012Calibrate(); void handleHLW8012CalibrateSave(); - String suplaWebpageHLW8012Calibrate(uint8_t save); + void suplaWebpageHLW8012Calibrate(uint8_t save); #endif }; diff --git a/src/SuplaWebPageTools.cpp b/src/SuplaWebPageTools.cpp index d8bb1ab2..4cd9173a 100644 --- a/src/SuplaWebPageTools.cpp +++ b/src/SuplaWebPageTools.cpp @@ -11,7 +11,7 @@ void createWebTools() { } WebServer->httpServer.sendHeader("Location", "/"); WebServer->httpServer.send(303); - WebServer->sendContent(WebServer->supla_webpage_start(0)); + WebServer->supla_webpage_start(0); ConfigESP->factoryReset(true); }); } @@ -22,20 +22,19 @@ void handleTools() { return WebServer->httpServer.requestAuthentication(); } - String content = ""; - addFormHeader(content, "Tools"); + addFormHeader(webContentBuffer, F("Tools")); //#ifdef SUPLA_BUTTON - addButton(content, "Save config", PATH_DOWNLOAD); + addButton(webContentBuffer, F("Save config"), PATH_DOWNLOAD); //#endif //#ifdef SUPLA_BUTTON - addButton(content, "Load config", PATH_UPLOAD); + addButton(webContentBuffer, F("Load config"), PATH_UPLOAD); //#endif #ifdef SUPLA_OTA - addButton(content, S_UPDATE, PATH_UPDATE_HENDLE); + addButton(webContentBuffer, S_UPDATE, PATH_UPDATE_HENDLE); #endif - addButton(content, "Factory reset", PATH_FACTORY_RESET); - addFormHeaderEnd(content); - addButton(content, S_RETURN, ""); + addButton(webContentBuffer, F("Factory reset"), PATH_FACTORY_RESET); + addFormHeaderEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, ""); - WebServer->sendContent(content); + WebServer->sendContent(); } \ No newline at end of file diff --git a/src/SuplaWebPageUpload.cpp b/src/SuplaWebPageUpload.cpp index c364e500..c05e6f74 100644 --- a/src/SuplaWebPageUpload.cpp +++ b/src/SuplaWebPageUpload.cpp @@ -24,23 +24,22 @@ void handleUpload(int save) { return WebServer->httpServer.requestAuthentication(); } - String content = ""; - content += SuplaSaveResult(save); - content += SuplaJavaScript(); - content += F("
"); - content += F("

"); - content += "Wgraj konfiguracje"; - content += F("

"); - content += F("
"); - content += FPSTR(uploadIndex); - content += F("
"); - content += F("

"); + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(); + webContentBuffer += F("
"); + webContentBuffer += F("

"); + webContentBuffer += F("Wgraj konfiguracje"); + webContentBuffer += F("

"); + webContentBuffer += F("
"); + webContentBuffer += FPSTR(uploadIndex); + webContentBuffer += F("
"); + webContentBuffer += F("

"); - WebServer->sendContent(content); + WebServer->sendContent(); // WebServer->httpServer.send(200, PSTR("text/html"), FPSTR(uploadIndex)); } diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index 38163f35..72c18bc9 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -24,6 +24,8 @@ #include "SuplaTemplateBoard.h" #include "Markup.h" +String webContentBuffer; + SuplaWebServer::SuplaWebServer() { } @@ -81,7 +83,7 @@ void SuplaWebServer::handle() { if (!httpServer.authenticate(this->www_username, this->www_password)) return httpServer.requestAuthentication(); } - this->sendContent(supla_webpage_start(0)); + supla_webpage_start(0); } void SuplaWebServer::handleSave() { @@ -114,17 +116,17 @@ void SuplaWebServer::handleSave() { case E_CONFIG_OK: // Serial.println(F("E_CONFIG_OK: Dane zapisane")); if (ConfigESP->configModeESP == NORMAL_MODE) { - this->sendContent(supla_webpage_start(1)); + supla_webpage_start(1); ConfigESP->rebootESP(); } else { - this->sendContent(supla_webpage_start(7)); + supla_webpage_start(7); } break; case E_CONFIG_FILE_OPEN: // Serial.println(F("E_CONFIG_FILE_OPEN: Couldn't open file")); - this->sendContent(supla_webpage_start(4)); + supla_webpage_start(4); break; } } @@ -134,51 +136,50 @@ void SuplaWebServer::handleDeviceSettings() { if (!httpServer.authenticate(www_username, www_password)) return httpServer.requestAuthentication(); } - this->sendContent(deviceSettings(0)); + deviceSettings(0); } -String SuplaWebServer::supla_webpage_start(int save) { - String content = F(""); - content += SuplaSaveResult(save); - content += SuplaJavaScript(); +void SuplaWebServer::supla_webpage_start(int save) { + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(); - addForm(content, F("post")); - addFormHeader(content, S_SETTING_WIFI_SSID); - addTextBox(content, INPUT_WIFI_SSID, S_WIFI_SSID, KEY_WIFI_SSID, 0, MAX_SSID, true); - addTextBoxPassword(content, INPUT_WIFI_PASS, S_WIFI_PASS, KEY_WIFI_PASS, MIN_PASSWORD, MAX_PASSWORD, true); - addTextBox(content, INPUT_HOSTNAME, S_HOST_NAME, KEY_HOST_NAME, 0, MAX_HOSTNAME, true); - addFormHeaderEnd(content); + addForm(webContentBuffer, F("post")); + addFormHeader(webContentBuffer, S_SETTING_WIFI_SSID); + addTextBox(webContentBuffer, INPUT_WIFI_SSID, S_WIFI_SSID, KEY_WIFI_SSID, 0, MAX_SSID, true); + addTextBoxPassword(webContentBuffer, INPUT_WIFI_PASS, S_WIFI_PASS, KEY_WIFI_PASS, MIN_PASSWORD, MAX_PASSWORD, true); + addTextBox(webContentBuffer, INPUT_HOSTNAME, S_HOST_NAME, KEY_HOST_NAME, 0, MAX_HOSTNAME, true); + addFormHeaderEnd(webContentBuffer); - addFormHeader(content, S_SETTING_SUPLA); - addTextBox(content, INPUT_SERVER, S_SUPLA_SERVER, KEY_SUPLA_SERVER, DEFAULT_SERVER, 0, MAX_SUPLA_SERVER, true); - addTextBox(content, INPUT_EMAIL, S_SUPLA_EMAIL, KEY_SUPLA_EMAIL, DEFAULT_EMAIL, 0, MAX_EMAIL, true); - addFormHeaderEnd(content); + addFormHeader(webContentBuffer, S_SETTING_SUPLA); + addTextBox(webContentBuffer, INPUT_SERVER, S_SUPLA_SERVER, KEY_SUPLA_SERVER, DEFAULT_SERVER, 0, MAX_SUPLA_SERVER, true); + addTextBox(webContentBuffer, INPUT_EMAIL, S_SUPLA_EMAIL, KEY_SUPLA_EMAIL, DEFAULT_EMAIL, 0, MAX_EMAIL, true); + addFormHeaderEnd(webContentBuffer); - addFormHeader(content, S_SETTING_ADMIN); - addTextBox(content, INPUT_MODUL_LOGIN, S_LOGIN, KEY_LOGIN, 0, MAX_MLOGIN, true); - addTextBoxPassword(content, INPUT_MODUL_PASS, S_LOGIN_PASS, KEY_LOGIN_PASS, MIN_PASSWORD, MAX_MPASSWORD, true); - addFormHeaderEnd(content); + addFormHeader(webContentBuffer, S_SETTING_ADMIN); + addTextBox(webContentBuffer, INPUT_MODUL_LOGIN, S_LOGIN, KEY_LOGIN, 0, MAX_MLOGIN, true); + addTextBoxPassword(webContentBuffer, INPUT_MODUL_PASS, S_LOGIN_PASS, KEY_LOGIN_PASS, MIN_PASSWORD, MAX_MPASSWORD, true); + addFormHeaderEnd(webContentBuffer); #ifdef SUPLA_ROLLERSHUTTER uint8_t maxrollershutter = ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); if (maxrollershutter >= 2) { - addFormHeader(content, S_ROLLERSHUTTERS); - addNumberBox(content, INPUT_ROLLERSHUTTER, S_QUANTITY, KEY_MAX_ROLLERSHUTTER, (maxrollershutter / 2)); - addFormHeaderEnd(content); + addFormHeader(webContentBuffer, S_ROLLERSHUTTERS); + addNumberBox(webContentBuffer, INPUT_ROLLERSHUTTER, S_QUANTITY, KEY_MAX_ROLLERSHUTTER, (maxrollershutter / 2)); + addFormHeaderEnd(webContentBuffer); } #endif #ifdef SUPLA_DS18B20 - WebPageSensor->showDS18B20(content, true); + WebPageSensor->showDS18B20(true); #endif - addButtonSubmit(content, S_SAVE); - addFormEnd(content); + addButtonSubmit(webContentBuffer, S_SAVE); + addFormEnd(webContentBuffer); - addButton(content, S_DEVICE_SETTINGS, PATH_DEVICE_SETTINGS); - addButton(content, F("Tools"), PATH_TOOLS); + addButton(webContentBuffer, S_DEVICE_SETTINGS, PATH_DEVICE_SETTINGS); + addButton(webContentBuffer, F("Tools"), PATH_TOOLS); - return content; + WebServer->sendContent(); } void SuplaWebServer::supla_webpage_reboot() { @@ -186,65 +187,63 @@ void SuplaWebServer::supla_webpage_reboot() { if (!httpServer.authenticate(www_username, www_password)) return httpServer.requestAuthentication(); } - this->sendContent(supla_webpage_start(2)); + supla_webpage_start(2); ConfigESP->rebootESP(); } -String SuplaWebServer::deviceSettings(int save) { - String content = ""; +void SuplaWebServer::deviceSettings(int save) { + webContentBuffer += SuplaSaveResult(save); + webContentBuffer += SuplaJavaScript(PATH_DEVICE_SETTINGS); - content += SuplaSaveResult(save); - content += SuplaJavaScript(PATH_DEVICE_SETTINGS); + webContentBuffer += F("
"); - content += F(""); - - addFormHeader(content, S_TEMPLATE_BOARD); + addFormHeader(webContentBuffer, S_TEMPLATE_BOARD); uint8_t selected = ConfigManager->get(KEY_BOARD)->getValueInt(); - addListBox(content, INPUT_BOARD, S_TYPE, BOARD_P, MAX_MODULE, selected); - addFormHeaderEnd(content); + addListBox(webContentBuffer, INPUT_BOARD, S_TYPE, BOARD_P, MAX_MODULE, selected); + addFormHeaderEnd(webContentBuffer); - content += F("


"); + webContentBuffer += F("

"); - addFormHeader(content, S_DEVICE_SETTINGS); + addFormHeader(webContentBuffer, S_DEVICE_SETTINGS); #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) - addButton(content, S_RELAYS, PATH_RELAY); + addButton(webContentBuffer, S_RELAYS, PATH_RELAY); #endif #ifdef SUPLA_BUTTON - addButton(content, S_BUTTONS, PATH_CONTROL); + addButton(webContentBuffer, S_BUTTONS, PATH_CONTROL); #endif #ifdef SUPLA_LIMIT_SWITCH - addButton(content, F("KONTAKTRON"), PATH_SWITCH); + addButton(webContentBuffer, F("KONTAKTRON"), PATH_SWITCH); #endif #if defined(SUPLA_DS18B20) || defined(SUPLA_DHT11) || defined(SUPLA_DHT22) || defined(SUPLA_SI7021_SONOFF) - addButton(content, S_SENSORS_1WIRE, PATH_1WIRE); + addButton(webContentBuffer, S_SENSORS_1WIRE, PATH_1WIRE); #endif #if defined(SUPLA_BME280) || defined(SUPLA_SI7021) || defined(SUPLA_SHT3x) || defined(SUPLA_OLED) || defined(SUPLA_MCP23017) - addButton(content, S_SENSORS_I2C, PATH_I2C); + addButton(webContentBuffer, S_SENSORS_I2C, PATH_I2C); #endif #if defined(SUPLA_MAX6675) - addButton(content, S_SENSORS_SPI, PATH_SPI); + addButton(webContentBuffer, S_SENSORS_SPI, PATH_SPI); #endif #if defined(SUPLA_HC_SR04) || defined(SUPLA_IMPULSE_COUNTER) - addButton(content, S_SENSORS_OTHER, PATH_OTHER); + addButton(webContentBuffer, S_SENSORS_OTHER, PATH_OTHER); #endif #ifdef SUPLA_CONFIG - addButton(content, S_LED_BUTTON_CFG, PATH_CONFIG); + addButton(webContentBuffer, S_LED_BUTTON_CFG, PATH_CONFIG); #endif - addFormHeaderEnd(content); - addButton(content, S_RETURN, ""); + addFormHeaderEnd(webContentBuffer); + addButton(webContentBuffer, S_RETURN, ""); - return content; + WebServer->sendContent(); } void SuplaWebServer::handleBoardSave() { @@ -269,88 +268,14 @@ void SuplaWebServer::handleBoardSave() { switch (ConfigManager->save()) { case E_CONFIG_OK: - WebServer->sendContent(deviceSettings(1)); + deviceSettings(1); break; case E_CONFIG_FILE_OPEN: - WebServer->sendContent(deviceSettings(2)); + deviceSettings(2); break; } } -const String& SuplaWebServer::SuplaIconEdit() { - return F( - ""); -} - -void SuplaWebServer::sendContent(const String& content) { - // httpServer.send(200, "text/html", ""); - // const int bufferSize = 1000; - // String _buffer; - //_buffer.reserve(bufferSize); - // int bufferCounter = 0; - - int fileSize = content.length(); - -#ifdef DEBUG_MODE - Serial.print(F("Content size: ")); - Serial.println(fileSize); - checkRAM(); -#endif - - httpServer.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - httpServer.sendHeader("Pragma", "no-cache"); - httpServer.sendHeader("Expires", "-1"); - httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer.chunkedResponseModeStart(200, "text/html"); - - httpServer.sendContent_P(HTTP_META); - httpServer.sendContent_P(HTTP_FAVICON); - httpServer.sendContent_P(HTTP_STYLE); - httpServer.sendContent_P(HTTP_LOGO); - - String summary = FPSTR(HTTP_SUMMARY); - - summary.replace("{h}", ConfigManager->get(KEY_HOST_NAME)->getValue()); - summary.replace("{s}", ConfigESP->getLastStatusSupla()); - summary.replace("{v}", Supla::Channel::reg_dev.SoftVer); - summary.replace("{g}", ConfigManager->get(KEY_SUPLA_GUID)->getValueHex(SUPLA_GUID_SIZE)); - summary.replace("{m}", ConfigESP->getMacAddress(true)); - summary.replace("{f}", String(ESP.getFreeHeap() / 1024.0)); - httpServer.sendContent(summary); - httpServer.sendContent_P(HTTP_COPYRIGHT); - - // httpServer.send(200, "text/html", ""); - /*for (int i = 0; i < fileSize; i++) { - _buffer += content[i]; - bufferCounter++; - - if (bufferCounter >= bufferSize) { - httpServer.sendContent(_buffer); - yield(); - bufferCounter = 0; - _buffer = ""; - } - } - if (bufferCounter > 0) { - httpServer.sendContent(_buffer); - yield(); - bufferCounter = 0; - _buffer = ""; - }*/ - - httpServer.sendContent(content); - httpServer.sendContent_P(HTTP_RBT); - httpServer.chunkedResponseFinalize(); -} - -String webContentBuffer; - void SuplaWebServer::sendContent() { // httpServer.send(200, "text/html", ""); // const int bufferSize = 1000; diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 958b0e2e..9479bf2b 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -80,10 +80,9 @@ class SuplaWebServer : public Supla::Element { char www_username[MAX_MLOGIN]; char www_password[MAX_MPASSWORD]; - const String& SuplaIconEdit(); - String supla_webpage_start(int save); + void supla_webpage_start(int save); - void sendContent(const String& content); + //void sendContent(const String& content); void sendContent(); MyWebServer httpServer; @@ -107,8 +106,7 @@ class SuplaWebServer : public Supla::Element { void createWebServer(); void supla_webpage_reboot(); - String deviceSettings(int save); - String loginSettings(); + void deviceSettings(int save); void handleNotFound(); }; From 78e68b963adaadddea923ac436cde120b23b07e1 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Sun, 17 Jan 2021 16:36:39 +0100 Subject: [PATCH 106/233] poprawki --- platformio.ini | 2 +- src/GUIGenericCommon.cpp | 18 +++++++++++++++++ src/Markup.cpp | 4 ++-- src/SuplaCommonPROGMEM.h | 2 +- src/SuplaConfigESP.cpp | 36 +++++++++------------------------ src/SuplaConfigESP.h | 2 +- src/SuplaConfigManager.cpp | 4 ++-- src/SuplaHTTPUpdateServer.cpp | 7 ++++--- src/SuplaWebPageSensor.cpp | 38 +++++++++++++++++------------------ src/SuplaWebServer.cpp | 3 ++- 10 files changed, 59 insertions(+), 57 deletions(-) diff --git a/platformio.ini b/platformio.ini index bd7f22d3..d19e5603 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.10"' +build_flags = -D BUILD_VERSION='"GUI 1.1.11"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/GUIGenericCommon.cpp b/src/GUIGenericCommon.cpp index 86e92967..5b8efdb9 100644 --- a/src/GUIGenericCommon.cpp +++ b/src/GUIGenericCommon.cpp @@ -35,4 +35,22 @@ int getNumberChannels() { } return maxFrame; +} + +uint32_t lowestRAM = 0; +uint32_t lowestFreeStack = 0; + +void checkRAM() { + uint32_t freeRAM = ESP.getFreeHeap(); + Serial.print(F("freeRAM: ")); + Serial.println(freeRAM); + if (freeRAM <= lowestRAM) { + lowestRAM = freeRAM; + } + uint32_t freeStack = ESP.getFreeContStack(); + Serial.print(F("freeStack: ")); + Serial.println(freeStack); + if (freeStack <= lowestFreeStack) { + lowestFreeStack = freeStack; + } } \ No newline at end of file diff --git a/src/Markup.cpp b/src/Markup.cpp index d58d9739..a9c864c4 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -163,7 +163,7 @@ void addListGPIOBox(String& html, const String& input_id, const String& name, ui void addListMCP23017GPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { if (nr == 1) { uint8_t address = ConfigESP->getAdressMCP23017(nr, function); - addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 4, address); + addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 3, address); } html += F(""); html += F(""); @@ -198,7 +198,7 @@ void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const Stri } html += name; if (ConfigESP->getGpioMCP23017(nr, function) != OFF_GPIO) { - html += PGMT(ICON_EDIT); + // html += PGMT(ICON_EDIT); html += F(""); } html += F(""); diff --git a/src/SuplaConfigESP.h b/src/SuplaConfigESP.h index f5711b8a..61c3e614 100644 --- a/src/SuplaConfigESP.h +++ b/src/SuplaConfigESP.h @@ -37,6 +37,7 @@ enum _ConfigMode FACTORYRESET }; +#define OFF_GPIO 17 #define OFF_MCP23017 2 typedef struct { diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 4f9fc3d3..24f60372 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -176,6 +176,7 @@ SuplaConfigManager::SuplaConfigManager() { this->addKey(KEY_CFG_MODE, "0", 2); this->addKey(KEY_ADDR_DS18B20, MAX_DS18B20_ADDRESS_HEX * MAX_DS18B20); this->addKey(KEY_NAME_SENSOR, MAX_DS18B20_NAME * MAX_DS18B20); + this->addKey(KEY_LEVEL_LED, "0", 1); this->load(); // switch (this->load()) { diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index 8d0d1efe..ef012d53 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -73,8 +73,8 @@ enum _key KEY_CFG_MODE, KEY_ADDR_DS18B20, KEY_NAME_SENSOR, - KEY_GPIO, + KEY_LEVEL_LED = KEY_GPIO + MAX_GPIO + 1 //KEY_DS = KEY_GPIO + MAX_GPIO + MAX_DS18B20, //KEY_DS_NAME = KEY_DS + MAX_DS18B20 }; diff --git a/src/SuplaDeviceGUI.cpp b/src/SuplaDeviceGUI.cpp index c35c7e8e..c73f7fc8 100644 --- a/src/SuplaDeviceGUI.cpp +++ b/src/SuplaDeviceGUI.cpp @@ -56,12 +56,13 @@ void begin() { #if defined(SUPLA_RELAY) || defined(SUPLA_ROLLERSHUTTER) void addRelayButton(uint8_t nr) { uint8_t pinRelay, pinButton, pinLED; - bool highIsOn; + bool highIsOn, levelLed; pinRelay = ConfigESP->getGpio(nr, FUNCTION_RELAY); pinButton = ConfigESP->getGpio(nr, FUNCTION_BUTTON); pinLED = ConfigESP->getGpio(nr, FUNCTION_LED); highIsOn = ConfigESP->getLevel(nr, FUNCTION_RELAY); + levelLed = ConfigManager->get(KEY_LEVEL_LED)->getValueInt(); if (pinRelay != OFF_GPIO) { relay.push_back(new Supla::Control::Relay(pinRelay, highIsOn)); @@ -90,7 +91,7 @@ void addRelayButton(uint8_t nr) { } if (pinLED != OFF_GPIO) { - new Supla::Control::PinStatusLed(pinRelay, pinLED, highIsOn); + new Supla::Control::PinStatusLed(pinRelay, pinLED, !levelLed); } } } @@ -123,7 +124,7 @@ std::vector RollerShutterButtonClose; void addRolleShutter(uint8_t nr) { int pinRelayUp, pinRelayDown, pinButtonUp, pinButtonDown, pinLedUP, pinLedDown; - bool highIsOn; + bool highIsOn, levelLed; pinRelayUp = ConfigESP->getGpio(nr, FUNCTION_RELAY); pinRelayDown = ConfigESP->getGpio(nr + 1, FUNCTION_RELAY); @@ -133,6 +134,7 @@ void addRolleShutter(uint8_t nr) { pinLedDown = ConfigESP->getGpio(nr + 1, FUNCTION_LED); highIsOn = ConfigESP->getLevel(nr, FUNCTION_RELAY); + levelLed = ConfigManager->get(KEY_LEVEL_LED)->getValueInt(); RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); if (pinButtonUp != OFF_GPIO) @@ -150,16 +152,16 @@ void addRolleShutter(uint8_t nr) { eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); if (pinLedUP != OFF_GPIO) { - new Supla::Control::PinStatusLed(pinRelayUp, pinLedUP, highIsOn); + new Supla::Control::PinStatusLed(pinRelayUp, pinLedUP, !levelLed); } if (pinLedDown != OFF_GPIO) { - new Supla::Control::PinStatusLed(pinRelayDown, pinLedDown, highIsOn); + new Supla::Control::PinStatusLed(pinRelayDown, pinLedDown, !levelLed); } } void addRolleShutterMomentary(uint8_t nr) { int pinRelayUp, pinRelayDown, pinButtonUp, pinButtonDown, pinLedUP, pinLedDown; - bool highIsOn; + bool highIsOn, levelLed; pinRelayUp = ConfigESP->getGpio(nr, FUNCTION_RELAY); pinRelayDown = ConfigESP->getGpio(nr + 1, FUNCTION_RELAY); @@ -169,6 +171,7 @@ void addRolleShutterMomentary(uint8_t nr) { pinLedDown = ConfigESP->getGpio(nr + 1, FUNCTION_LED); highIsOn = ConfigESP->getLevel(nr, FUNCTION_RELAY); + levelLed = ConfigManager->get(KEY_LEVEL_LED)->getValueInt(); RollerShutterRelay.push_back(new Supla::Control::RollerShutter(pinRelayUp, pinRelayDown, highIsOn)); if (pinButtonUp != OFF_GPIO) @@ -186,10 +189,10 @@ void addRolleShutterMomentary(uint8_t nr) { eeprom.setStateSavePeriod(TIME_SAVE_PERIOD_SEK * 1000); if (pinLedUP != OFF_GPIO) { - new Supla::Control::PinStatusLed(pinRelayUp, pinLedUP, highIsOn); + new Supla::Control::PinStatusLed(pinRelayUp, pinLedUP, !levelLed); } if (pinLedDown != OFF_GPIO) { - new Supla::Control::PinStatusLed(pinRelayDown, pinLedDown, highIsOn); + new Supla::Control::PinStatusLed(pinRelayDown, pinLedDown, !levelLed); } } #endif diff --git a/src/SuplaWebPageStatusLed.cpp b/src/SuplaWebPageStatusLed.cpp index 1c09d085..b6d110eb 100644 --- a/src/SuplaWebPageStatusLed.cpp +++ b/src/SuplaWebPageStatusLed.cpp @@ -25,6 +25,10 @@ void handleStatusLedSave() { return WebServer->httpServer.requestAuthentication(); } + if (strcmp(WebServer->httpServer.arg(INPUT_LEVEL_LED).c_str(), "") != 0) { + ConfigManager->set(KEY_LEVEL_LED, WebServer->httpServer.arg(INPUT_LEVEL_LED).c_str()); + } + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { if (!WebServer->saveGPIO(INPUT_LED, FUNCTION_LED, nr)) { webStatusLed(6); @@ -43,13 +47,15 @@ void handleStatusLedSave() { } void webStatusLed(int save) { - uint8_t nr; + uint8_t nr, selected; webContentBuffer += SuplaSaveResult(save); webContentBuffer += SuplaJavaScript(PATH_LED); addForm(webContentBuffer, F("post"), PATH_SAVE_LED); addFormHeader(webContentBuffer, String(S_GPIO_SETTINGS_FOR) + F(" LED")); + selected = ConfigManager->get(KEY_LEVEL_LED)->getValueInt(); + addListBox(webContentBuffer, INPUT_LEVEL_LED, S_STATE_CONTROL, LEVEL_P, 2, selected); for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RELAY)->getValueInt(); nr++) { addListGPIOBox(webContentBuffer, INPUT_LED, F("LED"), FUNCTION_LED, nr); diff --git a/src/SuplaWebPageStatusLed.h b/src/SuplaWebPageStatusLed.h index 332eef9c..3da59441 100644 --- a/src/SuplaWebPageStatusLed.h +++ b/src/SuplaWebPageStatusLed.h @@ -1,9 +1,10 @@ #ifndef SuplaWebPageStatusLed_h #define SuplaWebPageStatusLed_h -#define PATH_LED "led" -#define PATH_SAVE_LED "saveled" -#define INPUT_LED "led" +#define PATH_LED "led" +#define PATH_SAVE_LED "saveled" +#define INPUT_LED "led" +#define INPUT_LEVEL_LED "ill" void createWebStatusLed(); void handleStatusLed(); diff --git a/src/SuplaWebServer.h b/src/SuplaWebServer.h index 9479bf2b..ce5e8d77 100644 --- a/src/SuplaWebServer.h +++ b/src/SuplaWebServer.h @@ -32,9 +32,6 @@ #define DEFAULT_LOGIN "admin" #define DEFAULT_PASSWORD "password" -#define MAX_GPIO 13 -#define OFF_GPIO 17 - #define PATH_START "/" #define PATH_SAVE_LOGIN "savelogin" #define PATH_REBOT "rbt" diff --git a/src/language/pl.h b/src/language/pl.h index f414b9d3..62eecf04 100644 --- a/src/language/pl.h +++ b/src/language/pl.h @@ -22,11 +22,11 @@ #define S_TYPE "Rodzaj" #define S_RELAYS "PRZEKAŹNIKI" #define S_BUTTONS "PRZYCISKI" -#define S_SENSORS_1WIRE "SENSORY 1Wire" -#define S_SENSORS_I2C "i2c" -#define S_SENSORS_SPI "SENSORY SPI" -#define S_SENSORS_OTHER "SENSORY INNE" -#define S_LED_BUTTON_CFG "LED, BUTTON CONFIG" +#define S_SENSORS_1WIRE "1WIRE" +#define S_SENSORS_I2C "I2C" +#define S_SENSORS_SPI "SPI" +#define S_SENSORS_OTHER "INNE" +#define S_LED_BUTTON_CFG "KONFIGURACJA" #define S_CFG_MODE "Tryb" #define S_QUANTITY "ILOŚĆ" #define S_GPIO_SETTINGS_FOR_RELAYS "Ustawienie GPIO dla przekaźników" From 2a37099c40ae7811063bc93177199dd2c7649196 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Wed, 27 Jan 2021 06:13:03 +0100 Subject: [PATCH 117/233] dodatkowe wsparcie dla OLEDa --- .../.github/stale.yml | 17 - lib/esp8266-oled-ssd1306-master/.travis.yml | 8 +- lib/esp8266-oled-ssd1306-master/README.md | 8 +- lib/esp8266-oled-ssd1306-master/keywords.txt | 100 +++ lib/esp8266-oled-ssd1306-master/library.json | 3 +- .../library.properties | 3 +- .../platformio.ini | 2 +- .../resources/DemoFrame1.jpg | Bin 16026 -> 0 bytes .../resources/DemoFrame2.jpg | Bin 19502 -> 0 bytes .../resources/DemoFrame3.jpg | Bin 21777 -> 0 bytes .../resources/DemoFrame4.jpg | Bin 25325 -> 0 bytes .../resources/FontTool.png | Bin 14052 -> 0 bytes .../resources/SPI_version.jpg | Bin 26912 -> 0 bytes .../resources/glyphEditor.html | 664 ------------------ .../resources/glyphEditor.png | Bin 68751 -> 0 bytes .../resources/xbmPreview.png | Bin 41692 -> 0 bytes .../src/OLEDDisplay.cpp | 18 +- .../src/OLEDDisplay.h | 1 + .../src/OLEDDisplayUi.cpp | 7 +- .../src/SSD1306Wire.h | 4 +- platformio.ini | 2 +- src/SuplaCommonPROGMEM.h | 5 + src/SuplaConfigManager.cpp | 2 + src/SuplaConfigManager.h | 12 +- src/SuplaOled.cpp | 32 +- src/SuplaOled.h | 10 +- src/SuplaWebPageSensor.cpp | 21 +- src/SuplaWebPageSensor.h | 3 + 28 files changed, 208 insertions(+), 714 deletions(-) delete mode 100644 lib/esp8266-oled-ssd1306-master/.github/stale.yml create mode 100644 lib/esp8266-oled-ssd1306-master/keywords.txt delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/DemoFrame1.jpg delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/DemoFrame2.jpg delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/DemoFrame3.jpg delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/DemoFrame4.jpg delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/FontTool.png delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/SPI_version.jpg delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/glyphEditor.html delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/glyphEditor.png delete mode 100644 lib/esp8266-oled-ssd1306-master/resources/xbmPreview.png diff --git a/lib/esp8266-oled-ssd1306-master/.github/stale.yml b/lib/esp8266-oled-ssd1306-master/.github/stale.yml deleted file mode 100644 index 9bcd4eb1..00000000 --- a/lib/esp8266-oled-ssd1306-master/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 180 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 14 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false diff --git a/lib/esp8266-oled-ssd1306-master/.travis.yml b/lib/esp8266-oled-ssd1306-master/.travis.yml index 1ade30e9..49ea5b62 100644 --- a/lib/esp8266-oled-ssd1306-master/.travis.yml +++ b/lib/esp8266-oled-ssd1306-master/.travis.yml @@ -1,12 +1,15 @@ +# documentation at https://docs.platformio.org/en/latest/integration/ci/travis.html + language: python python: - - "2.7" + - "3.7" # Cache PlatformIO packages using Travis CI container-based infrastructure sudo: false cache: directories: - "~/.platformio" + - $HOME/.cache/pip env: - PLATFORMIO_CI_SRC=examples/SSD1306UiDemo @@ -19,7 +22,8 @@ env: install: - pip install -U platformio - - platformio lib install 44 + - pio update + - platformio lib -g install "paulstoffregen/Time@^1.6" script: - platformio ci --lib="." --board=nodemcuv2 diff --git a/lib/esp8266-oled-ssd1306-master/README.md b/lib/esp8266-oled-ssd1306-master/README.md index ff3a6ca4..ed2d8b13 100644 --- a/lib/esp8266-oled-ssd1306-master/README.md +++ b/lib/esp8266-oled-ssd1306-master/README.md @@ -2,14 +2,16 @@ # ThingPulse OLED SSD1306 (ESP8266/ESP32/Mbed-OS) -> We just released version 4.0.0. Please have a look at our [upgrade guide](UPGRADE-4.0.md) - This is a driver for SSD1306 128x64, 128x32, 64x48 and 64x32 OLED displays running on the Arduino/ESP8266 & ESP32 and mbed-os platforms. Can be used with either the I2C or SPI version of the display. +This library drives the OLED display included in the [ThingPulse IoT starter kit](https://thingpulse.com/product/esp8266-iot-electronics-starter-kit-weatherstation-planespotter-worldclock/) aka classic kit aka weather station kit. + +[![ThingPulse ESP8266 WeatherStation Classic Kit](https://github.com/ThingPulse/esp8266-weather-station/blob/master/resources/ThingPulse-ESP8266-Weather-Station.jpeg?raw=true)](https://thingpulse.com/product/esp8266-iot-electronics-starter-kit-weatherstation-planespotter-worldclock/) + You can either download this library as a zip file and unpack it to your Arduino/libraries folder or find it in the Arduino library manager under "ESP8266 and ESP32 Oled Driver for SSD1306 display". For mbed-os a copy of the files are available as an mbed-os library. -It is also available as a platformio library. Just execute the following command: +It is also available as a [PlatformIO library](https://platformio.org/lib/show/562/ESP8266%20and%20ESP32%20OLED%20driver%20for%20SSD1306%20displays/examples). Just execute the following command: ``` platformio lib install 562 ``` diff --git a/lib/esp8266-oled-ssd1306-master/keywords.txt b/lib/esp8266-oled-ssd1306-master/keywords.txt new file mode 100644 index 00000000..db59d6e8 --- /dev/null +++ b/lib/esp8266-oled-ssd1306-master/keywords.txt @@ -0,0 +1,100 @@ +####################################### +# Syntax Coloring Map List +####################################### + + +####################################### +# Constants (LITERAL1) +####################################### +INVERSE LITERAL1 + +TEXT_ALIGN_LEFT LITERAL1 +TEXT_ALIGN_RIGHT LITERAL1 +TEXT_ALIGN_CENTER LITERAL1 +TEXT_ALIGN_CENTER_BOTH LITERAL1 + +GEOMETRY_128_64 LITERAL1 +GEOMETRY_128_32 LITERAL1 +GEOMETRY_RAWMODE LITERAL1 + +ArialMT_Plain_10 LITERAL1 +ArialMT_Plain_16 LITERAL1 +ArialMT_Plain_24 LITERAL1 + +SLIDE_UP LITERAL1 +SLIDE_DOWN LITERAL1 +SLIDE_LEFT LITERAL1 +SLIDE_RIGHT LITERAL1 + +TOP LITERAL1 +RIGHT LITERAL1 +BOTTOM LITERAL1 +LEFT LITERAL1 + +LEFT_RIGHT LITERAL1 +RIGHT_LEFT LITERAL1 + +IN_TRANSITION LITERAL1 +FIXED LITERAL1 + + +####################################### +# Datatypes (KEYWORD1) +####################################### +OLEDDisplay KEYWORD1 +OLEDDisplayUi KEYWORD1 + +SH1106Wire KEYWORD1 +SH1106Brzo KEYWORD1 +SH1106Spi KEYWORD1 + +SSD1306Wire KEYWORD1 +SSD1306Brzo KEYWORD1 +SSD1306I2C KEYWORD1 +SSD1306Spi KEYWORD1 + + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +allocateBuffer KEYWORD2 +init KEYWORD2 +resetDisplay KEYWORD2 +setColor KEYWORD2 +getColor KEYWORD2 +setPixel KEYWORD2 +setPixelColor KEYWORD2 +clearPixel KEYWORD2 +drawLine KEYWORD2 +drawRect KEYWORD2 +fillRect KEYWORD2 +drawCircle KEYWORD2 +drawCircleQuads KEYWORD2 +fillCircle KEYWORD2 +fillRing KEYWORD2 +drawHorizontalLine KEYWORD2 +drawVerticalLine KEYWORD2 +drawProgressBar KEYWORD2 +drawFastImage KEYWORD2 +drawXbm KEYWORD2 +drawIco16x16 KEYWORD2 +drawString KEYWORD2 +drawStringMaxWidth KEYWORD2 +getStringWidth KEYWORD2 +setTextAlignment KEYWORD2 +setFont KEYWORD2 +setFontTableLookupFunction KEYWORD2 +displayOn KEYWORD2 +displayOff KEYWORD2 +invertDisplay KEYWORD2 +normalDisplay KEYWORD2 +setContrast KEYWORD2 +setBrightness KEYWORD2 +resetOrientation KEYWORD2 +flipScreenVertically KEYWORD2 +mirrorScreen KEYWORD2 +display KEYWORD2 +setLogBuffer KEYWORD2 +drawLogBuffer KEYWORD2 +getWidth KEYWORD2 +getHeight KEYWORD2 diff --git a/lib/esp8266-oled-ssd1306-master/library.json b/lib/esp8266-oled-ssd1306-master/library.json index 446d8e08..acd11ce9 100644 --- a/lib/esp8266-oled-ssd1306-master/library.json +++ b/lib/esp8266-oled-ssd1306-master/library.json @@ -1,8 +1,9 @@ { "name": "ESP8266 and ESP32 OLED driver for SSD1306 displays", - "version": "4.1.0", + "version": "4.2.0", "keywords": "ssd1306, oled, display, i2c", "description": "I2C display driver for SSD1306 OLED displays connected to ESP8266, ESP32, Mbed-OS", + "license": "MIT", "repository": { "type": "git", diff --git a/lib/esp8266-oled-ssd1306-master/library.properties b/lib/esp8266-oled-ssd1306-master/library.properties index a62c352f..e931a3f6 100644 --- a/lib/esp8266-oled-ssd1306-master/library.properties +++ b/lib/esp8266-oled-ssd1306-master/library.properties @@ -1,5 +1,5 @@ name=ESP8266 and ESP32 OLED driver for SSD1306 displays -version=4.1.0 +version=4.2.0 author=ThingPulse, Fabrice Weinberg maintainer=ThingPulse sentence=I2C display driver for SSD1306 OLED displays connected to ESP8266, ESP32, Mbed-OS @@ -7,3 +7,4 @@ paragraph=The following geometries are currently supported: 128x64, 128x32, 64x4 category=Display url=https://github.com/ThingPulse/esp8266-oled-ssd1306 architectures=esp8266,esp32 +license=MIT diff --git a/lib/esp8266-oled-ssd1306-master/platformio.ini b/lib/esp8266-oled-ssd1306-master/platformio.ini index db055d8d..530c046e 100644 --- a/lib/esp8266-oled-ssd1306-master/platformio.ini +++ b/lib/esp8266-oled-ssd1306-master/platformio.ini @@ -13,7 +13,7 @@ platform = espressif8266 board = d1_mini framework = arduino upload_speed = 921600 -board_f_cpu = 160000000L +board_build.f_cpu = 160000000L upload_port = /dev/cu.SLAB_USBtoUART monitor_port = /dev/cu.SLAB_USBtoUART lib_deps = diff --git a/lib/esp8266-oled-ssd1306-master/resources/DemoFrame1.jpg b/lib/esp8266-oled-ssd1306-master/resources/DemoFrame1.jpg deleted file mode 100644 index 536b570db09931ec634b38777033432276bafed8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16026 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 diff --git a/lib/esp8266-oled-ssd1306-master/resources/DemoFrame2.jpg b/lib/esp8266-oled-ssd1306-master/resources/DemoFrame2.jpg deleted file mode 100644 index 8dccbcd0210b5c1dd7bad824982c1ecbf5c1edc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19502 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 C1xwrj diff --git a/lib/esp8266-oled-ssd1306-master/resources/DemoFrame4.jpg b/lib/esp8266-oled-ssd1306-master/resources/DemoFrame4.jpg deleted file mode 100644 index 99cbe1bc41ce7fe01700f5cd00bf04da68f9602a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25325 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 EUrx=8H~;_u diff --git a/lib/esp8266-oled-ssd1306-master/resources/SPI_version.jpg b/lib/esp8266-oled-ssd1306-master/resources/SPI_version.jpg deleted file mode 100644 index 115c9f3bccef1177204c88ef8b8cb8f22daa9e7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26912 zcmeFXXH*nj^Do*1NDw6FETBl193&Y?A|N0+j3i;mISGt{pk&EP6af(hM#&k;nUO4U zfFTG7%z(fM1KjpW@Bf~4KHT?yxNF^Yp54>a)wOGfs@~yO)qVc$d=a3&tFEOEKp+r6 z6Fh+PC8lh(K-ePy(9r<|0RSKcC;=|u0!Tr?n|25lKmgLjAl(n4{(~k1>8}u4fbb6) zFb5+3kIW22;t!f0lvyO8`%4C_={XD_`m4^LFRuJeGhQo{_@gZ_kLg~a7~cKy+O~Li{D+-<><|WZTbZ!#lPZBAqzuE#dNhbS?2BA*!#eZZ*lj;B9gP4hSCOPDHTZHN$ zy_}r%M;{2qL7kJ6|3km|yU*g+ufKu(RVRp`-Vpqa!zjMIA^e*rd-;~>FBwqxfo6qg`AKZ4){Xe*k+&kE%2`e{H3oL_zsiAcFag@tnZ>ZZPL>ncqn6 zZ@LMLi2Y4Bg3^D}5HJ>X5iASF#t210?q7Te9T-*nTL$dWKlJaI7+~w^3iI*3eqE1- z0MG^Q0k;5szyL4@OaTzZg559%x`1!M6mSF#0y^NK0XzWnT>)<(1n>cz!7_$GFMtAY z01~9k0TVzOFai<*UqA`C21o(Y057Q2Hn0KA0v3Qh@DPvzB!C>CAC$TWo@Kx|kOt;n zfE)uIkTF0WETsZ;0z+UKE`SpV1P*~*@WcS8z!N|jatERc2mmX<87RXIN(ce3foSmV z2s|hu;eZIh1h4>a05re{I08jrJ{Ry9xCEhv5JM6m7LZ6V_ZnFHEZDC~@Lv$%0D=KB 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 Vmt>cr&zI7iF#5{vw(shH|JlZ6us8q! diff --git a/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.html b/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.html deleted file mode 100644 index 31fbd907..00000000 --- a/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
Font array name: -
First char code:
Width:
Height:
- - - - -
-
-
-
-
-

- -
-
- -
-
-
-
- - - diff --git a/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.png b/lib/esp8266-oled-ssd1306-master/resources/glyphEditor.png deleted file mode 100644 index 800efc56f4bd50ba347eab57f3205ec8d0c0dd31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68751 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 M 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℞9m-8fv#DxWbiNUN1>5} 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 V=l%V&;(J^#ke61GDt>7i_#ZiQOU?iQ diff --git a/lib/esp8266-oled-ssd1306-master/resources/xbmPreview.png b/lib/esp8266-oled-ssd1306-master/resources/xbmPreview.png deleted file mode 100644 index 70ea3a53a843f220f18d2ef7cc0698516a990ba6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41692 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 diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp index bf4a5ee0..89a80108 100644 --- a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.cpp @@ -65,14 +65,14 @@ bool OLEDDisplay::allocateBuffer() { logBufferMaxLines = 0; logBuffer = NULL; - if (!this->connect()) { + if (!connect()) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); return false; } if(this->buffer==NULL) { - this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer += getBufferOffset(); + this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + BufferOffset); + this->buffer += BufferOffset; if(!this->buffer) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); @@ -82,12 +82,12 @@ bool OLEDDisplay::allocateBuffer() { #ifdef OLEDDISPLAY_DOUBLE_BUFFER if(this->buffer_back==NULL) { - this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer_back += getBufferOffset(); + this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + BufferOffset); + this->buffer_back += BufferOffset; if(!this->buffer_back) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); - free(this->buffer - getBufferOffset()); + free(this->buffer - BufferOffset); return false; } } @@ -98,6 +98,8 @@ bool OLEDDisplay::allocateBuffer() { bool OLEDDisplay::init() { + BufferOffset = getBufferOffset(); + if(!allocateBuffer()) { return false; } @@ -109,9 +111,9 @@ bool OLEDDisplay::init() { } void OLEDDisplay::end() { - if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } + if (this->buffer) { free(this->buffer - BufferOffset); this->buffer = NULL; } #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } + if (this->buffer_back) { free(this->buffer_back - BufferOffset); this->buffer_back = NULL; } #endif if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } } diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h index 8500b761..d43ee162 100644 --- a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplay.h @@ -359,6 +359,7 @@ class OLEDDisplay : public Stream { // the header size of the buffer used, e.g. for the SPI command header + int BufferOffset; virtual int getBufferOffset(void) = 0; // Send a command to the display (low level function) diff --git a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp index 6fcfafbf..778a2e77 100644 --- a/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp +++ b/lib/esp8266-oled-ssd1306-master/src/OLEDDisplayUi.cpp @@ -41,7 +41,7 @@ void LoadingDrawDefault(OLEDDisplay *display, LoadingStage* stage, uint8_t progr OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) { this->display = display; - + indicatorPosition = BOTTOM; indicatorDirection = LEFT_RIGHT; activeSymbol = ANIMATION_activeSymbol; @@ -300,7 +300,10 @@ void OLEDDisplayUi::resetState() { void OLEDDisplayUi::drawFrame(){ switch (this->state.frameState){ case IN_TRANSITION: { - float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition; + float progress = 0.f; + if (this->ticksPerTransition > 0u) { + progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition; + } int16_t x = 0, y = 0, x1 = 0, y1 = 0; switch(this->frameAnimationDirection){ case SLIDE_LEFT: diff --git a/lib/esp8266-oled-ssd1306-master/src/SSD1306Wire.h b/lib/esp8266-oled-ssd1306-master/src/SSD1306Wire.h index 7d27850f..9df32d14 100644 --- a/lib/esp8266-oled-ssd1306-master/src/SSD1306Wire.h +++ b/lib/esp8266-oled-ssd1306-master/src/SSD1306Wire.h @@ -82,7 +82,7 @@ class SSD1306Wire : public OLEDDisplay { } bool connect() { -#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH8266) +#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP8266) _wire->begin(); #else // On ESP32 arduino, -1 means 'don't change pins', someone else has called begin for us. @@ -198,7 +198,7 @@ class SSD1306Wire : public OLEDDisplay { void initI2cIfNeccesary() { if (_doI2cAutoInit) { -#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH8266) +#if !defined(ARDUINO_ARCH_ESP32) && !defined(ARDUINO_ARCH_ESP8266) _wire->begin(); #else _wire->begin(this->_sda, this->_scl); diff --git a/platformio.ini b/platformio.ini index 5a340709..3a0ff0bd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.15"' +build_flags = -D BUILD_VERSION='"GUI 1.1.16"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/SuplaCommonPROGMEM.h b/src/SuplaCommonPROGMEM.h index 18cf7412..b0c7e11c 100644 --- a/src/SuplaCommonPROGMEM.h +++ b/src/SuplaCommonPROGMEM.h @@ -150,6 +150,11 @@ const char SSD1306[] PROGMEM = "SSD1306 - 0,96''"; const char SH1106[] PROGMEM = "SH1106 - 1,3''"; const char SSD1306_WEMOS_SHIELD[] PROGMEM = "SSD1306 - 0,66'' WEMOS OLED shield"; const char* const OLED_P[] PROGMEM = {OFF, SSD1306, SH1106, SSD1306_WEMOS_SHIELD}; + +const char CONTROLL_NORMAL[] PROGMEM = "NORMALNE"; +const char CONTROLL_SLOW[] PROGMEM = "WOLNE"; +const char CONTROLL_MANUAL[] PROGMEM = "RĘCZNE"; +const char* const OLED_CONTROLL_P[] PROGMEM = {CONTROLL_NORMAL, CONTROLL_SLOW, CONTROLL_MANUAL}; #endif #endif // SuplaCommonPROGMEM_h diff --git a/src/SuplaConfigManager.cpp b/src/SuplaConfigManager.cpp index 24f60372..3c8ffe7f 100644 --- a/src/SuplaConfigManager.cpp +++ b/src/SuplaConfigManager.cpp @@ -177,6 +177,8 @@ SuplaConfigManager::SuplaConfigManager() { this->addKey(KEY_ADDR_DS18B20, MAX_DS18B20_ADDRESS_HEX * MAX_DS18B20); this->addKey(KEY_NAME_SENSOR, MAX_DS18B20_NAME * MAX_DS18B20); this->addKey(KEY_LEVEL_LED, "0", 1); + this->addKey(KEY_OLED_ANIMATION, "0", 1); + this->addKey(KEY_OLED_BACK_LIGHT_TIME, "5", 2); this->load(); // switch (this->load()) { diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index ef012d53..dcd0f50f 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -44,8 +44,8 @@ #define MAX_MONOSTABLE_TRIGGER 1 #define MAX_FUNCTION 1 -#define MAX_DS18B20 20 -#define MAX_GPIO 17 +#define MAX_DS18B20 20 +#define MAX_GPIO 17 enum _key { @@ -74,9 +74,11 @@ enum _key KEY_ADDR_DS18B20, KEY_NAME_SENSOR, KEY_GPIO, - KEY_LEVEL_LED = KEY_GPIO + MAX_GPIO + 1 - //KEY_DS = KEY_GPIO + MAX_GPIO + MAX_DS18B20, - //KEY_DS_NAME = KEY_DS + MAX_DS18B20 + KEY_LEVEL_LED = KEY_GPIO + MAX_GPIO + 1, + KEY_OLED_ANIMATION, + KEY_OLED_BACK_LIGHT_TIME + // KEY_DS = KEY_GPIO + MAX_GPIO + MAX_DS18B20, + // KEY_DS_NAME = KEY_DS + MAX_DS18B20 }; //#define GPIO "GPIO" diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index 689c43cf..c3f68126 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -340,12 +340,24 @@ SuplaOled::SuplaOled() { ui->disableAutoTransition(); } else { + switch (ConfigManager->get(KEY_OLED_ANIMATION)->getValueInt()) { + case OLED_CONTROLL_NORMAL: + ui->setTimePerFrame(5000); + break; + case OLED_CONTROLL_SLOW: + ui->setTimePerFrame(10000); + break; + case OLED_CONTROLL_MANUAL: + ui->disableAutoTransition(); + ui->setTimePerTransition(250); + break; + } + /*ui->setTargetFPS(30); ui->setIndicatorPosition(BOTTOM); ui->setIndicatorDirection(LEFT_RIGHT); - ui->setFrameAnimation(SLIDE_LEFT); + ui->setFrameAnimation(SLIDE_LEFT);*/ } - ui->setTargetFPS(60); ui->setFrames(frames, frameCount); ui->setOverlays(overlays, overlaysCount); ui->init(); @@ -360,7 +372,8 @@ void SuplaOled::iterateAlways() { return; } - if (millis() - timeLastChangeOled > 30000 && oledON) { + if (millis() - timeLastChangeOled > (ConfigManager->get(KEY_OLED_BACK_LIGHT_TIME)->getValueInt() * 1000) && oledON && + ConfigManager->get(KEY_OLED_BACK_LIGHT_TIME)->getValueInt() != 0) { display->setBrightness(50); oledON = false; // display.displayOff(); @@ -381,11 +394,22 @@ void SuplaOled::iterateAlways() { void SuplaOled::addButtonOled(int pin) { if (pin != OFF_GPIO) { Supla::Control::Button* button = new Supla::Control::Button(pin, true, true); - button->addAction(TURN_ON_OLED, this, Supla::ON_PRESS); + + if (ConfigManager->get(KEY_OLED_BACK_LIGHT_TIME)->getValueInt() != 0) { + button->addAction(TURN_ON_OLED, this, Supla::ON_PRESS); + } + + if (ConfigManager->get(KEY_OLED_ANIMATION)->getValueInt() == OLED_CONTROLL_MANUAL) { + button->addAction(NEXT_FRAME, this, Supla::ON_PRESS); + } } } void SuplaOled::handleAction(int event, int action) { + if (action == NEXT_FRAME) { + ui->nextFrame(); + } + if (action == TURN_ON_OLED) { display->setBrightness(255); timeLastChangeOled = millis(); diff --git a/src/SuplaOled.h b/src/SuplaOled.h index 61e8b787..da33d3e6 100644 --- a/src/SuplaOled.h +++ b/src/SuplaOled.h @@ -14,7 +14,8 @@ enum customActions { - TURN_ON_OLED + TURN_ON_OLED, + NEXT_FRAME }; enum _OLED @@ -24,6 +25,13 @@ enum _OLED OLED_SSD1306_0_66 }; +enum +{ + OLED_CONTROLL_NORMAL, + OLED_CONTROLL_SLOW, + OLED_CONTROLL_MANUAL +}; + String getTempString(double temperature); String getHumidityString(double humidity); String getPressureString(double pressure); diff --git a/src/SuplaWebPageSensor.cpp b/src/SuplaWebPageSensor.cpp index 299964d0..e7e5dbcb 100644 --- a/src/SuplaWebPageSensor.cpp +++ b/src/SuplaWebPageSensor.cpp @@ -452,6 +452,16 @@ void SuplaWebPageSensor::handlei2cSave() { ConfigManager->setElement(KEY_ACTIVE_SENSOR, SENSOR_OLED, WebServer->httpServer.arg(input).toInt()); } + if (!WebServer->saveGPIO(INPUT_BUTTON_GPIO, FUNCTION_CFG_BUTTON)) { + supla_webpage_i2c(6); + return; + } + + input = INPUT_OLED_ANIMATION; + ConfigManager->set(KEY_OLED_ANIMATION, WebServer->httpServer.arg(input).c_str()); + input = INPUT_OLED_BRIGHTNESS; + ConfigManager->set(KEY_OLED_BACK_LIGHT_TIME, WebServer->httpServer.arg(input).c_str()); + for (uint8_t i = 0; i < getCountSensorChannels(); i++) { input = INPUT_DS18B20_NAME; input += i; @@ -521,17 +531,24 @@ void SuplaWebPageSensor::supla_webpage_i2c(int save) { #endif #ifdef SUPLA_OLED - selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); addFormHeader(webContentBuffer); + + selected = ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt(); addListBox(webContentBuffer, INPUT_OLED, F("OLED"), OLED_P, 4, selected); + if (ConfigManager->get(KEY_ACTIVE_SENSOR)->getElement(SENSOR_OLED).toInt()) { String name, sensorName, input; + addListGPIOBox(webContentBuffer, INPUT_BUTTON_GPIO, F("PRZYCISK OLED"), FUNCTION_CFG_BUTTON); + selected = ConfigManager->get(KEY_OLED_ANIMATION)->getValueInt(); + addListBox(webContentBuffer, INPUT_OLED_ANIMATION, F("STEROWANIE"), OLED_CONTROLL_P, 3, selected); + addNumberBox(webContentBuffer, INPUT_OLED_BRIGHTNESS, F("PODŚWIETLENIE[s]"), KEY_OLED_BACK_LIGHT_TIME, 99); + for (uint8_t i = 0; i < getCountSensorChannels(); i++) { sensorName = String(ConfigManager->get(KEY_NAME_SENSOR)->getElement(i)); input = INPUT_DS18B20_NAME; input += i; - name = F("Ekran "); + name = F("EKRAN "); name += i + 1; addTextBox(webContentBuffer, input, name, sensorName, 0, MAX_DS18B20_NAME, false); } diff --git a/src/SuplaWebPageSensor.h b/src/SuplaWebPageSensor.h index c0b98eb1..c539868f 100644 --- a/src/SuplaWebPageSensor.h +++ b/src/SuplaWebPageSensor.h @@ -3,6 +3,7 @@ #include "SuplaDeviceGUI.h" #include "SuplaWebServer.h" +#include "SuplaWebPageControl.h" #define PATH_MULTI_DS "multids" #define PATH_SAVE_MULTI_DS "savemultids" @@ -27,6 +28,8 @@ #define INPUT_SHT3x "sht30" #define INPUT_SI7021 "si7021" #define INPUT_OLED "oled" +#define INPUT_OLED_ANIMATION "oleda" +#define INPUT_OLED_BRIGHTNESS "oledb" #define INPUT_MCP23017 "mcp" #define INPUT_SI7021_SONOFF "si7021sonoff" #define INPUT_TRIG_GPIO "trig" From 6533c27e591c95e083360c94e051d557bd5d2f5f Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 28 Jan 2021 21:30:58 +0100 Subject: [PATCH 118/233] poprawki oled --- platformio.ini | 2 +- src/SuplaOled.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 3a0ff0bd..4caa2fdb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.16"' +build_flags = -D BUILD_VERSION='"GUI 1.1.17"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/SuplaOled.cpp b/src/SuplaOled.cpp index c3f68126..140f31ee 100644 --- a/src/SuplaOled.cpp +++ b/src/SuplaOled.cpp @@ -352,15 +352,17 @@ SuplaOled::SuplaOled() { ui->setTimePerTransition(250); break; } - /*ui->setTargetFPS(30); + ui->setTargetFPS(30); ui->setIndicatorPosition(BOTTOM); ui->setIndicatorDirection(LEFT_RIGHT); - ui->setFrameAnimation(SLIDE_LEFT);*/ + ui->setFrameAnimation(SLIDE_LEFT); } ui->setFrames(frames, frameCount); ui->setOverlays(overlays, overlaysCount); ui->init(); + + display->setBrightness(255); display->flipScreenVertically(); } } From 68559869ff20df1a5a8194f4e8b6ed132aa8698c Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 28 Jan 2021 21:31:17 +0100 Subject: [PATCH 119/233] poprawki HLW8012 --- lib/SuplaDevice/src/supla/sensor/HJ101.cpp | 22 +++++++--------------- lib/SuplaDevice/src/supla/sensor/HJ101.h | 15 +++++++++++---- src/SuplaTemplateBoard.cpp | 1 - 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/SuplaDevice/src/supla/sensor/HJ101.cpp b/lib/SuplaDevice/src/supla/sensor/HJ101.cpp index 13b3f082..37217aa1 100644 --- a/lib/SuplaDevice/src/supla/sensor/HJ101.cpp +++ b/lib/SuplaDevice/src/supla/sensor/HJ101.cpp @@ -50,14 +50,14 @@ void HJ101::readValuesFromDevice() { } void HJ101::onSaveState() { - double current_multiplier = getCurrentMultiplier(); - double voltage_multiplier = getVoltageMultiplier(); - double power_multiplier = getPowerMultiplier(); + //double current_multiplier = getCurrentMultiplier(); + //double voltage_multiplier = getVoltageMultiplier(); + //double power_multiplier = getPowerMultiplier(); Supla::Storage::WriteState((unsigned char *)&energy, sizeof(energy)); - Supla::Storage::WriteState((unsigned char *)¤t_multiplier, sizeof(current_multiplier)); - Supla::Storage::WriteState((unsigned char *)&voltage_multiplier, sizeof(voltage_multiplier)); - Supla::Storage::WriteState((unsigned char *)&power_multiplier, sizeof(power_multiplier)); + //Supla::Storage::WriteState((unsigned char *)¤t_multiplier, sizeof(current_multiplier)); + //Supla::Storage::WriteState((unsigned char *)&voltage_multiplier, sizeof(voltage_multiplier)); + //Supla::Storage::WriteState((unsigned char *)&power_multiplier, sizeof(power_multiplier)); } void HJ101::onLoadState() { @@ -134,18 +134,10 @@ void HJ101::calibrate(double calibPower, double calibVoltage) { Serial.println(voltage_multi); Serial.print(F("[HLW] New power multiplier : ")); Serial.println(power_multi); - saveState(); + Supla::Storage::ScheduleSave(1000); yield(); } -void HJ101::saveState() { - Supla::Storage::PrepareState(); - for (auto element = Supla::Element::begin(); element != nullptr; element = element->next()) { - element->onSaveState(); - } - Supla::Storage::FinalizeSaveState(); -} - HLW8012 *HJ101::hj101 = nullptr; }; // namespace Sensor }; // namespace Supla \ No newline at end of file diff --git a/lib/SuplaDevice/src/supla/sensor/HJ101.h b/lib/SuplaDevice/src/supla/sensor/HJ101.h index c4814604..5ccd921d 100644 --- a/lib/SuplaDevice/src/supla/sensor/HJ101.h +++ b/lib/SuplaDevice/src/supla/sensor/HJ101.h @@ -6,8 +6,9 @@ // https://github.com/xoseperez/hlw8012 #include -#include #include +#include + #include "one_phase_electricity_meter.h" namespace Supla { @@ -15,7 +16,11 @@ namespace Sensor { class HJ101 : public OnePhaseElectricityMeter, public Element { public: - HJ101(int8_t pinCF, int8_t pinCF1, int8_t pinSEL, bool currentWhen = LOW, bool use_interrupts = true); + HJ101(int8_t pinCF, + int8_t pinCF1, + int8_t pinSEL, + bool currentWhen = LOW, + bool use_interrupts = true); void onInit(); void readValuesFromDevice(); @@ -26,7 +31,6 @@ class HJ101 : public OnePhaseElectricityMeter, public Element { static void ICACHE_RAM_ATTR hjl01_cf1_interrupt(); static void ICACHE_RAM_ATTR hjl01_cf_interrupt(); void calibrate(double calibPower, double calibVoltage); - void saveState(); double getCurrentMultiplier() { return hj101->getCurrentMultiplier(); @@ -40,12 +44,15 @@ class HJ101 : public OnePhaseElectricityMeter, public Element { void setCurrentMultiplier(double current_multiplier) { hj101->setCurrentMultiplier(current_multiplier); + Supla::Storage::ScheduleSave(1000); }; void setVoltageMultiplier(double voltage_multiplier) { hj101->setVoltageMultiplier(voltage_multiplier); + Supla::Storage::ScheduleSave(1000); }; void setPowerMultiplier(double power_multiplier) { hj101->setPowerMultiplier(power_multiplier); + Supla::Storage::ScheduleSave(1000); }; protected: @@ -57,7 +64,7 @@ class HJ101 : public OnePhaseElectricityMeter, public Element { bool use_interrupts; unsigned _supla_int64_t energy; - unsigned _supla_int64_t _energy; // ---------- energy value read from memory at startup ---- + unsigned _supla_int64_t _energy; // energy value read from memory at startup }; }; // namespace Sensor diff --git a/src/SuplaTemplateBoard.cpp b/src/SuplaTemplateBoard.cpp index 88aa8ace..1fd78e46 100644 --- a/src/SuplaTemplateBoard.cpp +++ b/src/SuplaTemplateBoard.cpp @@ -205,7 +205,6 @@ void chooseTemplateBoard(uint8_t board) { Supla::GUI::counterHLW8012->setCurrentMultiplier(18388); Supla::GUI::counterHLW8012->setVoltageMultiplier(247704); Supla::GUI::counterHLW8012->setPowerMultiplier(2586583); - Supla::GUI::counterHLW8012->saveState(); #endif break; } From 79aaeac9aa830d40f6d9963b0dd3f836aaaa119f Mon Sep 17 00:00:00 2001 From: krycha88 Date: Fri, 29 Jan 2021 13:32:27 +0100 Subject: [PATCH 120/233] MCP23017 -> LEVEL_RELAY, LEVEL_BUTTON --- platformio.ini | 2 +- src/Markup.cpp | 61 +++++++++++++++++++++++++++---------- src/Markup.h | 19 ++++++++++-- src/SuplaConfigESP.cpp | 30 ++++++++++++++---- src/SuplaConfigManager.h | 2 ++ src/SuplaWebPageControl.cpp | 11 +++---- src/SuplaWebPageRelay.cpp | 11 +++---- src/SuplaWebServer.cpp | 2 ++ 8 files changed, 99 insertions(+), 39 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4caa2fdb..8be24209 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.17"' +build_flags = -D BUILD_VERSION='"GUI 1.1.18"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC diff --git a/src/Markup.cpp b/src/Markup.cpp index c00b434c..4e5b0069 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -141,7 +141,7 @@ void addLinkBox(String& html, const String& name, const String& url) { html += url; html += F("'>"); html += name; - //html += PGMT(ICON_EDIT); + // html += PGMT(ICON_EDIT); html += F(""); html += F(""); html += F(""); @@ -181,26 +181,16 @@ void addListMCP23017GPIOBox(String& html, const String& input_id, const String& void addListMCP23017GPIOLinkBox(String& html, const String& input_id, const String& name, uint8_t function, const String& url, uint8_t nr) { if (nr == 1) { uint8_t address = ConfigESP->getAdressMCP23017(nr, function); - addListBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 3, address); + addListLinkBox(html, INPUT_ADRESS_MCP23017, F("MCP23017 Adres"), MCP23017_P, 3, address, url); } - html += F(""); - html += F(""); addListMCP23017GPIO(html, input_id, function, nr); html += F(""); @@ -267,6 +257,46 @@ void addListBox(String& html, const String& input_id, const String& name, const html += F(""); } +void addListLinkBox(String& html, + const String& input_id, + const String& name, + const char* const* array_P, + uint8_t size, + uint8_t selected, + const String& url, + uint8_t nr) { + html += F(""); +} + void addButton(String& html, const String& name, const String& url) { html += F("get(key)->getElement(MCP23017_FUNCTION_1).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_1).toInt() == nr) { - return ConfigManager->get(key)->getElement(LEVEL).toInt(); - ; + switch (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_1).toInt()) { + case FUNCTION_RELAY: + return ConfigManager->get(key)->getElement(LEVEL_RELAY).toInt(); + break; + case FUNCTION_BUTTON: + return ConfigManager->get(key)->getElement(LEVEL_BUTTON).toInt(); + break; + } } } break; case 1: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_2).toInt() == nr) { - return ConfigManager->get(key)->getElement(LEVEL).toInt(); - ; + switch (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_2).toInt()) { + case FUNCTION_RELAY: + return ConfigManager->get(key)->getElement(LEVEL_RELAY).toInt(); + break; + case FUNCTION_BUTTON: + return ConfigManager->get(key)->getElement(LEVEL_BUTTON).toInt(); + break; + } } } break; case 2: if (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt() == function) { if (ConfigManager->get(key)->getElement(MCP23017_NR_3).toInt() == nr) { - return ConfigManager->get(key)->getElement(LEVEL).toInt(); - ; + switch (ConfigManager->get(key)->getElement(MCP23017_FUNCTION_3).toInt()) { + case FUNCTION_RELAY: + return ConfigManager->get(key)->getElement(LEVEL_RELAY).toInt(); + break; + case FUNCTION_BUTTON: + return ConfigManager->get(key)->getElement(LEVEL_BUTTON).toInt(); + break; + } } } break; diff --git a/src/SuplaConfigManager.h b/src/SuplaConfigManager.h index dcd0f50f..6f7be111 100644 --- a/src/SuplaConfigManager.h +++ b/src/SuplaConfigManager.h @@ -100,6 +100,8 @@ enum _settings MCP23017_FUNCTION_3, MCP23017_NR_4, MCP23017_FUNCTION_4, + LEVEL_RELAY, + LEVEL_BUTTON, SETTINGSCOUNT }; diff --git a/src/SuplaWebPageControl.cpp b/src/SuplaWebPageControl.cpp index 996b70bb..b188a0bf 100644 --- a/src/SuplaWebPageControl.cpp +++ b/src/SuplaWebPageControl.cpp @@ -355,13 +355,10 @@ void SuplaWebPageControl::handleButtonSaveSetMCP23017() { input = INPUT_BUTTON_ACTION; action = WebServer->httpServer.arg(input).toInt(); - for (uint8_t i = 1; i <= OFF_GPIO; i++) { - gpio = ConfigESP->getGpioMCP23017(i, FUNCTION_BUTTON); - if (gpio != OFF_GPIO) { - key = KEY_GPIO + gpio; - ConfigManager->setElement(key, LEVEL, level); - ConfigManager->setElement(key, ACTION, action); - } + for (gpio = 0; gpio <= OFF_GPIO; gpio++) { + key = KEY_GPIO + gpio; + ConfigManager->setElement(key, LEVEL_BUTTON, level); + ConfigManager->setElement(key, ACTION, action); } switch (ConfigManager->save()) { case E_CONFIG_OK: diff --git a/src/SuplaWebPageRelay.cpp b/src/SuplaWebPageRelay.cpp index 09f4bb08..d6fc0862 100644 --- a/src/SuplaWebPageRelay.cpp +++ b/src/SuplaWebPageRelay.cpp @@ -261,13 +261,10 @@ void SuplaWebPageRelay::handleRelaySaveSetMCP23017() { input = INPUT_RELAY_LEVEL; level = WebServer->httpServer.arg(input).toInt(); - for (uint8_t i = 1; i <= OFF_GPIO; i++) { - gpio = ConfigESP->getGpioMCP23017(i, FUNCTION_RELAY); - if (gpio != OFF_GPIO) { - key = KEY_GPIO + gpio; - ConfigManager->setElement(key, MEMORY, memory); - ConfigManager->setElement(key, LEVEL, level); - } + for (gpio = 0; gpio <= OFF_GPIO; gpio++) { + key = KEY_GPIO + gpio; + ConfigManager->setElement(key, MEMORY, memory); + ConfigManager->setElement(key, LEVEL_RELAY, level); } switch (ConfigManager->save()) { diff --git a/src/SuplaWebServer.cpp b/src/SuplaWebServer.cpp index c20e6b4a..62649dff 100644 --- a/src/SuplaWebServer.cpp +++ b/src/SuplaWebServer.cpp @@ -344,6 +344,8 @@ void SuplaWebServer::sendContent() { void SuplaWebServer::handleNotFound() { httpServer.sendHeader("Location", "/", true); httpServer.send(302, "text/plane", ""); + + supla_webpage_reboot(); } bool SuplaWebServer::saveGPIO(const String& _input, uint8_t function, uint8_t nr, const String& input_max) { From 879749900467fed110c56852ef2925d4c30c15c9 Mon Sep 17 00:00:00 2001 From: krycha88 Date: Thu, 4 Feb 2021 12:59:26 +0100 Subject: [PATCH 121/233] wsparcie dla RGBW, RGB, DIMMER --- platformio.ini | 9 +- src/GUI-Generic.ino | 7 + src/GUI-Generic_Config.h | 3 + src/Markup.cpp | 14 +- src/Markup.h | 4 +- src/SuplaConfigESP.cpp | 16 +- src/SuplaConfigManager.cpp | 10 +- src/SuplaConfigManager.h | 13 +- src/SuplaDeviceGUI.cpp | 65 ++++++- src/SuplaDeviceGUI.h | 12 ++ src/SuplaOled.cpp | 10 +- src/SuplaTemplateBoard.cpp | 50 ++++-- src/SuplaTemplateBoard.h | 8 +- src/SuplaWebPageOther.cpp | 359 +++++++++++++++++++++++++++++++++++++ src/SuplaWebPageOther.h | 53 ++++++ src/SuplaWebPageSensor.cpp | 351 ------------------------------------ src/SuplaWebPageSensor.h | 35 ---- src/SuplaWebServer.cpp | 24 +-- src/language/pl.h | 1 - 19 files changed, 605 insertions(+), 439 deletions(-) create mode 100644 src/SuplaWebPageOther.cpp create mode 100644 src/SuplaWebPageOther.h diff --git a/platformio.ini b/platformio.ini index 8be24209..edc13b3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ default_envs = [common] -build_flags = -D BUILD_VERSION='"GUI 1.1.18"' +build_flags = -D BUILD_VERSION='"GUI-Generic 1.2.0"' -w -DATOMIC_FS_UPDATE -DBEARSSL_SSL_BASIC @@ -44,7 +44,8 @@ build_flags = -D BUILD_VERSION='"GUI 1.1.18"' -D SUPLA_IMPULSE_COUNTER -D SUPLA_OLED -D SUPLA_HLW8012 - -D SUPLA_MCP23017 + -D SUPLA_MCP23017 + -D SUPLA_RGBW [env] framework = arduino @@ -53,8 +54,10 @@ upload_speed = 256000 monitor_speed = 74880 upload_resetmethod = nodemcu board_build.flash_mode = dout -; set frequency to 160MHz +;set frequency to 160MHz board_build.f_cpu = 160000000L +; set frequency to 80MHz +board_build.f_flash = 80000000L lib_deps = milesburton/DallasTemperature@^3.9.1 adafruit/DHT sensor library@^1.4.0 diff --git a/src/GUI-Generic.ino b/src/GUI-Generic.ino index d43920d0..09c3b128 100644 --- a/src/GUI-Generic.ino +++ b/src/GUI-Generic.ino @@ -195,6 +195,13 @@ void setup() { #endif } #endif + +#ifdef SUPLA_RGBW + for (nr = 1; nr <= ConfigManager->get(KEY_MAX_RGBW)->getValueInt(); nr++) { + Supla::GUI::addRGBWLeds(nr); + } +#endif + Supla::GUI::begin(); } diff --git a/src/GUI-Generic_Config.h b/src/GUI-Generic_Config.h index c3d7cb17..23c604a7 100644 --- a/src/GUI-Generic_Config.h +++ b/src/GUI-Generic_Config.h @@ -1,7 +1,9 @@ #ifndef GUI_Generic_Config_h #define GUI_Generic_Config_h +#ifndef DEBUG_MODE #define supla_lib_config_h_ // silences unnecessary debug messages "should be disabled by default" +#endif //#define USE_CUSTOM @@ -49,6 +51,7 @@ #define SUPLA_HC_SR04 #define SUPLA_IMPULSE_COUNTER #define SUPLA_HLW8012 +#define SUPLA_RGBW #endif #endif // GUI-Generic_Config_h diff --git a/src/Markup.cpp b/src/Markup.cpp index 4e5b0069..98f7127f 100644 --- a/src/Markup.cpp +++ b/src/Markup.cpp @@ -147,8 +147,14 @@ void addLinkBox(String& html, const String& name, const String& url) { html += F(""); } -void addListGPIOBox(String& html, const String& input_id, const String& name, uint8_t function, uint8_t nr) { - html += F("