From 6031e5df98936e84e5547d4fb69421dde616ffcd Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 9 Jul 2022 12:27:59 +0200 Subject: [PATCH] Add better support for bistable (latching) relays Add support for bistable (latching) relays mixed with monostable relays using GPIO Relay_b or Relay_bi --- CHANGELOG.md | 1 + RELEASENOTES.md | 1 + tasmota/include/tasmota_template.h | 11 ++++ tasmota/my_user_config.h | 3 + tasmota/tasmota.ino | 3 + tasmota/tasmota_support/support_tasmota.ino | 73 +++++++++++++++++++-- 6 files changed, 87 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1a260d6..08c835494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [12.0.2.3] ### Added - Support for Sonoff POWR3xxD and THR3xxD (#15856) +- Support for bistable (latching) relays mixed with monostable relays using GPIO Relay_b or Relay_bi ### Changed - Driver DHT v6 consolidation for both ESP8266 and ESP32 to support SI7021, THS01 and MS01 on ESP32 (#15856) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 833ce1892..b0b8156cf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -114,6 +114,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo - Command ``GlobalPress2 1..250`` to select Global Pressure source indexed from teleperiod occurance data [#15834](https://github.com/arendst/Tasmota/issues/15834) - Support for 5-channel light dimmer driver SM2335 used in SwitchBot Color Bulbs [#15839](https://github.com/arendst/Tasmota/issues/15839) - Support for Sonoff POWR3xxD and THR3xxD [#15856](https://github.com/arendst/Tasmota/issues/15856) +- Support for bistable (latching) relays mixed with monostable relays using GPIO Relay_b or Relay_bi ### Breaking Changed diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h index b833a8b0d..5920db14a 100644 --- a/tasmota/include/tasmota_template.h +++ b/tasmota/include/tasmota_template.h @@ -192,8 +192,12 @@ enum UserSelectablePins { GPIO_SM2335_CLK, GPIO_SM2335_DAT, // SM2335 PWM controller GPIO_MP3_DFR562_BUSY, // RB-DFR-562, DFPlayer Mini MP3 Player busy flag GPIO_TM1621_CS, GPIO_TM1621_WR, GPIO_TM1621_RD, GPIO_TM1621_DAT, // Sonoff POWR3xxD and THR3xxD LCD display + GPIO_REL1_BI, GPIO_REL1_BI_INV, // 8 x Relays bistable GPIO_SENSOR_END }; +// Error as warning to rethink GPIO usage +static_assert(GPIO_SENSOR_END < 2000, "Too many UserSelectablePins"); + enum ProgramSelectablePins { GPIO_FIX_START = 2046, GPIO_USER, // User configurable needs to be 2047 @@ -428,6 +432,7 @@ const char kSensorNames[] PROGMEM = D_SENSOR_SM2335_CLK "|" D_SENSOR_SM2335_DAT "|" D_SENSOR_DFR562_BUSY "|" D_GPIO_TM1621_CS "|" D_GPIO_TM1621_WR "|" D_GPIO_TM1621_RD "|" D_GPIO_TM1621_DAT "|" + D_SENSOR_RELAY "_b|" D_SENSOR_RELAY "_bi|" ; const char kSensorNamesFixed[] PROGMEM = @@ -471,6 +476,12 @@ const uint16_t kGpioNiceList[] PROGMEM = { #endif AGPIO(GPIO_REL1) + MAX_RELAYS, // Relays AGPIO(GPIO_REL1_INV) + MAX_RELAYS, + +#ifdef USE_BISTABLE_RELAY_SUPPORT + AGPIO(GPIO_REL1_BI) + MAX_RELAYS, // Bistable (Latching) two coil relays + AGPIO(GPIO_REL1_BI_INV) + MAX_RELAYS, +#endif + AGPIO(GPIO_LED1) + MAX_LEDS, // Leds AGPIO(GPIO_LED1_INV) + MAX_LEDS, #ifdef USE_COUNTER diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 2e25186a1..9e0dde67a 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -397,6 +397,9 @@ //#define MY_LANGUAGE zh_CN // Chinese (Simplified) in China //#define MY_LANGUAGE zh_TW // Chinese (Traditional) in Taiwan +// -- Basic features ------------------------------ +#define USE_BISTABLE_RELAY_SUPPORT // Add support for bistable (latching) relays using GPIO Relay_b or Relay_bi + // -- Wifi Config tools --------------------------- #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 13 as used by Wi-Fi Manager web GUI #define USE_IMPROV // Add support for IMPROV serial protocol as used by esp-web-tools (+2k code) diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index fafd1d936..79e8aa697 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -221,6 +221,9 @@ struct TasmotaGlobal_t { power_t power; // Current copy of Settings->power power_t rel_inverted; // Relay inverted flag (1 = (0 = On, 1 = Off)) +#ifdef USE_BISTABLE_RELAY_SUPPORT + power_t rel_bistable; // Relay bistable bitmap +#endif // USE_BISTABLE_RELAY_SUPPORT power_t last_power; // Last power set state power_t blink_power; // Blink power state power_t blink_powersave; // Blink start power save state diff --git a/tasmota/tasmota_support/support_tasmota.ino b/tasmota/tasmota_support/support_tasmota.ino index 3b7bce95c..d5325e46e 100644 --- a/tasmota/tasmota_support/support_tasmota.ino +++ b/tasmota/tasmota_support/support_tasmota.ino @@ -221,8 +221,7 @@ void ZeroCrossInit(uint32_t offset) { /********************************************************************************************/ -void SetLatchingRelay(power_t lpower, uint32_t state) -{ +void SetLatchingRelay(power_t lpower, uint32_t state) { // TasmotaGlobal.power xx00 - toggle REL1 (Off) and REL3 (Off) - device 1 Off, device 2 Off // TasmotaGlobal.power xx01 - toggle REL2 (On) and REL3 (Off) - device 1 On, device 2 Off // TasmotaGlobal.power xx10 - toggle REL1 (Off) and REL4 (On) - device 1 Off, device 2 On @@ -240,8 +239,17 @@ void SetLatchingRelay(power_t lpower, uint32_t state) } } -void SetDevicePower(power_t rpower, uint32_t source) -{ +#ifdef USE_BISTABLE_RELAY_SUPPORT +void ResetBistableRelays(void) { + for (uint32_t i = 0; i < MAX_RELAYS; i++) { + if (bitRead(TasmotaGlobal.rel_bistable, i)) { + DigitalWrite(GPIO_REL1, i, bitRead(TasmotaGlobal.rel_inverted, i) ? 1 : 0); + } + } +} +#endif // USE_BISTABLE_RELAY_SUPPORT + +void SetDevicePower(power_t rpower, uint32_t source) { ShowSource(source); TasmotaGlobal.last_source = source; @@ -298,6 +306,26 @@ void SetDevicePower(power_t rpower, uint32_t source) { ZeroCrossMomentStart(); +#ifdef USE_BISTABLE_RELAY_SUPPORT + uint32_t port = 0; + uint32_t port_next; + for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) { + power_t state = rpower &1; + + port_next = 1; // Select next relay + if (bitRead(TasmotaGlobal.rel_bistable, port)) { + if (!state) { port_next = 2; } // Skip highest relay + TasmotaGlobal.latching_relay_pulse = 2; // max 200mS (initiated by stateloop()) + port += state; // Relay = Off, Relay = On + state = 1; // Set pulse + } + if (i < MAX_RELAYS) { + DigitalWrite(GPIO_REL1, port, bitRead(TasmotaGlobal.rel_inverted, port) ? !state : state); + } + port += port_next; // Select next relay + rpower >>= 1; + } +#else for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) { power_t state = rpower &1; if (i < MAX_RELAYS) { @@ -305,6 +333,7 @@ void SetDevicePower(power_t rpower, uint32_t source) } rpower >>= 1; } +#endif // USE_BISTABLE_RELAY_SUPPORT ZeroCrossMomentEnd(); } @@ -1125,7 +1154,16 @@ void Every100mSeconds(void) if (TasmotaGlobal.latching_relay_pulse) { TasmotaGlobal.latching_relay_pulse--; - if (!TasmotaGlobal.latching_relay_pulse) SetLatchingRelay(0, 0); + if (!TasmotaGlobal.latching_relay_pulse) { +#ifdef ESP8266 + if (EXS_RELAY == TasmotaGlobal.module_type) { + SetLatchingRelay(0, 0); + } +#endif // ESP8266 +#ifdef USE_BISTABLE_RELAY_SUPPORT + ResetBistableRelays(); +#endif // USE_BISTABLE_RELAY_SUPPORT + } } if (TasmotaGlobal.skip_sleep) { @@ -1969,6 +2007,19 @@ void GpioInit(void) bitSet(TasmotaGlobal.rel_inverted, mpin - AGPIO(GPIO_REL1_INV)); mpin -= (AGPIO(GPIO_REL1_INV) - AGPIO(GPIO_REL1)); } + +#ifdef USE_BISTABLE_RELAY_SUPPORT + else if ((mpin >= AGPIO(GPIO_REL1_BI)) && (mpin < (AGPIO(GPIO_REL1_BI) + MAX_RELAYS))) { + bitSet(TasmotaGlobal.rel_bistable, mpin - AGPIO(GPIO_REL1_BI)); + mpin -= (AGPIO(GPIO_REL1_BI) - AGPIO(GPIO_REL1)); + } + else if ((mpin >= AGPIO(GPIO_REL1_BI_INV)) && (mpin < (AGPIO(GPIO_REL1_BI_INV) + MAX_RELAYS))) { + bitSet(TasmotaGlobal.rel_bistable, mpin - AGPIO(GPIO_REL1_BI_INV)); + bitSet(TasmotaGlobal.rel_inverted, mpin - AGPIO(GPIO_REL1_BI_INV)); + mpin -= (AGPIO(GPIO_REL1_BI_INV) - AGPIO(GPIO_REL1)); + } +#endif // USE_BISTABLE_RELAY_SUPPORT + else if ((mpin >= AGPIO(GPIO_LED1_INV)) && (mpin < (AGPIO(GPIO_LED1_INV) + MAX_LEDS))) { bitSet(TasmotaGlobal.led_inverted, mpin - AGPIO(GPIO_LED1_INV)); mpin -= (AGPIO(GPIO_LED1_INV) - AGPIO(GPIO_LED1)); @@ -2163,6 +2214,10 @@ void GpioInit(void) GpioInitPwm(); +#ifdef USE_BISTABLE_RELAY_SUPPORT + uint32_t bi_device = 0; +#endif // USE_BISTABLE_RELAY_SUPPORT + for (uint32_t i = 0; i < MAX_RELAYS; i++) { if (PinUsed(GPIO_REL1, i)) { TasmotaGlobal.devices_present++; @@ -2171,6 +2226,14 @@ void GpioInit(void) if (i &1) { TasmotaGlobal.devices_present--; } } #endif // ESP8266 + +#ifdef USE_BISTABLE_RELAY_SUPPORT + if (bitRead(TasmotaGlobal.rel_bistable, i)) { + if (bi_device &1) { TasmotaGlobal.devices_present--; } + bi_device++; + } +#endif // USE_BISTABLE_RELAY_SUPPORT + } }