From e40475b5631c66e0b84d78526d0ec4abbb8424b6 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 29 Sep 2019 18:00:01 +0200 Subject: [PATCH] Add initial support for shutters Add initial support for shutters by Stefan Bode (#288) --- sonoff/_changelog.ino | 1 + sonoff/language/bg-BG.h | 5 + sonoff/language/cs-CZ.h | 5 + sonoff/language/de-DE.h | 5 + sonoff/language/el-GR.h | 5 + sonoff/language/en-GB.h | 5 + sonoff/language/es-ES.h | 5 + sonoff/language/fr-FR.h | 5 + sonoff/language/he-HE.h | 5 + sonoff/language/hu-HU.h | 5 + sonoff/language/it-IT.h | 5 + sonoff/language/ko-KO.h | 5 + sonoff/language/nl-NL.h | 5 + sonoff/language/pl-PL.h | 5 + sonoff/language/pt-BR.h | 5 + sonoff/language/pt-PT.h | 5 + sonoff/language/ru-RU.h | 5 + sonoff/language/sk-SK.h | 5 + sonoff/language/sv-SE.h | 5 + sonoff/language/tr-TR.h | 5 + sonoff/language/uk-UK.h | 5 + sonoff/language/zh-CN.h | 5 + sonoff/language/zh-TW.h | 5 + sonoff/my_user_config.h | 1 + sonoff/settings.h | 23 +- sonoff/sonoff.h | 13 +- sonoff/sonoff.ino | 3 + sonoff/xdrv_01_webserver.ino | 53 +++- sonoff/xdrv_07_domoticz.ino | 11 +- sonoff/xdrv_10_rules.ino | 10 +- sonoff/xdrv_10_scripter.ino | 13 + sonoff/xdrv_20_hue.ino | 49 ++- sonoff/xdrv_27_shutter.ino | 588 +++++++++++++++++++++++++++++++++++ 33 files changed, 844 insertions(+), 31 deletions(-) create mode 100644 sonoff/xdrv_27_shutter.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 2da2d46ff..5b26d35d3 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -4,6 +4,7 @@ * Remove support for define USE_DS18x20_LEGACY and legacy DS18x20 driver (#6486) * Add initial support for MQTT logging using command MqttLog (#6498) * Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command + * Add initial support for shutters by Stefan Bode (#288) * * 6.6.0.13 20190922 * Add command EnergyReset4 x,x to initialize total usage for two tarrifs diff --git a/sonoff/language/bg-BG.h b/sonoff/language/bg-BG.h index 69e35925d..1cc34be2c 100644 --- a/sonoff/language/bg-BG.h +++ b/sonoff/language/bg-BG.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Използвана енергия вчера" #define D_ENERGY_TOTAL "Използвана енергия общо" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Датчикът DS18x20 е зает" #define D_SENSOR_CRC_ERROR "Датчик DS18x20 - грешка CRC" diff --git a/sonoff/language/cs-CZ.h b/sonoff/language/cs-CZ.h index 3cc0d3197..c8a5713c8 100644 --- a/sonoff/language/cs-CZ.h +++ b/sonoff/language/cs-CZ.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Spotřeba Včera" #define D_ENERGY_TOTAL "Celková spotřeba" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor DS18x20 obsazen" #define D_SENSOR_CRC_ERROR "Sensor DS18x20 chyba CRC" diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h index a516eeb42..295402d6b 100644 --- a/sonoff/language/de-DE.h +++ b/sonoff/language/de-DE.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energie gestern" #define D_ENERGY_TOTAL "Energie insgesamt" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor beschäftigt" #define D_SENSOR_CRC_ERROR "Sensor CRC-Fehler" diff --git a/sonoff/language/el-GR.h b/sonoff/language/el-GR.h index 100791bb1..222c06ef6 100644 --- a/sonoff/language/el-GR.h +++ b/sonoff/language/el-GR.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Ενέργεια χθες" #define D_ENERGY_TOTAL "Ενέργεια συνολικά" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Ο αισθητήρας είναι απασχολημένος" #define D_SENSOR_CRC_ERROR "Σφάλμα CRC αισθητήρα" diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h index 3e415c6e2..546d99af1 100644 --- a/sonoff/language/en-GB.h +++ b/sonoff/language/en-GB.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energy Yesterday" #define D_ENERGY_TOTAL "Energy Total" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor busy" #define D_SENSOR_CRC_ERROR "Sensor CRC error" diff --git a/sonoff/language/es-ES.h b/sonoff/language/es-ES.h index bbd1d776a..b62e54661 100644 --- a/sonoff/language/es-ES.h +++ b/sonoff/language/es-ES.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energía Ayer" #define D_ENERGY_TOTAL "Energía Total" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor ocupado" #define D_SENSOR_CRC_ERROR "Error CRC del Sensor" diff --git a/sonoff/language/fr-FR.h b/sonoff/language/fr-FR.h index d08f654ff..6091da84f 100644 --- a/sonoff/language/fr-FR.h +++ b/sonoff/language/fr-FR.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Énergie hier" #define D_ENERGY_TOTAL "Énergie totale" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Capteur occupé" #define D_SENSOR_CRC_ERROR "Erreur CRC capteur" diff --git a/sonoff/language/he-HE.h b/sonoff/language/he-HE.h index e2a54fe57..ee16e5073 100644 --- a/sonoff/language/he-HE.h +++ b/sonoff/language/he-HE.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "צריכה בעבר" #define D_ENERGY_TOTAL "צריכה כללית" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "שרת עסוק" #define D_SENSOR_CRC_ERROR "בחיישן CRC שגיאת" diff --git a/sonoff/language/hu-HU.h b/sonoff/language/hu-HU.h index 564d385c1..90a52b03a 100644 --- a/sonoff/language/hu-HU.h +++ b/sonoff/language/hu-HU.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Tegnapi energia" #define D_ENERGY_TOTAL "Összes energia" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Szenzor foglalt" #define D_SENSOR_CRC_ERROR "Szenzor CRC hiba" diff --git a/sonoff/language/it-IT.h b/sonoff/language/it-IT.h index cfb564eca..2a990dc8e 100644 --- a/sonoff/language/it-IT.h +++ b/sonoff/language/it-IT.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energia Ieri" #define D_ENERGY_TOTAL "Energia Totale" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensore occupato" #define D_SENSOR_CRC_ERROR "Sensore errore CRC" diff --git a/sonoff/language/ko-KO.h b/sonoff/language/ko-KO.h index d713fa79d..7ffc5271d 100644 --- a/sonoff/language/ko-KO.h +++ b/sonoff/language/ko-KO.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "어제 전력 사용량" #define D_ENERGY_TOTAL "총 전력 사용량" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "센서가 사용 중" #define D_SENSOR_CRC_ERROR "센서 CRC 에러" diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h index 03f77b4eb..822bf1542 100644 --- a/sonoff/language/nl-NL.h +++ b/sonoff/language/nl-NL.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Verbruik gisteren" #define D_ENERGY_TOTAL "Verbruik totaal" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor bezet" #define D_SENSOR_CRC_ERROR "Sensor CRC fout" diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h index 6b4d35678..8729be3c7 100644 --- a/sonoff/language/pl-PL.h +++ b/sonoff/language/pl-PL.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energia Wczoraj" #define D_ENERGY_TOTAL "Energia suma" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Czujnik DS18x20 zajęty" #define D_SENSOR_CRC_ERROR "Czujnik DS18x20 błąd CRC" diff --git a/sonoff/language/pt-BR.h b/sonoff/language/pt-BR.h index 388db5620..f1ec6326f 100644 --- a/sonoff/language/pt-BR.h +++ b/sonoff/language/pt-BR.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Consumo energético de ontem" #define D_ENERGY_TOTAL "Consumo total de energia" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor ocupado" #define D_SENSOR_CRC_ERROR "Erro sensor CRC" diff --git a/sonoff/language/pt-PT.h b/sonoff/language/pt-PT.h index 44e5b3469..0fac99308 100644 --- a/sonoff/language/pt-PT.h +++ b/sonoff/language/pt-PT.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Consumo energético de ontem" #define D_ENERGY_TOTAL "Consumo total de energial" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor ocupado" #define D_SENSOR_CRC_ERROR "Erro Sensor CRC" diff --git a/sonoff/language/ru-RU.h b/sonoff/language/ru-RU.h index 614fea8fe..2b0b68cb7 100644 --- a/sonoff/language/ru-RU.h +++ b/sonoff/language/ru-RU.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Энергия Вчера" #define D_ENERGY_TOTAL "Энергия Всего" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Датчик DS18x20 занят" #define D_SENSOR_CRC_ERROR "Датчик DS18x20 - ошибка CRC" diff --git a/sonoff/language/sk-SK.h b/sonoff/language/sk-SK.h index 18e3fcb41..84385ee28 100644 --- a/sonoff/language/sk-SK.h +++ b/sonoff/language/sk-SK.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Spotreba včera" #define D_ENERGY_TOTAL "Celková spotreba" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor DS18x20 obsadený" #define D_SENSOR_CRC_ERROR "Sensor DS18x20 chyba CRC" diff --git a/sonoff/language/sv-SE.h b/sonoff/language/sv-SE.h index 477e17e67..d55b6ddbc 100644 --- a/sonoff/language/sv-SE.h +++ b/sonoff/language/sv-SE.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energi igår" #define D_ENERGY_TOTAL "Energi totalt" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensor upptagen" #define D_SENSOR_CRC_ERROR "Sensor CRC-fel" diff --git a/sonoff/language/tr-TR.h b/sonoff/language/tr-TR.h index 6826c9e92..f31efe078 100755 --- a/sonoff/language/tr-TR.h +++ b/sonoff/language/tr-TR.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Energy Yesterday" #define D_ENERGY_TOTAL "Energy Total" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Sensör başgül" #define D_SENSOR_CRC_ERROR "Sensor CRC hatası" diff --git a/sonoff/language/uk-UK.h b/sonoff/language/uk-UK.h index eb9db9913..dd21904d8 100644 --- a/sonoff/language/uk-UK.h +++ b/sonoff/language/uk-UK.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "Енергія Вчора" #define D_ENERGY_TOTAL "Енергія Всього" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "Датчик DS18x20 зайнятий" #define D_SENSOR_CRC_ERROR "Датчик DS18x20 - помилка CRC" diff --git a/sonoff/language/zh-CN.h b/sonoff/language/zh-CN.h index 0f16bf057..0d776f40c 100644 --- a/sonoff/language/zh-CN.h +++ b/sonoff/language/zh-CN.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "昨日用电量" #define D_ENERGY_TOTAL "总用电量" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "传感器正忙" #define D_SENSOR_CRC_ERROR "传感器 CRC 校验错误" diff --git a/sonoff/language/zh-TW.h b/sonoff/language/zh-TW.h index 8d65c1c67..38d0b26be 100644 --- a/sonoff/language/zh-TW.h +++ b/sonoff/language/zh-TW.h @@ -442,6 +442,11 @@ #define D_ENERGY_YESTERDAY "昨日用電量" #define D_ENERGY_TOTAL "總用電量" +// xdrv_27_shutter.ino +#define D_OPEN "Open" +#define D_CLOSE "Close" +#define D_DOMOTICZ_SHUTTER "Shutter" + // xsns_05_ds18b20.ino #define D_SENSOR_BUSY "傳感器正忙" #define D_SENSOR_CRC_ERROR "傳感器 CRC 校驗錯誤" diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index 338ce5d17..a2a2eda67 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -313,6 +313,7 @@ #define USE_ARMTRONIX_DIMMERS // Add support for Armtronix Dimmers (+1k4 code) #define USE_PS_16_DZ // Add support for PS-16-DZ Dimmer and Sonoff L1 (+2k code) //#define ROTARY_V1 // Add support for MI Desk Lamp +//#define USE_SHUTTER // Add Shutter support for up to 4 shutter with different motortypes (+6k code) // -- Counter input ------------------------------- #define USE_COUNTER // Enable inputs as counter (+0k8 code) diff --git a/sonoff/settings.h b/sonoff/settings.h index a4470799a..c11926855 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -93,7 +93,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t spare27 : 1; uint32_t spare28 : 1; uint32_t spare29 : 1; - uint32_t spare30 : 1; + uint32_t shutter_mode : 1; // bit 30 (v6.6.0.15) - SetOption80 - Enable shutter support uint32_t spare31 : 1; }; } SysBitfield3; @@ -227,7 +227,8 @@ struct SYSCFG { uint8_t weblog_level; // 1AC uint8_t mqtt_fingerprint[2][20]; // 1AD uint8_t adc_param_type; // 1D5 - uint8_t register8[17]; // 1D6 - 17 x 8-bit registers indexed by enum SettingsRegister8 + uint8_t register8[16]; // 1D6 - 16 x 8-bit registers indexed by enum SettingsRegister8 + uint8_t shutter_accuracy; // 1E6 uint8_t mqttlog_level; // 1E7 uint8_t sps30_inuse_hours; // 1E8 char mqtt_host[33]; // 1E9 - Keep together with below as being copied as one chunck with reset 6 @@ -376,7 +377,15 @@ struct SYSCFG { uint16_t ina226_i_fs[4]; // E28 uint16_t tariff[4][2]; // E30 - uint8_t free_e40[440]; // E40 + uint16_t shutter_opentime[MAX_SHUTTERS]; // E40 + uint16_t shutter_closetime[MAX_SHUTTERS]; // E48 + int16_t shuttercoeff[5][MAX_SHUTTERS]; // E50 + uint8_t shutter_invert[MAX_SHUTTERS]; // E78 + uint8_t shutter_set50percent[MAX_SHUTTERS]; // E7C + uint8_t shutter_position[MAX_SHUTTERS]; // E80 + uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84 + + uint8_t free_e88[368]; // E88 uint32_t cfg_timestamp; // FF8 uint32_t cfg_crc32; // FFC @@ -427,7 +436,11 @@ struct XDRVMAILBOX { char *command; } XdrvMailbox; +#ifdef USE_SHUTTER +const uint8_t MAX_RULES_FLAG = 10; // Number of bits used in RulesBitfield (tricky I know...) +#else const uint8_t MAX_RULES_FLAG = 8; // Number of bits used in RulesBitfield (tricky I know...) +#endif // USE_SHUTTER typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... uint16_t data; // Allow bit manipulation struct { @@ -439,8 +452,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint16_t wifi_connected : 1; uint16_t wifi_disconnected : 1; uint16_t http_init : 1; - uint16_t spare08 : 1; - uint16_t spare09 : 1; + uint16_t shutter_moved : 1; + uint16_t shutter_moving : 1; uint16_t spare10 : 1; uint16_t spare11 : 1; uint16_t spare12 : 1; diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 1b29754a5..5ee19873c 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -67,6 +67,7 @@ const uint8_t MAX_XNRG_DRIVERS = 32; // Max number of allowed energy driv const uint8_t MAX_XDSP_DRIVERS = 32; // Max number of allowed display drivers const uint8_t MAX_XDRV_DRIVERS = 96; // Max number of allowed driver drivers const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor drivers +const uint8_t MAX_SHUTTERS = 4; // Max number of shutters const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules @@ -255,10 +256,10 @@ enum SettingsParamIndex { P_HOLD_TIME, P_MAX_POWER_RETRY, P_ex_TUYA_DIMMER_ID, P enum SettingsRegister8 { R8_SPARE00, R8_SPARE01, R8_SPARE02, R8_SPARE03, R8_SPARE04, R8_SPARE05, R8_SPARE06, R8_SPARE07, R8_SPARE08, R8_SPARE09, R8_SPARE10, R8_SPARE11, - R8_SPARE12, R8_SPARE13, R8_SPARE14, R8_SPARE15, - R8_SPARE16 }; // Max size is 17 (Settings.register8[]) + R8_SPARE12, R8_SPARE13, R8_SPARE14, R8_SPARE15 }; // Max size is 16 (Settings.register8[]) -enum DomoticzSensors {DZ_TEMP, DZ_TEMP_HUM, DZ_TEMP_HUM_BARO, DZ_POWER_ENERGY, DZ_ILLUMINANCE, DZ_COUNT, DZ_VOLTAGE, DZ_CURRENT, DZ_AIRQUALITY, DZ_P1_SMART_METER, DZ_MAX_SENSORS}; +enum DomoticzSensors {DZ_TEMP, DZ_TEMP_HUM, DZ_TEMP_HUM_BARO, DZ_POWER_ENERGY, DZ_ILLUMINANCE, DZ_COUNT, DZ_VOLTAGE, DZ_CURRENT, + DZ_AIRQUALITY, DZ_P1_SMART_METER, DZ_SHUTTER, DZ_MAX_SENSORS}; enum Ws2812ClockIndex { WS_SECOND, WS_MINUTE, WS_HOUR, WS_MARKER }; enum Ws2812Color { WS_RED, WS_GREEN, WS_BLUE }; @@ -282,8 +283,10 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND }; enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER, - SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_MAX }; -const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote"; + SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER, + SRC_MAX }; +const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|" + "Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter"; const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index ff771c95e..d0a786e0c 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -96,6 +96,7 @@ power_t blink_mask = 0; // Blink relay active mask power_t blink_powersave; // Blink start power save state power_t latching_power = 0; // Power state at latching start power_t rel_inverted = 0; // Relay inverted flag (1 = (0 = On, 1 = Off)) +power_t shutter_mask = 0; // Bit Array to mark all relays that belong to at least one shutter int baudrate = APP_BAUDRATE; // Serial interface baud rate int serial_in_byte_counter = 0; // Index in receive buffer int ota_state_flag = 0; // OTA state flag @@ -138,6 +139,8 @@ uint8_t seriallog_level; // Current copy of Settings.seriallo uint8_t syslog_level; // Current copy of Settings.syslog_level uint8_t my_module_type; // Current copy of Settings.module or user template type uint8_t my_adc0; // Active copy of Module ADC0 +uint8_t last_source = 0; +uint8_t shutters_present = 0; // Number of actual define shutters //uint8_t mdns_delayed_start = 0; // mDNS delayed start bool serial_local = false; // Handle serial locally; bool fallback_topic_flag = false; // Use Topic or FallbackTopic diff --git a/sonoff/xdrv_01_webserver.ino b/sonoff/xdrv_01_webserver.ino index c85c6a252..c4239e90f 100644 --- a/sonoff/xdrv_01_webserver.ino +++ b/sonoff/xdrv_01_webserver.ino @@ -132,7 +132,7 @@ const char HTTP_SCRIPT_ROOT[] PROGMEM = "rfsh=0;" "}" "}" -#else // USE_SCRIPT_WEB_DISPLAY +#else // USE_SCRIPT_WEB_DISPLAY "function la(p){" "var a='';" "if(la.arguments.length==1){" @@ -151,8 +151,7 @@ const char HTTP_SCRIPT_ROOT[] PROGMEM = "x.send();" "lt=setTimeout(la,%d);" // Settings.web_refresh "}" -#endif // USE_SCRIPT_WEB_DISPLAY - +#endif // USE_SCRIPT_WEB_DISPLAY #ifdef USE_JAVASCRIPT_ES6 "lb=p=>la('&d='+p);" // Dark - Bright &d related to lb(value) and WebGetArg("d", tmp, sizeof(tmp)); @@ -164,7 +163,29 @@ const char HTTP_SCRIPT_ROOT[] PROGMEM = "function lc(p){" "la('&t='+p);" // &t related to WebGetArg("t", tmp, sizeof(tmp)); "}" -#endif +#endif // USE_JAVASCRIPT_ES6 + +#ifdef USE_SHUTTER +#ifdef USE_JAVASCRIPT_ES6 + "ld1=p=>la('&u1='+p);" + "ld2=p=>la('&u2='+p);" + "ld3=p=>la('&u3='+p);" + "ld4=p=>la('&u4='+p);" +#else + "function ld1(p){" + "la('&u1='+p);" + "}" + "function ld2(p){" + "la('&u2='+p);" + "}" + "function ld3(p){" + "la('&u3='+p);" + "}" + "function ld4(p){" + "la('&u4='+p);" + "}" +#endif // USE_JAVASCRIPT_ES6 +#endif // USE_SHUTTER "wl(la);"; @@ -380,6 +401,11 @@ const char HTTP_MSG_SLIDER1[] PROGMEM = const char HTTP_MSG_SLIDER2[] PROGMEM = "
" D_DARKLIGHT "" D_BRIGHTLIGHT "
" "
"; +#ifdef USE_SHUTTER +const char HTTP_MSG_SLIDER3[] PROGMEM = + "
" D_CLOSE "" D_OPEN "
" + "
"; +#endif // USE_SHUTTER const char HTTP_MSG_RSTRT[] PROGMEM = "
" D_DEVICE_WILL_RESTART "

"; @@ -554,6 +580,7 @@ void ShowWebSource(uint32_t source) void ExecuteWebCommand(char* svalue, uint32_t source) { ShowWebSource(source); + last_source = source; ExecuteCommand(svalue, SRC_IGNORE); } @@ -992,6 +1019,13 @@ void HandleRoot(void) WSContentSend_P(HTTP_MSG_SLIDER2, Settings.light_dimmer); } #endif +#ifdef USE_SHUTTER + if (Settings.flag3.shutter_mode) { + for (uint32_t i = 0; i < shutters_present; i++) { + WSContentSend_P(HTTP_MSG_SLIDER3, Settings.shutter_position[i], i+1); + } + } +#endif // USE_SHUTTER WSContentSend_P(HTTP_TABLE100); WSContentSend_P(PSTR("")); #ifdef USE_SONOFF_IFAN @@ -1092,6 +1126,17 @@ bool HandleRootStatusRefresh(void) snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_COLORTEMPERATURE " %s"), tmp); ExecuteWebCommand(svalue, SRC_WEBGUI); } +#ifdef USE_SHUTTER + char webindex[5]; // WebGetArg name + for (uint32_t j = 1; j < 5; j++) { + snprintf_P(webindex, sizeof(webindex), PSTR("u%d"), j); + WebGetArg(webindex, tmp, sizeof(tmp)); // 0 - 100 percent + if (strlen(tmp)) { + snprintf_P(svalue, sizeof(svalue), PSTR("ShutterPosition%d %s"), j, tmp); + ExecuteWebCommand(svalue, SRC_WEBGUI); + } + } +#endif // USE_SHUTTER WebGetArg("k", tmp, sizeof(tmp)); // 1 - 16 Pre defined RF keys if (strlen(tmp)) { snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_RFKEY "%s"), tmp); diff --git a/sonoff/xdrv_07_domoticz.ino b/sonoff/xdrv_07_domoticz.ino index a65c82851..44959045b 100644 --- a/sonoff/xdrv_07_domoticz.ino +++ b/sonoff/xdrv_07_domoticz.ino @@ -42,7 +42,7 @@ const char DOMOTICZ_MESSAGE[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\" const char kDomoticzSensors[] PROGMEM = D_DOMOTICZ_TEMP "|" D_DOMOTICZ_TEMP_HUM "|" D_DOMOTICZ_TEMP_HUM_BARO "|" D_DOMOTICZ_POWER_ENERGY "|" D_DOMOTICZ_ILLUMINANCE "|" - D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER ; + D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER "|" D_DOMOTICZ_SHUTTER ; char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC; char domoticz_out_topic[] = DOMOTICZ_OUT_TOPIC; @@ -347,8 +347,15 @@ void DomoticzSensor(uint8_t idx, char *data) Response_P(PSTR("{\"idx\":%d,\"nvalue\":%s,\"Battery\":%d,\"RSSI\":%d}"), Settings.domoticz_sensor_idx[idx], data, DomoticzBatteryQuality(), DomoticzRssiQuality()); } else { + uint8_t nvalue = 0; +#ifdef USE_SHUTTER + if (DZ_SHUTTER == idx) { + uint8_t position = atoi(data); + nvalue = position < 2 ? 0 : (position == 100 ? 1 : 2); + } +#endif // USE_SHUTTER Response_P(DOMOTICZ_MESSAGE, - Settings.domoticz_sensor_idx[idx], 0, data, DomoticzBatteryQuality(), DomoticzRssiQuality()); + Settings.domoticz_sensor_idx[idx], nvalue, data, DomoticzBatteryQuality(), DomoticzRssiQuality()); } MqttPublish(domoticz_in_topic); memcpy(mqtt_data, dmess, sizeof(dmess)); diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index 74424386b..b26e5402d 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -605,6 +605,10 @@ void RulesEvery50ms(void) case 5: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break; case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break; case 7: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break; +#ifdef USE_SHUTTER + case 8: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moved\":1}}"), sizeof(json_event)); break; + case 9: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moving\":1}}"), sizeof(json_event)); break; +#endif // USE_SHUTTER } if (json_event[0]) { RulesProcessEvent(json_event); @@ -1881,9 +1885,6 @@ bool Xdrv10(uint8_t function) bool result = false; switch (function) { - case FUNC_PRE_INIT: - RulesInit(); - break; case FUNC_EVERY_50_MSECOND: RulesEvery50ms(); break; @@ -1910,6 +1911,9 @@ bool Xdrv10(uint8_t function) result = RulesMqttData(); break; #endif // SUPPORT_MQTT_EVENT + case FUNC_PRE_INIT: + RulesInit(); + break; } return result; } diff --git a/sonoff/xdrv_10_scripter.ino b/sonoff/xdrv_10_scripter.ino index 40fa40391..b1e91e64a 100644 --- a/sonoff/xdrv_10_scripter.ino +++ b/sonoff/xdrv_10_scripter.ino @@ -1514,6 +1514,19 @@ chknext: len+=1; goto exit; } +#ifdef USE_SHUTTER + if (!strncmp(vname,"sht[",4)) { + GetNumericResult(vname+4,OPER_EQU,&fvar,0); + uint8_t index=fvar; + if (index<=shutters_present) { + fvar=Settings.shutter_position[index-1]; + } else { + fvar=-1; + } + len+=1; + goto exit; + } +#endif if (!strncmp(vname,"pc[",3)) { GetNumericResult(vname+3,OPER_EQU,&fvar,0); uint8_t index=fvar; diff --git a/sonoff/xdrv_20_hue.ino b/sonoff/xdrv_20_hue.ino index 2b40b2eea..0299aba14 100644 --- a/sonoff/xdrv_20_hue.ino +++ b/sonoff/xdrv_20_hue.ino @@ -276,6 +276,12 @@ void HueLightStatus1(uint8_t device, String *response) if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254 if (bri < 1) bri = 1; +#ifdef USE_SHUTTER + if (ShutterState(device)) { + bri = (float)(Settings.shutter_invert[device-1] ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100; + } +#endif + if (light_type) { light_state.getHSB(&hue, &sat, nullptr); @@ -519,19 +525,32 @@ void HueLights(String *path) response.replace("{id", String(EncodeLightId(device))); response.replace("{cm", "on"); - on = hue_json["on"]; - switch(on) - { - case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE); - response.replace("{re", "false"); - break; - case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE); - response.replace("{re", "true"); - break; - default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false"); - break; +#ifdef USE_SHUTTER + if (ShutterState(device)) { + if (!change) { + on = hue_json["on"]; + bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it + change = true; + } + response.replace("{re", on ? "true" : "false"); + } else { +#endif + on = hue_json["on"]; + switch(on) + { + case false : ExecuteCommandPower(device, POWER_OFF, SRC_HUE); + response.replace("{re", "false"); + break; + case true : ExecuteCommandPower(device, POWER_ON, SRC_HUE); + response.replace("{re", "true"); + break; + default : response.replace("{re", (power & (1 << (device-1))) ? "true" : "false"); + break; + } + resp = true; +#ifdef USE_SHUTTER } - resp = true; +#endif // USE_SHUTTER } if (light_type && (local_light_subtype >= LST_SINGLE)) { @@ -637,6 +656,12 @@ void HueLights(String *path) resp = true; } if (change) { +#ifdef USE_SHUTTER + if (ShutterState(device)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_invert[device-1]); + SetShutterPosition(device, bri * 100.0f ); + } else +#endif if (light_type && (local_light_subtype > LST_NONE)) { // not relay if (!Settings.flag3.pwm_multi_channels) { if (g_gotct) { diff --git a/sonoff/xdrv_27_shutter.ino b/sonoff/xdrv_27_shutter.ino new file mode 100644 index 000000000..c6ebf8485 --- /dev/null +++ b/sonoff/xdrv_27_shutter.ino @@ -0,0 +1,588 @@ +/* + xdrv_27_shutter.ino - Shutter/Blind support for Sonoff-Tasmota + + Copyright (C) 2019 Stefan Bode + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_SHUTTER // +3.8k code +/*********************************************************************************************\ + * Energy +\*********************************************************************************************/ + +#define XDRV_27 27 + +#define D_PRFX_SHUTTER "Shutter" +#define D_CMND_SHUTTER_OPEN "Open" +#define D_CMND_SHUTTER_CLOSE "Close" +#define D_CMND_SHUTTER_STOP "Stop" +#define D_CMND_SHUTTER_POSITION "Position" +#define D_CMND_SHUTTER_OPENTIME "OpenDuration" +#define D_CMND_SHUTTER_CLOSETIME "CloseDuration" +#define D_CMND_SHUTTER_RELAY "Relay" +#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway" +#define D_CMND_SHUTTER_SETCLOSE "SetClose" +#define D_CMND_SHUTTER_INVERT "Invert" +#define D_CMND_SHUTTER_CLIBRATION "Calibration" + +#define D_SHUTTER "SHUTTER" + +enum ShutterModes { SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE }; + +const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|" + D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|" + D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|" + D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION; + +void (* const ShutterCommand[])(void) PROGMEM = { + &CmndShutterOpen, &CmndShutterClose, &CmndShutterStop, &CmndShutterPosition, + &CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay, + &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration }; + +const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_SHUTTER "%d\":{\"position\":%d,\"direction\":%d}"; + +Ticker TickerShutter; + +const uint16_t MOTOR_STOP_TIME=500; // in mS + +uint16_t Shutter_Open_Time[MAX_SHUTTERS] ; // duration to open the shutter +uint16_t Shutter_Close_Time[MAX_SHUTTERS]; // duration to close the shutter +int32_t Shutter_Open_Max[MAX_SHUTTERS]; // max value on maximum open calculated +int32_t Shutter_Target_Position[MAX_SHUTTERS] ; // position to go to +int32_t Shutter_Start_Position[MAX_SHUTTERS] ; +uint16_t Shutter_Close_Velocity[MAX_SHUTTERS]; // in relation to open velocity. higher value = faster +uint16_t Shutter_Operations[MAX_SHUTTERS]; +int8_t Shutter_Direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down +int32_t Shutter_Real_Position[MAX_SHUTTERS]; // value between 0 and Shutter_Open_Max +//power_t shutter_mask = 0; // bit mask with 11 at the position of relays that belong to at least ONE shutter +power_t old_power = power; // preserve old bitmask for power to extract the relay that changes. +power_t SwitchedRelay = 0; // bitmatrix that contain the relays that was lastly changed. +uint32_t shutter_time[MAX_SHUTTERS] ; +uint8_t shutterMode = 0; // operation mode definition. see enum type above OFF_OPEN-OFF_CLOSE, OFF_ON-OPEN_CLOSE, PULSE_OPEN-PULSE_CLOSE + +void Rtc_ms50_Second() +{ + for (byte i=0;i < MAX_SHUTTERS; i++) { + shutter_time[i]++; + } +} + +int32_t percent_to_realposition(uint8_t percent,uint8_t index) +{ + if (Settings.shutter_set50percent[index] != 50) { + return percent <= 5 ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; + } else { + return percent <= 5 ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; + } +} + +uint8_t realposition_to_percent(int32_t realpos, uint8_t index) +{ + if (Settings.shutter_set50percent[index] != 50) { + return Settings.shuttercoeff[2][index] * 5 > realpos ? realpos / Settings.shuttercoeff[2][index] : (realpos-Settings.shuttercoeff[0][index]) / Settings.shuttercoeff[1][index]; + } else { + return Settings.shuttercoeff[2][index] * 5 > realpos ? realpos / Settings.shuttercoeff[2][index] : (realpos-Settings.shuttercoeff[0][index]) / Settings.shuttercoeff[1][index]; + } +} + +void ShutterInit() +{ + shutters_present = 0; + shutter_mask = 0; + //Initialize to get relay that changed + old_power = power; + char shutter_open_chr[10]; + char shutter_close_chr[10]; + bool relay_in_interlock = false; + + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy); + + for (byte i=0;i < MAX_SHUTTERS; i++) { + // upgrade to 0.1sec calculation base. + if ( Settings.shutter_accuracy == 0) { + Settings.shutter_closetime[i] = Settings.shutter_closetime[i] * 10; + Settings.shutter_opentime[i] = Settings.shutter_opentime[i] * 10; + } + // set startrelay to 1 on first init, but only to shutter 1. 90% usecase + Settings.shutter_startrelay[i] = (Settings.shutter_startrelay[i] == 0 && i == 0? 1 : Settings.shutter_startrelay[i]); + if (Settings.shutter_startrelay[i] && Settings.shutter_startrelay[i] <9) { + shutters_present++; + + // Determine shutter types + shutter_mask |= 3 << (Settings.shutter_startrelay[i] -1) ; + + for (uint8_t i = 0; i < MAX_INTERLOCKS * Settings.flag.interlock; i++) { + //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Interlock state i=%d %d, flag %d, , shuttermask %d, maskedIL %d"),i, Settings.interlock[i], Settings.flag.interlock,shutter_mask, Settings.interlock[i]&shutter_mask); + if (Settings.interlock[i] && Settings.interlock[i] & shutter_mask) { + //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Relay in Interlock group")); + relay_in_interlock = true; + } + } + if ( relay_in_interlock) { + if (Settings.pulse_timer[i] > 0) { + shutterMode = SHT_PULSE_OPEN__PULSE_CLOSE; + } else { + shutterMode = SHT_OFF_OPEN__OFF_CLOSE; + } + } else { + shutterMode = SHT_OFF_ON__OPEN_CLOSE; + } + + TickerShutter.attach_ms(50, Rtc_ms50_Second ); + // default the 50 percent should not have any impact without changing it. set to 60 + Settings.shutter_set50percent[i] = (Settings.shutter_set50percent[i] == 0 ? 50 : Settings.shutter_set50percent[i]); + // use 10 sec. as default to allow everybody to play without deep initialize + Shutter_Open_Time[i] = Settings.shutter_opentime[i] > 0 ? Settings.shutter_opentime[i] : 100; + Shutter_Close_Time[i] = Settings.shutter_closetime[i] > 0 ? Settings.shutter_closetime[i] : 100; + + // Update Calculation 20 because time interval is 0.05 sec + Shutter_Open_Max[i] = 200 * Shutter_Open_Time[i]; + Shutter_Close_Velocity[i] = Shutter_Open_Max[i] / Shutter_Close_Time[i] / 2 ; + + // calculate a ramp slope at the first 5 percent to compensate that shutters move with down part later than the upper part + Settings.shuttercoeff[1][i] = Shutter_Open_Max[i] * (100 - Settings.shutter_set50percent[i] ) / 5000; + Settings.shuttercoeff[0][i] = Shutter_Open_Max[i] - (Settings.shuttercoeff[1][i] * 100); + Settings.shuttercoeff[2][i] = (Settings.shuttercoeff[0][i] + 5 * Settings.shuttercoeff[1][i]) / 5; + shutter_mask |= 3 << (Settings.shutter_startrelay[i] -1) ; + + Shutter_Real_Position[i] = percent_to_realposition(Settings.shutter_position[i], i); + //Shutter_Real_Position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i]; + Shutter_Start_Position[i] = Shutter_Real_Position[i]; + dtostrfd((double)Shutter_Open_Time[i] / 10 , 1, shutter_open_chr); + dtostrfd((double)Shutter_Close_Time[i] / 10, 1, shutter_close_chr); + + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d"), i, Settings.shutter_startrelay[i],Shutter_Real_Position[i],Settings.shutter_position[i], Shutter_Close_Velocity[i] , Shutter_Open_Max[i], shutter_open_chr, shutter_close_chr,Settings.shuttercoeff[0][i],Settings.shuttercoeff[1][i],Settings.shuttercoeff[2][i],Settings.shuttercoeff[3][i],Settings.shuttercoeff[4][i],shutter_mask,Settings.shutter_invert[i],shutterMode ); + } else { + // terminate loop at first INVALID shutter. + break; + } + Settings.shutter_accuracy = 1; + } +} + +void Schutter_Update_Position() +{ + char scommand[CMDSZ]; + char stopic[TOPSZ]; + + for (byte i=0; i < shutters_present; i++) { + if (Shutter_Direction[i] != 0) { + //char stemp1[20]; + Shutter_Real_Position[i] = Shutter_Start_Position[i] + ( shutter_time[i] * (Shutter_Direction[i] > 0 ? 100 : -Shutter_Close_Velocity[i])); + // avoid real position leaving the boundaries. + Shutter_Real_Position[i] = Shutter_Real_Position[i] < 0 ? 0 : (Shutter_Real_Position[i] > Shutter_Open_Max[i] ? Shutter_Open_Max[i] : Shutter_Real_Position[i]) ; + + // Add additional runtime, if shutter did not reach the endstop for some time. + if (Shutter_Target_Position[i] == Shutter_Real_Position[i] && Shutter_Target_Position[i] == 0) { + // for every operation add 5x50ms = 250ms to stop position + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Adding additional runtime")); + Shutter_Real_Position[i] += 500 * Shutter_Operations[i] ; + Shutter_Operations[i] = 0; + } + if (Shutter_Real_Position[i] * Shutter_Direction[i] >= Shutter_Target_Position[i] * Shutter_Direction[i] ) { + // calculate relay number responsible for current movement. + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Stop Condition detected: real: %d, Target: %d, direction: %d"),Shutter_Real_Position[i], Shutter_Target_Position[i],Shutter_Direction[i]); + uint8_t cur_relay = Settings.shutter_startrelay[i] + (Shutter_Direction[i] == 1 ? 0 : 1) ; + char stemp2[10]; + + Settings.shutter_position[i] = realposition_to_percent(Shutter_Real_Position[i], i); + //Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter_Real_Position[i] ? (Shutter_Real_Position[i] * 10 / Settings.shuttercoeff[2][i] + 4)/10 : ((Shutter_Real_Position[i]-Settings.shuttercoeff[0,i]) *10 / Settings.shuttercoeff[1][i] +4) / 10; + + if (0 < Settings.shutter_position[i] && Settings.shutter_position[i] < 100) { + Shutter_Operations[i]++; + } else { + Shutter_Operations[i] = 0; + } + + dtostrfd((double)shutter_time[i] / 20, 1, stemp2); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Real Pos. %d, Stoppos: %ld, relay: %d, direction %d, pulsetimer: %d, rtcshutter: %s [s], operationtime %d"), i, Shutter_Real_Position[i], Settings.shutter_position[i], cur_relay -1, Shutter_Direction[i], Settings.pulse_timer[cur_relay -1], stemp2, Shutter_Operations[i]); + Shutter_Start_Position[i] = Shutter_Real_Position[i]; + + // sending MQTT result to broker + snprintf_P(scommand, sizeof(scommand),PSTR("%s%d"), D_SHUTTER, i+1); + GetTopic_P(stopic, STAT, mqtt_topic, scommand); + Response_P("%d", Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]); + MqttPublish(stopic, Settings.flag.mqtt_power_retain); + + switch (shutterMode) { + case SHT_PULSE_OPEN__PULSE_CLOSE: + // we have a momentary switch here. Needs additional pulse on same relay after the end + if (SRC_PULSETIMER == last_source || SRC_SHUTTER == last_source || SRC_WEBGUI == last_source) { + ExecuteCommandPower(cur_relay, 1, SRC_SHUTTER); + } else { + last_source = SRC_SHUTTER; + } + break; + case SHT_OFF_ON__OPEN_CLOSE: + // This is a failsafe configuration. Relay1 ON/OFF Relay2 -1/1 direction + if ((1 << (Settings.shutter_startrelay[i]-1)) & power) { + ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER); + } + break; + case SHT_OFF_OPEN__OFF_CLOSE: + // avoid switching OFF a relay already OFF + if ((1 << (cur_relay-1)) & power) { + // Relay is on and need to be switched off. + ExecuteCommandPower(cur_relay, 0, SRC_SHUTTER); + } + break; + } + Shutter_Direction[i] = 0; + byte position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]; + Response_P(PSTR("{")); + ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, 0 /*Shutter_Direction[i]*/); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data); + XdrvRulesProcess(); + } + } + } + +} + +bool ShutterState(uint8_t device) +{ + device--; + device &= 3; + return (Settings.flag3.shutter_mode && (shutter_mask & (1 << (Settings.shutter_startrelay[device]-1))) ); +} + +void Shutter_StartInit (uint8_t index, uint8_t direction, int32_t target_pos) +{ + Shutter_Direction[index] = direction; + Shutter_Target_Position[index] = target_pos; + Shutter_Start_Position[index] = Shutter_Real_Position[index]; + shutter_time[index] = 0; + //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter: %d from %d to %d in directin %d"), index, Shutter_Start_Position[index], Shutter_Target_Position[index], Shutter_Direction[index]); +} + +void DelayForMotorStop() +{ + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop %d"), MOTOR_STOP_TIME); + delay(MOTOR_STOP_TIME); +} + +void Schutter_Report_Position() +{ + uint16_t shutter_moving = 0; + for (byte i=0; i < shutters_present; i++) { + if (Shutter_Direction[i] != 0) { + char stemp1[20]; + char stemp2[10]; + dtostrfd((double)shutter_time[i] / 20, 1, stemp2); + shutter_moving = 1; + //Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter_Real_Position[i] ? Shutter_Real_Position[i] / Settings.shuttercoeff[2][i] : (Shutter_Real_Position[i]-Settings.shuttercoeff[0,i]) / Settings.shuttercoeff[1][i]; + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d, Target %d, source: %s, start-pos: %d %%, direction: %d, rtcshutter: %s [s]"), i,Shutter_Real_Position[i], Shutter_Target_Position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), Settings.shutter_position[i], Shutter_Direction[i], stemp2 ); + } + } + if (rules_flag.shutter_moving > shutter_moving) { + rules_flag.shutter_moved = 1; + } else { + rules_flag.shutter_moved = 0; + } + rules_flag.shutter_moving = shutter_moving; + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); +} + +void Shutter_Relay_changed() +{ + + // SwitchedRelay = binary relay that was recently changed and cause an Action + // powerstate_local = binary powermatrix and relays from shutter: 0..3 + // relays_changed = bool if one of the relays that belong to the shutter changed not by shutter or pulsetimer + char stemp1[10]; + + + for (byte i=0; i < shutters_present; i++) { + power_t powerstate_local = (power >> (Settings.shutter_startrelay[i] -1)) & 3; + //uint8 manual_relays_changed = ((SwitchedRelay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_IGNORE != last_source && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ; + uint8 manual_relays_changed = ((SwitchedRelay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ; + if (manual_relays_changed) { + if (shutterMode == SHT_OFF_ON__OPEN_CLOSE) { + switch (powerstate_local) { + case 1: + DelayForMotorStop(); + Shutter_StartInit(i, 1, Shutter_Open_Max[i]); + break; + case 3: + DelayForMotorStop(); + Shutter_StartInit(i, -1, 0); + break; + default: + Shutter_Direction[i] = 0; + Shutter_Target_Position[i] = Shutter_Real_Position[i]; + } + } else { + if (Shutter_Direction[i] != 0 && (!powerstate_local || (powerstate_local && shutterMode == SHT_PULSE_OPEN__PULSE_CLOSE))) { + Shutter_Target_Position[i] = Shutter_Real_Position[i]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, switchedRelay %d, manual change %d"), i, Shutter_Target_Position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,SwitchedRelay,manual_relays_changed); + } else { + last_source = SRC_SHUTTER; // avoid switch off in the next loop + if (powerstate_local == 2) { // testing on CLOSE relay, if ON + // close with relay two + DelayForMotorStop(); + Shutter_StartInit(i, -1, 0); + } else { + // opens with relay one + DelayForMotorStop(); + Shutter_StartInit(i, 1, Shutter_Open_Max[i]); + } + } + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i, Shutter_Target_Position[i], powerstate_local); + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////////// +// Shutter specific functions +// TODO: move to shutter driver and make them accessible in a generic way + +// device: 1.. +// position: 0-100 +void SetShutterPosition(uint8_t device, uint8_t position) +{ + char svalue[32]; // Command and number parameter + snprintf_P(svalue, sizeof(svalue), PSTR(D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION "%d %d"), device, position); + ExecuteCommand(svalue, SRC_IGNORE); +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndShutterOpen(void) +{ + XdrvMailbox.payload = 100; + last_source = SRC_WEBGUI; + CmndShutterPosition(); +} + +void CmndShutterClose(void) +{ + XdrvMailbox.payload = 0; + last_source = SRC_WEBGUI; + CmndShutterPosition(); +} + +void CmndShutterStop(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + uint32_t index = XdrvMailbox.index -1; + if (Shutter_Direction[index] != 0) { + + //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving shutter %d: direction:%d"), XdrvMailbox.index, Shutter_Direction[index]); + + int32_t temp_realpos = Shutter_Start_Position[index] + ( (shutter_time[index]+10) * (Shutter_Direction[index] > 0 ? 100 : -Shutter_Close_Velocity[index])); + XdrvMailbox.payload = realposition_to_percent(temp_realpos, index); + //XdrvMailbox.payload = Settings.shuttercoeff[2][index] * 5 > temp_realpos ? temp_realpos / Settings.shuttercoeff[2][index] : (temp_realpos-Settings.shuttercoeff[0,index]) / Settings.shuttercoeff[1][index]; + last_source = SRC_WEBGUI; + CmndShutterPosition(); + } else { + ResponseCmndDone(); + } + } +} + +void CmndShutterPosition(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + uint32_t index = XdrvMailbox.index -1; + //limit the payload + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Position in: payload %d, index %d, source %d"), XdrvMailbox.payload , XdrvMailbox.index, last_source ); + + int8_t target_pos_percent = XdrvMailbox.payload < 0 ? 0 : (XdrvMailbox.payload > 100 ? 100 : XdrvMailbox.payload); + // webgui still send also on inverted shutter the native position. + target_pos_percent = Settings.shutter_invert[index] && SRC_WEBGUI != last_source ? 100 - target_pos_percent : target_pos_percent; + if (target_pos_percent != -99) { + //target_pos_percent = Settings.shutter_invert[index] ? 100 - target_pos_percent : target_pos_percent; + Shutter_Target_Position[index] = percent_to_realposition(target_pos_percent, index); + //Shutter_Target_Position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index]; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, realpos %d, target %d, payload %d"), last_source, Shutter_Real_Position[index] ,Shutter_Target_Position[index],target_pos_percent); + } + if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && abs(Shutter_Target_Position[index] - Shutter_Real_Position[index] ) / Shutter_Close_Velocity[index] > 2) { + int8_t new_shutterdirection = Shutter_Real_Position[index] < Shutter_Target_Position[index] ? 1 : -1; + if (Shutter_Direction[index] == -new_shutterdirection ) { + // direction need to be changed. on momentary switches first stop the Shutter + if (shutterMode == SHT_PULSE_OPEN__PULSE_CLOSE) { + // code for momentary shutters only small switch on to stop Shutter + ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); + delay(100); + } else { + ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 1 : 0), 0, SRC_SHUTTER); + DelayForMotorStop(); + } + } + if (Shutter_Direction[index] != new_shutterdirection ) { + Shutter_StartInit(index, new_shutterdirection, Shutter_Target_Position[index]); + Shutter_Operations[index]++; + if (shutterMode == SHT_OFF_ON__OPEN_CLOSE) { + ExecuteCommandPower(Settings.shutter_startrelay[index] , 0, SRC_SHUTTER); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay5 5s, xdrv %d"), XdrvMailbox.payload); + DelayForMotorStop(); + // Code for shutters with circuit safe configuration, switch the direction Relay + ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER); + // power on + ExecuteCommandPower(Settings.shutter_startrelay[index] , 1, SRC_SHUTTER); + } else { + // now start the motor for the right direction, work for momentary and normal shutters. + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter in direction %d"), Shutter_Direction[index]); + ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay6 5s, xdrv %d"), XdrvMailbox.payload); + } + SwitchedRelay = 0; + } + } else { + target_pos_percent = realposition_to_percent(Shutter_Real_Position[index], index); + } + ResponseCmndIdxNumber(Settings.shutter_invert[index] ? 100 - target_pos_percent : target_pos_percent); + } +} + +void CmndShutterOpenTime(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + Settings.shutter_opentime[XdrvMailbox.index-1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); + ShutterInit(); + } + char time_chr[10]; + dtostrfd((float)(Settings.shutter_opentime[XdrvMailbox.index-1]) / 10, 1, time_chr); + ResponseCmndIdxChar(time_chr); + } +} + +void CmndShutterCloseTime(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if (XdrvMailbox.data_len > 0) { + Settings.shutter_closetime[XdrvMailbox.index-1] = (uint16_t)(10 * CharToFloat(XdrvMailbox.data)); + ShutterInit(); + } + char time_chr[10]; + dtostrfd((float)(Settings.shutter_closetime[XdrvMailbox.index-1]) / 10, 1, time_chr); + ResponseCmndIdxChar(time_chr); + } +} + +void CmndShutterRelay(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 64)) { + Settings.shutter_startrelay[XdrvMailbox.index-1] = XdrvMailbox.payload; + if (XdrvMailbox.payload > 0) { + shutter_mask |= 3 << (XdrvMailbox.payload - 1); + } else { + shutter_mask ^= 3 << (Settings.shutter_startrelay[XdrvMailbox.index-1] - 1); + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Relay %d is %d"), XdrvMailbox.index, XdrvMailbox.payload); + Settings.shutter_startrelay[XdrvMailbox.index-1] = XdrvMailbox.payload; + ShutterInit(); + // if payload is 0 to disable the relay there must be a reboot. Otherwhise does not work + } + ResponseCmndIdxNumber(Settings.shutter_startrelay[XdrvMailbox.index -1]); + } +} + +void CmndShutterSetHalfway(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { + Settings.shutter_set50percent[XdrvMailbox.index-1] = Settings.shutter_invert[XdrvMailbox.index-1] ? 100 - XdrvMailbox.payload : XdrvMailbox.payload; + ShutterInit(); + ResponseCmndIdxNumber(XdrvMailbox.payload); // ???? + } else { + ResponseCmndIdxNumber(Settings.shutter_set50percent[XdrvMailbox.index-1]); + } + } +} + +void CmndShutterSetClose(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + Shutter_Real_Position[XdrvMailbox.index-1] = 0; + Shutter_StartInit(XdrvMailbox.index-1, 0, 0); + Settings.shutter_position[XdrvMailbox.index-1] = 0; + ResponseCmndChar(D_CONFIGURATION_RESET); + } +} + +void CmndShutterInvert(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) { + Settings.shutter_invert[XdrvMailbox.index-1] = XdrvMailbox.payload; + } + ResponseCmndIdxNumber(Settings.shutter_invert[XdrvMailbox.index-1]); + } +} + +void CmndShutterCalibration(void) // ???? +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) { + if (XdrvMailbox.data_len > 0) { + ResponseCmndIdxChar(XdrvMailbox.data); + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv27(uint8_t function) +{ + bool result = false; + + if (Settings.flag3.shutter_mode) { + switch (function) { + case FUNC_PRE_INIT: + ShutterInit(); + break; + case FUNC_EVERY_50_MSECOND: + Schutter_Update_Position(); + break; + case FUNC_EVERY_SECOND: + Schutter_Report_Position(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kShutterCommands, ShutterCommand); + break; + case FUNC_JSON_APPEND: + for (uint32_t i = 0; i < shutters_present; i++) { + uint8_t position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]; + ResponseAppend_P(","); + ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter_Direction[i]); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == i)) { + DomoticzSensor(DZ_SHUTTER, position); + } +#endif // USE_DOMOTICZ + } + break; + case FUNC_SET_POWER: + char stemp1[10]; + // extract the number of the relay that was switched and save for later in Update Position. + SwitchedRelay = power ^ old_power; + old_power = power; + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay: %d by %s"), SwitchedRelay,GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource)); + Shutter_Relay_changed(); + break; + } + } + return result; +} + +#endif //USE_SHUTTER