From 895fbb2e8a3125ed384492f00acdaee447a4eddb Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:35:45 +0200 Subject: [PATCH] Add optional USE_SONOFF_IFAN_CONFIG --- .../xdrv_22_sonoff_ifan.ino | 150 +++++++++++++----- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_22_sonoff_ifan.ino b/tasmota/tasmota_xdrv_driver/xdrv_22_sonoff_ifan.ino index 0644eeab0..ef640165c 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_22_sonoff_ifan.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_22_sonoff_ifan.ino @@ -21,18 +21,27 @@ /*********************************************************************************************\ * Sonoff iFan02, iFan03 and iFan04 * - * For iFan04 activate below template + * For iFan04-H activate below template: * {"NAME":"Sonoff iFan04-H","GPIO":[32,3200,5735,3232,0,0,256,512,226,320,225,227,0,0],"FLAG":0,"BASE":71} + * + * #ifdef USE_SONOFF_IFAN_CONFIG + * No template change needed. For iFan04-H select + * - a rule like: rule3 on file#ifan.dat do {"Name":"iFan04-H","Speed":[0,1,2,4,5,6],"Sequence":[[0,4,5,3],[0,1,2,3],[0,1,2,3],[0,4,5,3]]} endon + * - a script like: -y{"Name":"iFan04-H","Speed":[0,1,2,4,5,6],"Sequence":[[0,4,5,3],[0,1,2,3],[0,1,2,3],[0,4,5,3]]}# + * - a file called ifan.dat with contents: {"Name":"iFan04-H","Speed":[0,1,2,4,5,6],"Sequence":[[0,4,5,3],[0,1,2,3],[0,1,2,3],[0,4,5,3]]} + * + * For reference only: + * rule3 on file#ifan.dat do {"Name":"iFan03","Speed":[0,1,3,4,5,6],"Sequence":[[0,2,2,2],[0,1,2,4],[1,1,2,5],[4,4,5,3]]} endon \*********************************************************************************************/ #define XDRV_22 22 +//#define USE_SONOFF_IFAN_CONFIG // Enable ifan user config + const uint8_t MAX_FAN_SPEED = 4; // Max number of iFan02 fan speeds (0 .. 3) const uint8_t kIFan02Speed[MAX_FAN_SPEED] = { 0x00, 0x01, 0x03, 0x05 }; -const uint8_t kIFan03Speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x03, 0x04, 0x05, 0x06 }; const uint8_t kIFan04Speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x02, 0x04, 0x05, 0x06 }; -const uint8_t kIFan03Sequence[MAX_FAN_SPEED][MAX_FAN_SPEED] = {{0, 2, 2, 2}, {0, 1, 2, 4}, {1, 1, 2, 5}, {4, 4, 5, 3}}; const uint8_t kIFan04Sequence[MAX_FAN_SPEED][MAX_FAN_SPEED] = {{0, 4, 5, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 4, 5, 3}}; const char kSonoffIfanCommands[] PROGMEM = "|" // No prefix @@ -41,27 +50,28 @@ const char kSonoffIfanCommands[] PROGMEM = "|" // No prefix void (* const SonoffIfanCommand[])(void) PROGMEM = { &CmndFanspeed }; -uint8_t ifan_fanspeed_timer = 0; -uint8_t ifan_fanspeed_goal = 0; -bool ifan_receive_flag = false; -bool ifan_restart_flag = true; +struct IFAN { + uint8_t speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x03, 0x04, 0x05, 0x06 }; + uint8_t sequence[MAX_FAN_SPEED][MAX_FAN_SPEED] = {{0, 2, 2, 2}, {0, 1, 2, 4}, {1, 1, 2, 5}, {4, 4, 5, 3}}; + uint8_t fanspeed_timer = 0; + uint8_t fanspeed_goal = 0; + bool receive_flag = false; + bool restart_flag = true; +} Ifan; /*********************************************************************************************/ -bool IsModuleIfan(void) -{ +bool IsModuleIfan(void) { return ((SONOFF_IFAN02 == TasmotaGlobal.module_type) || (SONOFF_IFAN03 == TasmotaGlobal.module_type)); } -uint8_t MaxFanspeed(void) -{ +uint8_t MaxFanspeed(void) { return MAX_FAN_SPEED; } -uint8_t GetFanspeed(void) -{ - if (ifan_fanspeed_timer) { - return ifan_fanspeed_goal; // Do not show sequence fanspeed +uint8_t GetFanspeed(void) { + if (Ifan.fanspeed_timer) { + return Ifan.fanspeed_goal; // Do not show sequence fanspeed } else { /* Fanspeed is controlled by relay 2, 3 and 4 as in Sonoff 4CH. 000x = 0 @@ -77,28 +87,26 @@ uint8_t GetFanspeed(void) /*********************************************************************************************/ -void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence) -{ - ifan_fanspeed_timer = 0; // Stop any sequence - ifan_fanspeed_goal = fanspeed; +void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence) { + Ifan.fanspeed_timer = 0; // Stop any sequence + Ifan.fanspeed_goal = fanspeed; uint8_t fanspeed_now = GetFanspeed(); - if (fanspeed == fanspeed_now) { return; } uint8_t fans = kIFan02Speed[fanspeed]; if (SONOFF_IFAN03 == TasmotaGlobal.module_type) { if (sequence) { - fanspeed = (TasmotaGlobal.gpio_optiona.ifan04_h) ? kIFan04Sequence[fanspeed_now][ifan_fanspeed_goal] : kIFan03Sequence[fanspeed_now][ifan_fanspeed_goal]; - if (fanspeed != ifan_fanspeed_goal) { + fanspeed = Ifan.sequence[fanspeed_now][Ifan.fanspeed_goal]; + if (fanspeed != Ifan.fanspeed_goal) { if (0 == fanspeed_now) { - ifan_fanspeed_timer = 20; // Need extra time to power up fan + Ifan.fanspeed_timer = 20; // Need extra time to power up fan } else { - ifan_fanspeed_timer = 2; + Ifan.fanspeed_timer = 2; } } } - fans = (TasmotaGlobal.gpio_optiona.ifan04_h) ? kIFan04Speed[fanspeed] : kIFan03Speed[fanspeed]; + fans = Ifan.speed[fanspeed]; } for (uint32_t i = 2; i < 5; i++) { uint8_t state = (fans &1) + POWER_OFF_NO_STATE; // Add no publishPowerState @@ -113,8 +121,7 @@ void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence) /*********************************************************************************************/ -void SonoffIfanReceived(void) -{ +void SonoffIfanReceived(void) { char svalue[32]; uint8_t mode = TasmotaGlobal.serial_in_buffer[3]; @@ -159,15 +166,14 @@ void SonoffIfanReceived(void) } } -bool SonoffIfanSerialInput(void) -{ +bool SonoffIfanSerialInput(void) { if (SONOFF_IFAN03 != TasmotaGlobal.module_type) { return false; } if (0xAA == TasmotaGlobal.serial_in_byte) { // 0xAA - Start of text TasmotaGlobal.serial_in_byte_counter = 0; - ifan_receive_flag = true; + Ifan.receive_flag = true; } - if (ifan_receive_flag) { + if (Ifan.receive_flag) { TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter++] = TasmotaGlobal.serial_in_byte; if (TasmotaGlobal.serial_in_byte_counter == 8) { // AA 55 01 01 00 01 01 04 - Wifi long press - start wifi setup @@ -186,7 +192,7 @@ bool SonoffIfanSerialInput(void) } if (crc == TasmotaGlobal.serial_in_buffer[7]) { SonoffIfanReceived(); - ifan_receive_flag = false; + Ifan.receive_flag = false; return true; } } @@ -199,8 +205,7 @@ bool SonoffIfanSerialInput(void) * Commands \*********************************************************************************************/ -void CmndFanspeed(void) -{ +void CmndFanspeed(void) { if (XdrvMailbox.data_len > 0) { if ('-' == XdrvMailbox.data[0]) { XdrvMailbox.payload = (int16_t)GetFanspeed() -1; @@ -219,27 +224,84 @@ void CmndFanspeed(void) /*********************************************************************************************/ -bool SonoffIfanInit(void) -{ +void SonoffIFanParameters(void) { + if (TasmotaGlobal.gpio_optiona.ifan04_h) { // GPIO Option_A8 + memcpy(Ifan.speed, kIFan04Speed, sizeof(Ifan.speed)); + memcpy(Ifan.sequence, kIFan04Sequence, sizeof(Ifan.sequence)); + } + +#ifdef USE_SONOFF_IFAN_CONFIG + + String ifantmplt = ""; +#ifdef USE_UFILESYS + ifantmplt = TfsLoadString("/ifan.dat"); +#endif // USE_UFILESYS +#ifdef USE_RULES + if (!ifantmplt.length()) { + ifantmplt = RuleLoadFile("IFAN.DAT"); + } +#endif // USE_RULES +#ifdef USE_SCRIPT + if (!ifantmplt.length()) { + ifantmplt = ScriptLoadSection(">y"); + } +#endif // USE_SCRIPT + if (ifantmplt.length() > 75) { + JsonParser parser((char*)ifantmplt.c_str()); + JsonParserObject root = parser.getRootObject(); + if (root) { + // rule3 on file#ifan.dat do {"Name":"iFan03","Speed":[0,1,3,4,5,6],"Sequence":[[0,2,2,2],[0,1,2,4],[1,1,2,5],[4,4,5,3]]} endon + // rule3 on file#ifan.dat do {"Name":"iFan04-H","Speed":[0,1,2,4,5,6],"Sequence":[[0,4,5,3],[0,1,2,3],[0,1,2,3],[0,4,5,3]]} endon + JsonParserArray arr_speed = root[PSTR("SPEED")]; + if (arr_speed) { + for (uint32_t i = 0; i < sizeof(Ifan.speed); i++) { + JsonParserToken val = arr_speed[i]; + if (!val) { break; } + Ifan.speed[i] = val.getUInt() & 0x07; + } + } + JsonParserArray arr_sequence = root[PSTR("SEQUENCE")]; + if (arr_sequence) { + for (uint32_t j = 0; j < MAX_FAN_SPEED; j++) { + JsonParserArray arr_sequence2 = arr_sequence[j]; + if (!arr_sequence2) { break; } + for (uint32_t i = 0; i < MAX_FAN_SPEED; i++) { + JsonParserToken val = arr_sequence2[i]; + if (!val) { break; } + Ifan.sequence[j][i] = val.getUInt() & 0x07; + } + } + } + JsonParserToken val = root[PSTR(D_JSON_NAME)]; + AddLog(LOG_LEVEL_DEBUG, PSTR("IFN: Loaded%s%s"), (val)?" ":"", (val)?val.getStr():""); + } + } + +#endif // USE_SONOFF_IFAN_CONFIG + +// AddLog(LOG_LEVEL_DEBUG, PSTR("IFN: Speed %6_H, Sequence %16_H"), Ifan.speed, Ifan.sequence); +} + +bool SonoffIfanInit(void) { if (SONOFF_IFAN03 == TasmotaGlobal.module_type) { SetSerial(9600, TS_SERIAL_8N1); + SonoffIFanParameters(); } return false; // Continue init chain } -void SonoffIfanUpdate(void) -{ +void SonoffIfanUpdate(void) { if (SONOFF_IFAN03 == TasmotaGlobal.module_type) { - if (ifan_fanspeed_timer) { - ifan_fanspeed_timer--; - if (!ifan_fanspeed_timer) { - SonoffIFanSetFanspeed(ifan_fanspeed_goal, false); + if (Ifan.fanspeed_timer) { + Ifan.fanspeed_timer--; + if (!Ifan.fanspeed_timer) { + SonoffIFanSetFanspeed(Ifan.fanspeed_goal, false); } } } - if (ifan_restart_flag && (4 == TasmotaGlobal.uptime) && (SONOFF_IFAN02 == TasmotaGlobal.module_type)) { // Microcontroller needs 3 seconds before accepting commands - ifan_restart_flag = false; + if (Ifan.restart_flag && (4 == TasmotaGlobal.uptime) && (SONOFF_IFAN02 == TasmotaGlobal.module_type)) { // Microcontroller needs 3 seconds before accepting commands + Ifan.restart_flag = false; SetDevicePower(1, SRC_RETRY); // Sync with default power on state microcontroller being Light ON and Fan OFF SetDevicePower(TasmotaGlobal.power, SRC_RETRY); // Set required power on state }