2019-07-14 14:23:02 +01:00
|
|
|
/*
|
2024-07-22 14:35:20 +01:00
|
|
|
xdrv_22_sonoff_ifan.ino - sonoff iFan02, iFan03 and iFan04 support for Tasmota
|
2019-07-14 14:23:02 +01:00
|
|
|
|
2021-01-01 12:44:04 +00:00
|
|
|
Copyright (C) 2021 Theo Arends
|
2019-07-14 14:23:02 +01:00
|
|
|
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2019-07-14 21:08:19 +01:00
|
|
|
#ifdef USE_SONOFF_IFAN
|
2019-07-14 14:23:02 +01:00
|
|
|
/*********************************************************************************************\
|
2024-07-22 14:35:20 +01:00
|
|
|
* Sonoff iFan02, iFan03 and iFan04
|
|
|
|
*
|
|
|
|
* For iFan04 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}
|
2019-07-14 14:23:02 +01:00
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
#define XDRV_22 22
|
|
|
|
|
|
|
|
const uint8_t MAX_FAN_SPEED = 4; // Max number of iFan02 fan speeds (0 .. 3)
|
|
|
|
|
2019-07-14 15:26:02 +01:00
|
|
|
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 };
|
2024-07-22 14:35:20 +01:00
|
|
|
const uint8_t kIFan04Speed[MAX_FAN_SPEED +2] = { 0x00, 0x01, 0x02, 0x04, 0x05, 0x06 };
|
2019-07-14 15:26:02 +01:00
|
|
|
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}};
|
2024-07-22 14:35:20 +01:00
|
|
|
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}};
|
2019-07-14 14:23:02 +01:00
|
|
|
|
2019-08-11 17:12:18 +01:00
|
|
|
const char kSonoffIfanCommands[] PROGMEM = "|" // No prefix
|
|
|
|
D_CMND_FANSPEED;
|
2019-08-11 14:18:11 +01:00
|
|
|
|
2019-11-24 11:24:35 +00:00
|
|
|
void (* const SonoffIfanCommand[])(void) PROGMEM = {
|
|
|
|
&CmndFanspeed };
|
2019-08-11 14:18:11 +01:00
|
|
|
|
2019-07-14 14:23:02 +01:00
|
|
|
uint8_t ifan_fanspeed_timer = 0;
|
|
|
|
uint8_t ifan_fanspeed_goal = 0;
|
|
|
|
bool ifan_receive_flag = false;
|
2019-07-14 21:08:19 +01:00
|
|
|
bool ifan_restart_flag = true;
|
2019-07-14 14:23:02 +01:00
|
|
|
|
|
|
|
/*********************************************************************************************/
|
|
|
|
|
2019-11-20 19:53:12 +00:00
|
|
|
bool IsModuleIfan(void)
|
2019-07-14 14:23:02 +01:00
|
|
|
{
|
2020-10-30 11:29:48 +00:00
|
|
|
return ((SONOFF_IFAN02 == TasmotaGlobal.module_type) || (SONOFF_IFAN03 == TasmotaGlobal.module_type));
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t MaxFanspeed(void)
|
|
|
|
{
|
|
|
|
return MAX_FAN_SPEED;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t GetFanspeed(void)
|
|
|
|
{
|
|
|
|
if (ifan_fanspeed_timer) {
|
2019-07-14 17:17:34 +01:00
|
|
|
return ifan_fanspeed_goal; // Do not show sequence fanspeed
|
2019-07-14 14:23:02 +01:00
|
|
|
} else {
|
2019-07-14 17:17:34 +01:00
|
|
|
/* Fanspeed is controlled by relay 2, 3 and 4 as in Sonoff 4CH.
|
|
|
|
000x = 0
|
|
|
|
001x = 1
|
|
|
|
011x = 2
|
|
|
|
101x = 3 (ifan02) or 100x = 3 (ifan03)
|
|
|
|
*/
|
2020-10-28 18:03:39 +00:00
|
|
|
uint8_t fanspeed = (uint8_t)(TasmotaGlobal.power &0xF) >> 1;
|
2019-07-14 17:17:34 +01:00
|
|
|
if (fanspeed) { fanspeed = (fanspeed >> 1) +1; } // 0, 1, 2, 3
|
2019-07-14 14:23:02 +01:00
|
|
|
return fanspeed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************/
|
|
|
|
|
|
|
|
void SonoffIFanSetFanspeed(uint8_t fanspeed, bool sequence)
|
|
|
|
{
|
2019-07-14 15:26:02 +01:00
|
|
|
ifan_fanspeed_timer = 0; // Stop any sequence
|
2019-07-14 14:23:02 +01:00
|
|
|
ifan_fanspeed_goal = fanspeed;
|
|
|
|
|
|
|
|
uint8_t fanspeed_now = GetFanspeed();
|
|
|
|
|
|
|
|
if (fanspeed == fanspeed_now) { return; }
|
|
|
|
|
2019-07-14 15:26:02 +01:00
|
|
|
uint8_t fans = kIFan02Speed[fanspeed];
|
2020-10-30 11:29:48 +00:00
|
|
|
if (SONOFF_IFAN03 == TasmotaGlobal.module_type) {
|
2019-07-14 15:26:02 +01:00
|
|
|
if (sequence) {
|
2024-07-22 14:35:20 +01:00
|
|
|
fanspeed = (TasmotaGlobal.gpio_optiona.ifan04_h) ? kIFan04Sequence[fanspeed_now][ifan_fanspeed_goal] : kIFan03Sequence[fanspeed_now][ifan_fanspeed_goal];
|
2019-07-14 15:26:02 +01:00
|
|
|
if (fanspeed != ifan_fanspeed_goal) {
|
|
|
|
if (0 == fanspeed_now) {
|
|
|
|
ifan_fanspeed_timer = 20; // Need extra time to power up fan
|
|
|
|
} else {
|
2019-07-14 14:23:02 +01:00
|
|
|
ifan_fanspeed_timer = 2;
|
2019-07-14 15:26:02 +01:00
|
|
|
}
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
}
|
2024-07-22 14:35:20 +01:00
|
|
|
fans = (TasmotaGlobal.gpio_optiona.ifan04_h) ? kIFan04Speed[fanspeed] : kIFan03Speed[fanspeed];
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
2019-07-14 15:26:02 +01:00
|
|
|
for (uint32_t i = 2; i < 5; i++) {
|
2019-09-04 11:20:04 +01:00
|
|
|
uint8_t state = (fans &1) + POWER_OFF_NO_STATE; // Add no publishPowerState
|
2019-07-14 17:17:34 +01:00
|
|
|
ExecuteCommandPower(i, state, SRC_IGNORE); // Use relay 2, 3 and 4
|
2019-07-14 15:26:02 +01:00
|
|
|
fans >>= 1;
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef USE_DOMOTICZ
|
2019-07-14 15:26:02 +01:00
|
|
|
if (sequence) { DomoticzUpdateFanState(); } // Command FanSpeed feedback
|
2019-07-14 14:23:02 +01:00
|
|
|
#endif // USE_DOMOTICZ
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************/
|
|
|
|
|
|
|
|
void SonoffIfanReceived(void)
|
|
|
|
{
|
|
|
|
char svalue[32];
|
|
|
|
|
2020-10-30 11:29:48 +00:00
|
|
|
uint8_t mode = TasmotaGlobal.serial_in_buffer[3];
|
|
|
|
uint8_t action = TasmotaGlobal.serial_in_buffer[6];
|
2019-07-14 14:23:02 +01:00
|
|
|
|
|
|
|
if (4 == mode) {
|
|
|
|
if (action < 4) {
|
|
|
|
// AA 55 01 04 00 01 00 06 - Fan 0
|
|
|
|
// AA 55 01 04 00 01 01 07 - Fan 1
|
|
|
|
// AA 55 01 04 00 01 02 08 - Fan 2
|
|
|
|
// AA 55 01 04 00 01 03 09 - Fan 3
|
|
|
|
if (action != GetFanspeed()) {
|
|
|
|
snprintf_P(svalue, sizeof(svalue), PSTR(D_CMND_FANSPEED " %d"), action);
|
|
|
|
ExecuteCommand(svalue, SRC_REMOTE);
|
2019-08-12 16:18:08 +01:00
|
|
|
#ifdef USE_BUZZER
|
2019-10-15 15:23:37 +01:00
|
|
|
BuzzerEnabledBeep((action) ? action : 1, (action) ? 1 : 4); // Beep action times
|
2019-08-12 16:18:08 +01:00
|
|
|
#endif
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// AA 55 01 04 00 01 04 0A - Light
|
|
|
|
ExecuteCommandPower(1, POWER_TOGGLE, SRC_REMOTE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (6 == mode) {
|
|
|
|
// AA 55 01 06 00 01 01 09 - Buzzer
|
2021-06-11 17:14:12 +01:00
|
|
|
Settings->flag3.buzzer_enable = !Settings->flag3.buzzer_enable; // SetOption67 - Enable buzzer when available
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
if (7 == mode) {
|
|
|
|
// AA 55 01 07 00 01 01 0A - Rf long press - forget RF codes
|
2019-08-12 16:18:08 +01:00
|
|
|
#ifdef USE_BUZZER
|
2019-10-15 15:23:37 +01:00
|
|
|
BuzzerEnabledBeep(4, 1); // Beep four times
|
2019-08-12 16:18:08 +01:00
|
|
|
#endif
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
2019-07-14 17:17:34 +01:00
|
|
|
|
|
|
|
// Send Acknowledge - Copy first 5 bytes, reset byte 6 and store crc in byte 7
|
|
|
|
// AA 55 01 04 00 00 05
|
2020-10-30 11:29:48 +00:00
|
|
|
TasmotaGlobal.serial_in_buffer[5] = 0; // Ack
|
|
|
|
TasmotaGlobal.serial_in_buffer[6] = 0; // Crc
|
2019-07-14 17:17:34 +01:00
|
|
|
for (uint32_t i = 0; i < 7; i++) {
|
2020-10-30 11:29:48 +00:00
|
|
|
if ((i > 1) && (i < 6)) { TasmotaGlobal.serial_in_buffer[6] += TasmotaGlobal.serial_in_buffer[i]; }
|
|
|
|
Serial.write(TasmotaGlobal.serial_in_buffer[i]);
|
2019-07-14 17:17:34 +01:00
|
|
|
}
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SonoffIfanSerialInput(void)
|
|
|
|
{
|
2020-10-30 11:29:48 +00:00
|
|
|
if (SONOFF_IFAN03 != TasmotaGlobal.module_type) { return false; }
|
2020-07-14 15:21:11 +01:00
|
|
|
|
2020-10-30 11:29:48 +00:00
|
|
|
if (0xAA == TasmotaGlobal.serial_in_byte) { // 0xAA - Start of text
|
2020-10-29 11:21:24 +00:00
|
|
|
TasmotaGlobal.serial_in_byte_counter = 0;
|
2020-07-14 15:21:11 +01:00
|
|
|
ifan_receive_flag = true;
|
|
|
|
}
|
|
|
|
if (ifan_receive_flag) {
|
2020-10-30 11:29:48 +00:00
|
|
|
TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter++] = TasmotaGlobal.serial_in_byte;
|
2020-10-29 11:21:24 +00:00
|
|
|
if (TasmotaGlobal.serial_in_byte_counter == 8) {
|
2020-07-14 15:21:11 +01:00
|
|
|
// AA 55 01 01 00 01 01 04 - Wifi long press - start wifi setup
|
|
|
|
// AA 55 01 01 00 01 02 05 - Rf and Wifi short press
|
|
|
|
// AA 55 01 04 00 01 00 06 - Fan 0
|
|
|
|
// AA 55 01 04 00 01 01 07 - Fan 1
|
|
|
|
// AA 55 01 04 00 01 02 08 - Fan 2
|
|
|
|
// AA 55 01 04 00 01 03 09 - Fan 3
|
|
|
|
// AA 55 01 04 00 01 04 0A - Light
|
|
|
|
// AA 55 01 06 00 01 01 09 - Buzzer
|
|
|
|
// AA 55 01 07 00 01 01 0A - Rf long press - forget RF codes
|
2022-11-11 13:34:58 +00:00
|
|
|
AddLogSerial();
|
2020-07-14 15:21:11 +01:00
|
|
|
uint8_t crc = 0;
|
|
|
|
for (uint32_t i = 2; i < 7; i++) {
|
2020-10-30 11:29:48 +00:00
|
|
|
crc += TasmotaGlobal.serial_in_buffer[i];
|
2020-07-14 15:21:11 +01:00
|
|
|
}
|
2020-10-30 11:29:48 +00:00
|
|
|
if (crc == TasmotaGlobal.serial_in_buffer[7]) {
|
2020-07-14 15:21:11 +01:00
|
|
|
SonoffIfanReceived();
|
|
|
|
ifan_receive_flag = false;
|
|
|
|
return true;
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
}
|
2020-10-30 11:29:48 +00:00
|
|
|
TasmotaGlobal.serial_in_byte = 0;
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
2020-07-14 15:21:11 +01:00
|
|
|
return false;
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Commands
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2019-08-11 14:18:11 +01:00
|
|
|
void CmndFanspeed(void)
|
2019-07-14 14:23:02 +01:00
|
|
|
{
|
2019-08-11 14:18:11 +01:00
|
|
|
if (XdrvMailbox.data_len > 0) {
|
|
|
|
if ('-' == XdrvMailbox.data[0]) {
|
|
|
|
XdrvMailbox.payload = (int16_t)GetFanspeed() -1;
|
|
|
|
if (XdrvMailbox.payload < 0) { XdrvMailbox.payload = MAX_FAN_SPEED -1; }
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
2019-08-11 14:18:11 +01:00
|
|
|
else if ('+' == XdrvMailbox.data[0]) {
|
|
|
|
XdrvMailbox.payload = GetFanspeed() +1;
|
|
|
|
if (XdrvMailbox.payload > MAX_FAN_SPEED -1) { XdrvMailbox.payload = 0; }
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
2019-08-11 14:18:11 +01:00
|
|
|
}
|
|
|
|
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_FAN_SPEED)) {
|
|
|
|
SonoffIFanSetFanspeed(XdrvMailbox.payload, true);
|
|
|
|
}
|
|
|
|
ResponseCmndNumber(GetFanspeed());
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************/
|
|
|
|
|
|
|
|
bool SonoffIfanInit(void)
|
|
|
|
{
|
2020-10-30 11:29:48 +00:00
|
|
|
if (SONOFF_IFAN03 == TasmotaGlobal.module_type) {
|
2019-12-29 12:27:48 +00:00
|
|
|
SetSerial(9600, TS_SERIAL_8N1);
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
return false; // Continue init chain
|
|
|
|
}
|
|
|
|
|
|
|
|
void SonoffIfanUpdate(void)
|
|
|
|
{
|
2020-10-30 11:29:48 +00:00
|
|
|
if (SONOFF_IFAN03 == TasmotaGlobal.module_type) {
|
2019-07-14 14:23:02 +01:00
|
|
|
if (ifan_fanspeed_timer) {
|
|
|
|
ifan_fanspeed_timer--;
|
|
|
|
if (!ifan_fanspeed_timer) {
|
|
|
|
SonoffIFanSetFanspeed(ifan_fanspeed_goal, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-14 21:08:19 +01:00
|
|
|
|
2020-10-30 11:29:48 +00:00
|
|
|
if (ifan_restart_flag && (4 == TasmotaGlobal.uptime) && (SONOFF_IFAN02 == TasmotaGlobal.module_type)) { // Microcontroller needs 3 seconds before accepting commands
|
2019-07-14 21:08:19 +01:00
|
|
|
ifan_restart_flag = false;
|
|
|
|
SetDevicePower(1, SRC_RETRY); // Sync with default power on state microcontroller being Light ON and Fan OFF
|
2020-10-28 18:03:39 +00:00
|
|
|
SetDevicePower(TasmotaGlobal.power, SRC_RETRY); // Set required power on state
|
2019-07-14 21:08:19 +01:00
|
|
|
}
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
2022-11-11 13:34:58 +00:00
|
|
|
bool Xdrv22(uint32_t function) {
|
2019-07-14 14:23:02 +01:00
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
if (IsModuleIfan()) {
|
|
|
|
switch (function) {
|
|
|
|
case FUNC_EVERY_250_MSECOND:
|
|
|
|
SonoffIfanUpdate();
|
|
|
|
break;
|
2019-07-14 17:17:34 +01:00
|
|
|
case FUNC_SERIAL:
|
|
|
|
result = SonoffIfanSerialInput();
|
|
|
|
break;
|
2019-07-14 14:23:02 +01:00
|
|
|
case FUNC_COMMAND:
|
2019-08-11 14:18:11 +01:00
|
|
|
result = DecodeCommand(kSonoffIfanCommands, SonoffIfanCommand);
|
2019-07-14 14:23:02 +01:00
|
|
|
break;
|
2019-07-14 17:17:34 +01:00
|
|
|
case FUNC_MODULE_INIT:
|
|
|
|
result = SonoffIfanInit();
|
2019-07-14 14:23:02 +01:00
|
|
|
break;
|
2023-12-27 21:03:56 +00:00
|
|
|
case FUNC_ACTIVE:
|
|
|
|
result = true;
|
|
|
|
break;
|
2019-07-14 14:23:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2019-07-14 21:08:19 +01:00
|
|
|
|
|
|
|
#endif // USE_SONOFF_IFAN
|