From 49022d03201afd13d282d687bf7ff5678b003e0d Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sun, 14 Jul 2019 15:23:02 +0200 Subject: [PATCH] 6.6.0.2 Add support for Sonoff iFan03 as module 71 Add support for Sonoff iFan03 as module 71 (#5988) --- sonoff/_changelog.ino | 9 +- sonoff/settings.h | 6 +- sonoff/sonoff.h | 8 +- sonoff/sonoff.ino | 75 +++----- sonoff/sonoff_template.h | 26 +++ sonoff/sonoff_version.h | 2 +- sonoff/xdrv_01_webserver.ino | 12 +- sonoff/xdrv_02_mqtt.ino | 6 +- sonoff/xdrv_07_domoticz.ino | 8 +- sonoff/xdrv_22_sonoff_ifan.ino | 329 +++++++++++++++++++++++++++++++++ 10 files changed, 406 insertions(+), 75 deletions(-) create mode 100644 sonoff/xdrv_22_sonoff_ifan.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index d83520eff..64ddee9df 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -1,7 +1,14 @@ /*********************************************************************************************\ + * 6.6.0.2 20190714 + * Add support for Sonoff iFan03 as module 71 (#5988) + * Add support for a buzzer + * Add command SetOption67 0/1 to disable or enable a buzzer as used in iFan03 + * * 6.6.0.1 20190708 * Fix Domoticz battery level set to 100 if define USE_ADC_VCC is not used (#6033) * Fix Force Elliptic Curve for Letsencrypt TLS #6042 + * Fix WeMo emulation for 1G echo and 2G echo dot (#6086) + * Fix Xiaomi Philips brightness (#6091) * Change defines USE_TX20_WIND_SENSOR and USE_RC_SWITCH in my_user_config.h to disable to lower iram usage enabling latest core compilation (#6060, #6062) * Add blend RGB leds with White leds for better whites (#5895, #5704) * Add command SetOption41 0..8 to control number of Tuya switches (#6039) @@ -11,8 +18,6 @@ * Add AZ7798 automatic setting of clock display (#6034) * Add Epoch and UptimeSec to JSON messages (#6068) * Add support for up to 4 INA219 sensors (#6046) - * Fix WeMo emulation for 1G echo and 2G echo dot (#6086) - * Fix Xiaomi Philips brightness (#6091) * * 6.6.0 20190707 * Remove support of TLS on core 2.3.0 and extent support on core 2.4.2 and up diff --git a/sonoff/settings.h b/sonoff/settings.h index 8b593f9ec..da10fd06c 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -78,9 +78,9 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t no_hold_retain : 1; // bit 12 (v6.4.1.19) - SetOption62 - Don't use retain flag on HOLD messages uint32_t no_power_feedback : 1; // bit 13 (v6.5.0.9) - SetOption63 - Don't scan relay power state at restart uint32_t use_underscore : 1; // bit 14 (v6.5.0.12) - SetOption64 - Enable "_" instead of "-" as sensor index separator - uint32_t tuya_show_dimmer : 1; // bit 15 (v6.5.0.15) - SetOption65 - Enable or Disable Dimmer slider control - uint32_t tuya_dimmer_range_255 : 1; // bit 16 (v6.6.0.2) - SetOption66 - Enable or Disable Dimmer range 255 slider control - uint32_t spare17 : 1; + uint32_t tuya_show_dimmer : 1; // bit 15 (v6.5.0.15) - SetOption65 - Enable or Disable Dimmer slider control + uint32_t tuya_dimmer_range_255 : 1; // bit 16 (v6.6.0.1) - SetOption66 - Enable or Disable Dimmer range 255 slider control + uint32_t buzzer_enable : 1; // bit 17 (v6.6.0.1) - SetOption67 - Enable buzzer when available uint32_t spare18 : 1; uint32_t spare19 : 1; uint32_t spare20 : 1; diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index f739f84df..5a7d9c76a 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -71,8 +71,6 @@ 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 -const uint8_t MAX_FAN_SPEED = 4; // Max number of iFan02 fan speeds (0 .. 3) - const char MQTT_TOKEN_PREFIX[] PROGMEM = "%prefix%"; // To be substituted by mqtt_prefix[x] const char MQTT_TOKEN_TOPIC[] PROGMEM = "%topic%"; // To be substituted by mqtt_topic, mqtt_grptopic, mqtt_buttontopic, mqtt_switchtopic const char WIFI_HOSTNAME[] = "%s-%04d"; // Expands to - @@ -256,13 +254,11 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_MODULE_INIT, FUNC_PRE_INIT, FUN FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS}; 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_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"; + 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"; const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; -const uint8_t kIFan02Speed[MAX_FAN_SPEED][3] = {{6,6,6}, {7,6,6}, {7,7,6}, {7,6,7}}; // Do not use PROGMEM as it fails - /*********************************************************************************************\ * Extern global variables \*********************************************************************************************/ diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 04521ae00..465b59caf 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -73,7 +73,7 @@ #include "settings.h" enum TasmotaCommands { - CMND_BACKLOG, CMND_DELAY, CMND_POWER, CMND_FANSPEED, CMND_STATUS, CMND_STATE, CMND_POWERONSTATE, CMND_PULSETIME, + CMND_BACKLOG, CMND_DELAY, CMND_POWER, CMND_STATUS, CMND_STATE, CMND_POWERONSTATE, CMND_PULSETIME, CMND_BLINKTIME, CMND_BLINKCOUNT, CMND_SENSOR, CMND_SAVEDATA, CMND_SETOPTION, CMND_TEMPERATURE_RESOLUTION, CMND_HUMIDITY_RESOLUTION, CMND_PRESSURE_RESOLUTION, CMND_POWER_RESOLUTION, CMND_VOLTAGE_RESOLUTION, CMND_FREQUENCY_RESOLUTION, CMND_CURRENT_RESOLUTION, CMND_ENERGY_RESOLUTION, CMND_WEIGHT_RESOLUTION, CMND_MODULE, CMND_MODULES, CMND_ADC, CMND_ADCS, CMND_GPIO, CMND_GPIOS, CMND_PWM, CMND_PWMFREQUENCY, CMND_PWMRANGE, CMND_COUNTER, CMND_COUNTERTYPE, @@ -83,7 +83,7 @@ enum TasmotaCommands { CMND_TELEPERIOD, CMND_RESTART, CMND_RESET, CMND_TIME, CMND_TIMEZONE, CMND_TIMESTD, CMND_TIMEDST, CMND_ALTITUDE, CMND_LEDPOWER, CMND_LEDSTATE, CMND_LEDMASK, CMND_I2CSCAN, CMND_SERIALSEND, CMND_BAUDRATE, CMND_SERIALDELIMITER, CMND_DRIVER }; const char kTasmotaCommands[] PROGMEM = - D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_FANSPEED "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" + D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_STATE "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SENSOR "|" D_CMND_SAVEDATA "|" D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|" D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|" D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_COUNTER "|" D_CMND_COUNTERTYPE "|" @@ -151,6 +151,7 @@ uint8_t leds_present = 0; // Max number of LED supported uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t led_power = 0; // LED power state uint8_t ledlnk_inverted = 0; // Link LED inverted flag (1 = (0 = On, 1 = Off)) +uint8_t buzzer_inverted = 0; // Buzzer inverted flag (1 = (0 = On, 1 = Off)) uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) uint8_t counter_no_pullup = 0; // Counter input pullup flag (1 = No pullup) uint8_t energy_flg = 0; // Energy monitor configured @@ -163,6 +164,7 @@ 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 buzzer_count = 0; // Number of buzzes //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 @@ -430,35 +432,6 @@ void SetLedLink(uint8_t state) } } -uint8_t GetFanspeed(void) -{ - uint8_t fanspeed = 0; - -// if (SONOFF_IFAN02 == my_module_type) { - /* Fanspeed is controlled by relay 2, 3 and 4 as in Sonoff 4CH. - 000x = 0 - 001x = 1 - 011x = 2 - 101x = 3 - */ - fanspeed = (uint8_t)(power &0xF) >> 1; - if (fanspeed) { fanspeed = (fanspeed >> 1) +1; } -// } - return fanspeed; -} - -void SetFanspeed(uint8_t fanspeed) -{ - for (uint32_t i = 0; i < MAX_FAN_SPEED -1; i++) { - uint8_t state = kIFan02Speed[fanspeed][i]; -// uint8_t state = pgm_read_byte(kIFan02Speed +(speed *3) +i); - ExecuteCommandPower(i +2, state, SRC_IGNORE); // Use relay 2, 3 and 4 - } -#ifdef USE_DOMOTICZ - DomoticzUpdateFanState(); // Command FanSpeed feedback -#endif // USE_DOMOTICZ -} - void SetPulseTimer(uint8_t index, uint16_t time) { pulse_timer[index] = (time > 111) ? millis() + (1000 * (time - 100)) : (time > 0) ? millis() + (100 * time) : 0L; @@ -636,22 +609,6 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len) fallback_topic_flag = false; return; } - else if ((CMND_FANSPEED == command_code) && (SONOFF_IFAN02 == my_module_type)) { - if (data_len > 0) { - if ('-' == dataBuf[0]) { - payload = (int16_t)GetFanspeed() -1; - if (payload < 0) { payload = MAX_FAN_SPEED -1; } - } - else if ('+' == dataBuf[0]) { - payload = GetFanspeed() +1; - if (payload > MAX_FAN_SPEED -1) { payload = 0; } - } - } - if ((payload >= 0) && (payload < MAX_FAN_SPEED) && (payload != GetFanspeed())) { - SetFanspeed(payload); - } - Response_P(S_JSON_COMMAND_NVALUE, command, GetFanspeed()); - } else if (CMND_STATUS == command_code) { if ((payload < 0) || (payload > MAX_STATUS)) payload = 99; PublishStatus(payload); @@ -1675,7 +1632,7 @@ void ExecuteCommandPower(uint8_t device, uint8_t state, int source) // ShowSource(source); - if (SONOFF_IFAN02 == my_module_type) { + if (IsModuleIfan()) { blink_mask &= 1; // No blinking on the fan relays Settings.flag.interlock = 0; // No interlock mode as it is already done by the microcontroller Settings.pulse_timer[1] = 0; // No pulsetimers on the fan relays @@ -1826,7 +1783,7 @@ void PublishStatus(uint8_t payload) if ((0 == payload) || (99 == payload)) { uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; - if (SONOFF_IFAN02 == my_module_type) { maxfn = 1; } + if (IsModuleIfan()) { maxfn = 1; } stemp[0] = '\0'; for (uint32_t i = 0; i < maxfn; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), Settings.friendlyname[i]); @@ -1965,7 +1922,7 @@ void MqttShowState(void) } else { #endif ResponseAppend_P(PSTR(",\"%s\":\"%s\""), GetPowerDevice(stemp1, i +1, sizeof(stemp1), Settings.flag.device_index_enable), GetStateText(bitRead(power, i))); - if (SONOFF_IFAN02 == my_module_type) { + if (IsModuleIfan()) { ResponseAppend_P(PSTR(",\"" D_CMND_FANSPEED "\":%d"), GetFanspeed()); break; } @@ -2144,6 +2101,16 @@ void Every100mSeconds(void) if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; } } } + + if ((pin[GPIO_BUZZER] < 99) && (Settings.flag3.buzzer_enable)) { + if (buzzer_count) { + buzzer_count--; + uint8_t state = buzzer_count & 1; + digitalWrite(pin[GPIO_BUZZER], (buzzer_inverted) ? !state : state); + } + } else { + buzzer_count = 0; + } } /*-------------------------------------------------------------------------------------------*\ @@ -2609,6 +2576,10 @@ void GpioInit(void) ledlnk_inverted = 1; mpin -= (GPIO_LEDLNK_INV - GPIO_LEDLNK); } + else if (mpin == GPIO_BUZZER_INV) { + buzzer_inverted = 1; + mpin -= (GPIO_BUZZER_INV - GPIO_BUZZER); + } else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) { bitSet(pwm_inverted, mpin - GPIO_PWM1_INV); mpin -= (GPIO_PWM1_INV - GPIO_PWM1); @@ -2742,6 +2713,10 @@ void GpioInit(void) pinMode(pin[GPIO_LEDLNK], OUTPUT); digitalWrite(pin[GPIO_LEDLNK], ledlnk_inverted); } + if (pin[GPIO_BUZZER] < 99) { + pinMode(pin[GPIO_BUZZER], OUTPUT); + digitalWrite(pin[GPIO_BUZZER], buzzer_inverted); // Buzzer Off + } ButtonInit(); SwitchInit(); diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h index 631d2e712..a99b3bce9 100644 --- a/sonoff/sonoff_template.h +++ b/sonoff/sonoff_template.h @@ -184,6 +184,8 @@ enum UserSelectablePins { GPIO_LEDLNK, // Link led GPIO_LEDLNK_INV, // Inverted link led GPIO_ARIRFSEL, // Arilux RF Receive input selected + GPIO_BUZZER, // Buzzer + GPIO_BUZZER_INV, // Inverted buzzer GPIO_SENSOR_END }; // Programmer selectable GPIO functionality @@ -251,6 +253,7 @@ const char kSensorNames[] PROGMEM = D_SENSOR_ADE7953_IRQ "|" D_SENSOR_LED_LINK "|" D_SENSOR_LED_LINK "i|" D_SENSOR_ARIRFSEL "|" + D_SENSOR_BUZZER "|" D_SENSOR_BUZZER "i|" ; // User selectable ADC0 functionality @@ -353,6 +356,7 @@ enum SupportedModules { WAGA, SYF05, SONOFF_L1, + SONOFF_IFAN03, MAXMODULE}; #define USER_MODULE 255 @@ -476,6 +480,8 @@ const uint8_t kGpioNiceList[] PROGMEM = { GPIO_CNTR4, GPIO_CNTR4_NP, #endif + GPIO_BUZZER, // Buzzer + GPIO_BUZZER_INV, // Inverted buzzer GPIO_TXD, // Serial interface GPIO_RXD, // Serial interface #ifdef USE_I2C @@ -664,6 +670,7 @@ const uint8_t kModuleNiceList[] PROGMEM = { SLAMPHER, SONOFF_SC, // Sonoff Environmemtal Sensor SONOFF_IFAN02, // Sonoff Fan + SONOFF_IFAN03, SONOFF_BRIDGE, // Sonoff Bridge SONOFF_SV, // Sonoff Development Devices SONOFF_DEV, @@ -2002,6 +2009,25 @@ const mytmplt kModules[MAXMODULE] PROGMEM = { GPIO_USER, GPIO_USER, 0 + }, + { "Sonoff iFan03", // Sonoff iFan03 (ESP8285) + GPIO_KEY1, // GPIO00 WIFI_KEY0 Virtual button 1 as feedback from RC + GPIO_TXD, // GPIO01 ESP_TXD Serial RXD and Optional sensor + 0, // GPIO02 ESP_LOG + GPIO_RXD, // GPIO03 ESP_RXD Serial TXD and Optional sensor + 0, // GPIO04 DEBUG_RX + 0, // GPIO05 DEBUG_TX + // GPIO06 (SD_CLK Flash) + // GPIO07 (SD_DATA0 Flash QIO/DIO/DOUT) + // GPIO08 (SD_DATA1 Flash QIO/DIO/DOUT) + GPIO_REL1_INV, // GPIO09 WIFI_O0 Relay 1 (0 = Off, 1 = On) controlling the light + GPIO_BUZZER_INV, // GPIO10 WIFI_O4 Buzzer (0 = Off, 1 = On) + // GPIO11 (SD_CMD Flash) + GPIO_REL3, // GPIO12 WIFI_O2 Relay 3 (0 = Off, 1 = On) controlling the fan + GPIO_LED1_INV, // GPIO13 WIFI_CHK Blue Led on PCA (0 = On, 1 = Off) - Link and Power status + GPIO_REL2, // GPIO14 WIFI_O1 Relay 2 (0 = Off, 1 = On) controlling the fan + GPIO_REL4, // GPIO15 WIFI_O3 Relay 4 (0 = Off, 1 = On) controlling the fan + 0, 0 } }; diff --git a/sonoff/sonoff_version.h b/sonoff/sonoff_version.h index 0a9aa8dc3..d35c27fa9 100644 --- a/sonoff/sonoff_version.h +++ b/sonoff/sonoff_version.h @@ -20,6 +20,6 @@ #ifndef _SONOFF_VERSION_H_ #define _SONOFF_VERSION_H_ -const uint32_t VERSION = 0x06060001; +const uint32_t VERSION = 0x06060002; #endif // _SONOFF_VERSION_H_ diff --git a/sonoff/xdrv_01_webserver.ino b/sonoff/xdrv_01_webserver.ino index 2ce8595cc..2ab583218 100644 --- a/sonoff/xdrv_01_webserver.ino +++ b/sonoff/xdrv_01_webserver.ino @@ -942,9 +942,9 @@ void HandleRoot(void) #endif WSContentSend_P(HTTP_TABLE100); WSContentSend_P(PSTR("")); - if (SONOFF_IFAN02 == my_module_type) { + if (IsModuleIfan()) { WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, ""); - for (uint32_t i = 0; i < MAX_FAN_SPEED; i++) { + for (uint32_t i = 0; i < MaxFanspeed(); i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i); WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, stemp, ""); } @@ -1007,7 +1007,7 @@ bool HandleRootStatusRefresh(void) if (strlen(tmp)) { ShowWebSource(SRC_WEBGUI); uint8_t device = atoi(tmp); - if (SONOFF_IFAN02 == my_module_type) { + if (IsModuleIfan()) { if (device < 2) { ExecuteCommandPower(1, POWER_TOGGLE, SRC_IGNORE); } else { @@ -1042,7 +1042,7 @@ bool HandleRootStatusRefresh(void) if (devices_present) { WSContentSend_P(PSTR("{t}")); uint8_t fsize = (devices_present < 5) ? 70 - (devices_present * 8) : 32; - if (SONOFF_IFAN02 == my_module_type) { + if (IsModuleIfan()) { WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(power, 0))); uint8_t fanspeed = GetFanspeed(); snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed); @@ -1573,7 +1573,7 @@ void HandleOtherConfiguration(void) WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", (Settings.flag.mqtt_enabled) ? " checked" : ""); uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present; - if (SONOFF_IFAN02 == my_module_type) { maxfn = 1; } + if (IsModuleIfan()) { maxfn = 1; } for (uint32_t i = 0; i < maxfn; i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i +1); WSContentSend_P(PSTR("" D_FRIENDLY_NAME " %d (" FRIENDLY_NAME "%s)

"), @@ -1752,7 +1752,7 @@ void HandleInformation(void) WSContentSend_P(PSTR("}1" D_BOOT_COUNT "}2%d"), Settings.bootcount); WSContentSend_P(PSTR("}1" D_RESTART_REASON "}2%s"), GetResetReason().c_str()); uint8_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : devices_present; - if (SONOFF_IFAN02 == my_module_type) { maxfn = 1; } + if (IsModuleIfan()) { maxfn = 1; } for (uint32_t i = 0; i < maxfn; i++) { WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, Settings.friendlyname[i]); } diff --git a/sonoff/xdrv_02_mqtt.ino b/sonoff/xdrv_02_mqtt.ino index 0269f378d..77e8e1876 100644 --- a/sonoff/xdrv_02_mqtt.ino +++ b/sonoff/xdrv_02_mqtt.ino @@ -309,8 +309,8 @@ void MqttPublishPowerState(uint8_t device) if ((device < 1) || (device > devices_present)) { device = 1; } - if ((SONOFF_IFAN02 == my_module_type) && (device > 1)) { - if (GetFanspeed() < MAX_FAN_SPEED) { // 4 occurs when fanspeed is 3 and RC button 2 is pressed + if (IsModuleIfan() && (device > 1)) { + if (GetFanspeed() < MaxFanspeed()) { // 4 occurs when fanspeed is 3 and RC button 2 is pressed #ifdef USE_DOMOTICZ DomoticzUpdateFanState(); // RC Button feedback #endif // USE_DOMOTICZ @@ -335,7 +335,7 @@ void MqttPublishAllPowerState() { for (uint32_t i = 1; i <= devices_present; i++) { MqttPublishPowerState(i); - if (SONOFF_IFAN02 == my_module_type) { break; } // Report status of light relay only + if (IsModuleIfan()) { break; } // Report status of light relay only } } diff --git a/sonoff/xdrv_07_domoticz.ino b/sonoff/xdrv_07_domoticz.ino index c79710081..7519e677b 100644 --- a/sonoff/xdrv_07_domoticz.ino +++ b/sonoff/xdrv_07_domoticz.ino @@ -101,7 +101,7 @@ void MqttPublishDomoticzPowerState(uint8_t device) if (Settings.flag.mqtt_enabled) { if ((device < 1) || (device > devices_present)) { device = 1; } if (Settings.domoticz_relay_idx[device -1]) { - if ((SONOFF_IFAN02 == my_module_type) && (device > 1)) { + if (IsModuleIfan() && (device > 1)) { // Fan handled by MqttPublishDomoticzFanState } else { char svalue[8]; // Dimmer value @@ -129,7 +129,7 @@ void DomoticzMqttUpdate(void) if (domoticz_update_timer <= 0) { domoticz_update_timer = Settings.domoticz_update_timer; for (uint32_t i = 1; i <= devices_present; i++) { - if ((SONOFF_IFAN02 == my_module_type) && (i > 1)) { + if (IsModuleIfan() && (i > 1)) { MqttPublishDomoticzFanState(); break; } else { @@ -214,7 +214,7 @@ bool DomoticzMqttData(void) if (idx == Settings.domoticz_relay_idx[i]) { bool iscolordimmer = strcmp_P(domoticz["dtype"],PSTR("Color Switch")) == 0; snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), i +1); - if ((SONOFF_IFAN02 == my_module_type) && (1 == i)) { // Idx 2 is fanspeed + if (IsModuleIfan() && (1 == i)) { // Idx 2 is fanspeed uint8_t svalue = 0; if (domoticz.containsKey("svalue1")) { svalue = domoticz["svalue1"]; @@ -472,7 +472,7 @@ void HandleDomoticzConfiguration(void) WSContentSend_P(HTTP_FORM_DOMOTICZ_SWITCH, i +1, i, Settings.domoticz_switch_idx[i]); } - if ((SONOFF_IFAN02 == my_module_type) && (1 == i)) { break; } + if (IsModuleIfan() && (1 == i)) { break; } } for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) { WSContentSend_P(HTTP_FORM_DOMOTICZ_SENSOR, diff --git a/sonoff/xdrv_22_sonoff_ifan.ino b/sonoff/xdrv_22_sonoff_ifan.ino new file mode 100644 index 000000000..1d8066b09 --- /dev/null +++ b/sonoff/xdrv_22_sonoff_ifan.ino @@ -0,0 +1,329 @@ +/* + xdrv_22_sonoff_ifan.ino - sonoff iFan02 and iFan03 support for Sonoff-Tasmota + + Copyright (C) 2019 Theo Arends + + 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 . +*/ + +/*********************************************************************************************\ + Sonoff iFan02 and iFan03 +\*********************************************************************************************/ + +#define XDRV_22 22 + +const uint8_t MAX_FAN_SPEED = 4; // Max number of iFan02 fan speeds (0 .. 3) + +const uint8_t kIFan02Speed[MAX_FAN_SPEED][3] = {{6,6,6}, {7,6,6}, {7,7,6}, {7,6,7}}; // Do not use PROGMEM as it fails + +const uint8_t kIFan03Speed[MAX_FAN_SPEED +2][3] = {{6,6,6}, {7,6,6}, {7,7,6}, {6,6,7}, {7,6,7}, {6,7,7}}; // Do not use PROGMEM as it fails + +uint8_t ifan_fanspeed_timer = 0; +uint8_t ifan_fanspeed_goal = 0; +bool ifan_receive_flag = false; + +/*********************************************************************************************/ + +bool IsModuleIfan() +{ + return ((SONOFF_IFAN02 == my_module_type) || (SONOFF_IFAN03 == my_module_type)); +} + +uint8_t MaxFanspeed(void) +{ + return MAX_FAN_SPEED; +} + +uint8_t GetFanspeed(void) +{ + if (ifan_fanspeed_timer) { + return ifan_fanspeed_goal; + } else { + uint8_t fanspeed = 0; + +// if (SONOFF_IFAN02 == my_module_type) { + /* 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) + */ + fanspeed = (uint8_t)(power &0xF) >> 1; + if (fanspeed) { fanspeed = (fanspeed >> 1) +1; } // 0, 1, 2, 3 +// } + return fanspeed; + } +} + +void SetFanspeed(uint8_t fanspeed) +{ + if (SONOFF_IFAN02 == my_module_type) { + for (uint32_t i = 0; i < 3; i++) { + uint8_t state = kIFan02Speed[fanspeed][i]; + ExecuteCommandPower(i +2, state, SRC_IGNORE); // Use relay 2, 3 and 4 + } + +#ifdef USE_DOMOTICZ + DomoticzUpdateFanState(); // Command FanSpeed feedback +#endif // USE_DOMOTICZ + } + else if (SONOFF_IFAN03 == my_module_type) { + SonoffIFanSetFanspeed(fanspeed, true); + } +} + +/*********************************************************************************************/ + +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; } + + // Change to lookup table + if (sequence) { + if (0 == fanspeed_now) { + switch (fanspeed) { + case 1: + case 3: + fanspeed = 2; + ifan_fanspeed_timer = 20; + break; + } + } + if (1 == fanspeed_now) { + switch (fanspeed) { + case 3: + fanspeed = 4; + ifan_fanspeed_timer = 2; + break; + } + } + if (2 == fanspeed_now) { + switch (fanspeed) { + case 0: + fanspeed = 1; + ifan_fanspeed_timer = 2; + break; + case 3: + fanspeed = 5; + ifan_fanspeed_timer = 2; + break; + } + } + if (3 == fanspeed_now) { + switch (fanspeed) { + case 0: + case 1: + fanspeed = 4; + ifan_fanspeed_timer = 2; + break; + case 2: + fanspeed = 5; + ifan_fanspeed_timer = 2; + break; + } + } +// if (fanspeed == ifan_fanspeed_goal) { +// sequence = false; +// } + } + + for (uint32_t i = 0; i < 3; i++) { + uint8_t state = kIFan03Speed[fanspeed][i]; + ExecuteCommandPower(i +2, state, SRC_IGNORE); // Use relay 2, 3 and 4 + } + +#ifdef USE_DOMOTICZ + if (sequence) { DomoticzUpdateFanState(); } // Command FanSpeed feedback +#endif // USE_DOMOTICZ +} + +/*********************************************************************************************/ + +void SonoffIfanSendAck() +{ + // AA 55 01 04 00 00 05 + serial_in_buffer[5] = 0; + uint8_t crc = 0; + for (uint32_t i = 2; i < 5; i++) { + crc += serial_in_buffer[i]; + } + serial_in_buffer[6] = crc; + for (uint32_t i = 0; i < 7; i++) { + Serial.write(serial_in_buffer[i]); + } +} + +void SonoffIfanReceived(void) +{ + char svalue[32]; + + uint8_t mode = serial_in_buffer[3]; + uint8_t action = serial_in_buffer[6]; + + 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); + buzzer_count = 2; // Beep once + } + } 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 + Settings.flag3.buzzer_enable = !Settings.flag3.buzzer_enable; // SetOption67 + } + if (7 == mode) { + // AA 55 01 07 00 01 01 0A - Rf long press - forget RF codes + buzzer_count = 6; // Beep three times + } + SonoffIfanSendAck(); +} + +bool SonoffIfanSerialInput(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + if (0xAA == serial_in_byte) { // 0xAA - Start of text + serial_in_byte_counter = 0; + ifan_receive_flag = true; + } + if (ifan_receive_flag) { + // 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 + serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; + if (serial_in_byte_counter == 8) { + + AddLogSerial(LOG_LEVEL_DEBUG); + + uint8_t crc = 0; + for (uint32_t i = 2; i < 7; i++) { + crc += serial_in_buffer[i]; + } + if (crc == serial_in_buffer[7]) { + SonoffIfanReceived(); + ifan_receive_flag = false; + return true; + } + } + serial_in_byte = 0; + } + return false; + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +enum SonoffIfanCommands { CMND_FANSPEED }; +const char kSonoffIfanCommands[] PROGMEM = D_CMND_FANSPEED; + +bool SonoffIfanCommand(void) +{ + char command [CMDSZ]; + bool serviced = true; + + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic, kSonoffIfanCommands); + if (-1 == command_code) { + serviced = false; // Unknown command + } + else if (CMND_FANSPEED == command_code) { + 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; } + } + else if ('+' == XdrvMailbox.data[0]) { + XdrvMailbox.payload = GetFanspeed() +1; + if (XdrvMailbox.payload > MAX_FAN_SPEED -1) { XdrvMailbox.payload = 0; } + } + } + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < MAX_FAN_SPEED)) { + SetFanspeed(XdrvMailbox.payload); + } + Response_P(S_JSON_COMMAND_NVALUE, command, GetFanspeed()); + } else serviced = false; // Unknown command + + return serviced; +} + +/*********************************************************************************************/ + +bool SonoffIfanInit(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + Settings.flag.mqtt_serial = 0; + baudrate = 9600; + SetSeriallog(LOG_LEVEL_NONE); + } + return false; // Continue init chain +} + +void SonoffIfanUpdate(void) +{ + if (SONOFF_IFAN03 == my_module_type) { + if (ifan_fanspeed_timer) { + ifan_fanspeed_timer--; + if (!ifan_fanspeed_timer) { + SonoffIFanSetFanspeed(ifan_fanspeed_goal, false); + } + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv22(uint8_t function) +{ + bool result = false; + + if (IsModuleIfan()) { + switch (function) { + case FUNC_MODULE_INIT: + result = SonoffIfanInit(); + break; + case FUNC_EVERY_250_MSECOND: + SonoffIfanUpdate(); + break; + case FUNC_COMMAND: + result = SonoffIfanCommand(); + break; + case FUNC_SERIAL: + result = SonoffIfanSerialInput(); + break; + } + } + return result; +}