MOD-IO2: Tasmota driver (relays only)

Started by Peter G., October 22, 2021, 07:21:25 PM

Previous topic - Next topic

Peter G.

I had the need for such driver and now I have one and it is working for me. Maybe it is helpful for others too and as I have the permission to publish it I am doing it here.

As mentioned in the subject it just implements the commands for the relays to work with Tasmota. I can not support it, so it is really as it is!

Cheers

Hm, file upload is not allowed?
2021-10-22 - firmware - MOD-IO2 (only relays).patch
CHANGELOG.md               |   4 +
 I2CDEVICES.md              |   7 +-
 tasmota/settings.h         |   3 +-
 tasmota/tasmota.h          |   1 +
 tasmota/xdrv_85_modio2.ino | 217 +++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 230 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b819e733..d55a8c63e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
 # Changelog
 All notable changes to this project will be documented in this file.
 
+## [9.6.0.1] 20211007
+### Added
+- Initial support for MOD-IO2 I2C I/O Expander (currently relay outputs only)
+
 ## [Released]
 
 ## [9.5.0] 20210617
diff --git a/I2CDEVICES.md b/I2CDEVICES.md
index 84dac460b..46b9dbf80 100644
--- a/I2CDEVICES.md
+++ b/I2CDEVICES.md
@@ -92,4 +92,9 @@ Index | Define              | Driver  | Device   | Address(es) | Description
   56  | USE_SEESAW_SOIL     | xsns_81 | SEESOIL  | 0x36 - 0x39 | Adafruit seesaw soil moisture sensor
   57  | USE_TOF10120        | xsns_84 | TOF10120 | 0x52        | Time-of-flight (ToF) distance sensor
   58  | USE_MPU_ACCEL       | xsns_85 | MPU_ACCEL| 0x68        | MPU6886/MPU9250 6-axis MotionTracking sensor from M5Stack
-  59  | USE_BM8563          | xdrv_56 | BM8563   | 0x51        | BM8563 RTC from M5Stack
\ No newline at end of file
+  59  | USE_BM8563          | xdrv_56 | BM8563   | 0x51        | BM8563 RTC from M5Stack
+  60  | USE_AM2320          | xsns_88 | AM2320   | 0x5C        | Temperature and Humidity sensor
+  61  | USE_T67XX           | xsns_89 | T67XX    | 0x15        | CO2 sensor
+  62  | USE_SCD40           | xsns_92 | SCD40    | 0x62        | CO2 sensor Sensirion SCD40/SCD41
+  63  | USE_HM330X          | xsns_93 | HM330X   | 0x40        | Particule sensor
+  64  | USE_MODIO2          | xdrv_85 | MOD-IO2  | Any         | 2-channel relay expander
\ No newline at end of file
diff --git a/tasmota/settings.h b/tasmota/settings.h
index 075759b71..dc576adad 100644
--- a/tasmota/settings.h
+++ b/tasmota/settings.h
@@ -736,8 +736,9 @@ typedef struct {
   uint8_t       shd_leading_edge;          // F5B
   uint16_t      shd_warmup_brightness;     // F5C
   uint8_t       shd_warmup_time;           // F5E
+  uint8_t       modio2_config[MAX_MODIO2];          // F5F
 
-  uint8_t       free_f5f[65];              // F5F - Decrement if adding new Setting variables just above and below
+  uint8_t       free_f6D[51];              // F6D - Decrement if adding new Setting variables just above and below
 
   // Only 32 bit boundary variables below
 
diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h
index a76403d7d..ca9833917 100644
--- a/tasmota/tasmota.h
+++ b/tasmota/tasmota.h
@@ -80,6 +80,7 @@ const uint8_t MAX_PCF8574 = 4;              // Max number of PCF8574 devices
 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 uint16_t VL53L0X_MAX_SENSORS = 8;     // Max number of VL53L0X sensors
+const uint8_t MAX_MODIO2 = 14;              // Max number of MOD-IO2 devices
 
 #ifdef ESP32
 const uint8_t MAX_I2C = 2;                  // Max number of I2C controllers (ESP32 = 2)
diff --git a/tasmota/xdrv_85_modio2.ino b/tasmota/xdrv_85_modio2.ino
new file mode 100644
index 000000000..0fdedb176
--- /dev/null
+++ b/tasmota/xdrv_85_modio2.ino
@@ -0,0 +1,217 @@
+/*
+  xdrv_85_modio2.ino - MOD-IO2 I2C support for Tasmota
+
+  Copyright (C) 2021  Power Void
+
+  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/>.
+*/
+
+#ifdef USE_I2C
+#ifdef USE_MODIO2
+/*********************************************************************************************\
+ * MODIO2 - I2C 2-channel relay expander
+ *
+ * I2C Address: Any
+\*********************************************************************************************/
+
+#define XDRV_85           85
+#define XI2C_64           64    // See I2CDEVICES.md
+
+#define MODIO2_ADDR_1ST      0x00    /** 1st I2C address of board model **/
+#define MODIO2_I2C_ID_REG    0x20    /** Board model specific ID register **/
+#define MODIO2_I2C_ID_VAL    0x23    /** Board model specific ID register value **/
+#define MODIO2_I2C_FW_REG    0x21    /** Get firmware version **/
+#define MODIO2_I2C_STATE_REG 0x43    /** Get current relay state **/
+#define MODIO2_I2C_RONOFF_REG 0x40   /** Set relay(s) on/off **/
+#define MODIO2_I2C_RON_REG   0x41    /** Set relay(s) on **/
+#define MODIO2_I2C_ROFF_REG  0x42    /** Set relay(s) off **/
+
+struct MODIO2 {
+  int error;
+  uint8_t pin[64];                    // max IC2 devices
+  uint8_t address[MAX_MODIO2];
+  uint8_t pin_mask[MAX_MODIO2] = { 0 };
+  uint8_t connected_ports = 0;            // Number of ports coming from ModIO2 modules
+  uint8_t devices = 0;                // Number of ModIO2 modules
+  char stype[9];
+  bool type = false;
+} ModIo2;
+
+uint8_t ModIo2Read(uint8_t address, uint8_t reg)
+{
+  return I2cRead8(address, reg);
+}
+
+bool ModIo2Write(uint8_t address, uint8_t reg, uint8_t val)
+{
+  return I2cWrite8(address, reg, val);
+}
+
+void ModIo2SwitchRelay(void)
+{
+  for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
+    uint8_t relay_state = bitRead(XdrvMailbox.index, i);
+
+    if (ModIo2.devices > 0 && ModIo2.pin[i] < 99) {
+      uint8_t board = ModIo2.pin[i]>>3;
+      uint8_t pin = ModIo2.pin[i]&0x7;
+      uint8_t oldpinmask = ModIo2.pin_mask[board];
+
+      //AddLog(LOG_LEVEL_DEBUG, PSTR("MI2: SwitchRelay %d=%d => MODIO-%d.D%d=%d"), i, relay_state, board +1, pin, relay_state);
+      bitWrite(ModIo2.pin_mask[board], pin, relay_state);
+      if (oldpinmask != ModIo2.pin_mask[board]) {
+        ModIo2.error = SetRelay(ModIo2.address[board], ModIo2.pin_mask[board]);
+      }
+      //else AddLog(LOG_LEVEL_DEBUG, PSTR("MI2: SwitchRelay skipped"));
+    }
+  }
+}
+
+bool ModIo2IdCheck(uint8_t address)
+{
+  return MODIO2_I2C_ID_VAL == GetBoardId(address);
+}
+
+/*
+  Command code  0x20
+  Data range    0x23
+  Read the ID of the MOD-IO2. By default it must be 0x23.
+*/
+uint8_t GetBoardId(uint8_t address)
+{
+  return ModIo2Read(address, MODIO2_I2C_ID_REG);
+}
+
+/*
+  Command code  0x21
+  Data range    0x34
+  Read the firmware version of the MOD-IO2.
+*/
+uint8_t GetFirmwareVersion(uint8_t address)
+{
+  return ModIo2Read(address, MODIO2_I2C_FW_REG);
+}
+
+/*
+  Command code  0x40
+  Data range    0x00 - 0x03
+*/
+bool SetRelay(uint8_t address, uint8_t val)
+{
+  return ModIo2Write(address, MODIO2_I2C_RONOFF_REG, val);
+}
+
+/*
+  Command code  0x41
+  Data range    0x01 - 0x03
+*/
+bool SetRelayOn(uint8_t address, uint8_t val)
+{
+  return ModIo2Write(address, MODIO2_I2C_RON_REG, val);
+}
+
+/*
+  Command code  0x42
+  Data range    0x01 - 0x03
+*/
+bool SetRelayOff(uint8_t address, uint8_t val)
+{
+  return ModIo2Write(address, MODIO2_I2C_ROFF_REG, val);
+}
+
+/*
+  Command code  0x43
+  Data range    0x00 - 0x03
+  Read state of relays of the MOD-IO2. The read data is the state of the relays.
+  Bit0 is the state of RELAY1, and bit1 – RELAY2. If 1 is read the relay is turned
+  on, 0 means that it is off.
+*/
+uint8_t GetCurrentRelayState(uint8_t address)
+{
+  return ModIo2Read(address, MODIO2_I2C_STATE_REG);
+}
+
+void ModIo2Init(void)
+{
+  uint8_t modio2_address = MODIO2_ADDR_1ST;
+  while ((ModIo2.devices < MAX_MODIO2) && (modio2_address <= 0xFF)) {
+
+  //  AddLog(LOG_LEVEL_DEBUG, PSTR("MI2: Probing addr: 0x%x for MODIO2"), modio2_address);
+
+    if (I2cSetDevice(modio2_address) && ModIo2IdCheck(modio2_address)) {
+      ModIo2.type = true;
+
+      ModIo2.address[ModIo2.devices] = modio2_address;
+      ModIo2.devices++;
+
+      strcpy(ModIo2.stype, "MODIO2");
+      I2cSetActiveFound(modio2_address, ModIo2.stype);
+    }
+
+    if(modio2_address == 0xFF) {
+      break;
+    }
+    modio2_address++;
+  }
+  if (ModIo2.type) {
+    for (uint32_t i = 0; i < sizeof(ModIo2.pin); i++) {
+      ModIo2.pin[i] = 99;
+    }
+    TasmotaGlobal.devices_present = TasmotaGlobal.devices_present - ModIo2.connected_ports; // reset no of devices to avoid duplicate ports on duplicate init.
+    ModIo2.connected_ports = 0;  // reset no of devices to avoid duplicate ports on duplicate init.
+    for (uint32_t idx = 0; idx < ModIo2.devices; idx++) { // suport up to 14 boards MODIO2
+      uint8_t gpio = GetCurrentRelayState(ModIo2.address[idx]);
+      ModIo2.pin_mask[idx] = gpio;
+      //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: PCF-%d config=0x%02x, gpio=0x%02X"), idx +1, Settings->MODIO2_config[idx], gpio);
+
+      for (uint32_t i = 0; i < 2; i++, gpio>>=1) {
+        ModIo2.pin[TasmotaGlobal.devices_present] = i + 8 * idx;
+        if (!Settings->flag.save_state && !Settings->flag3.no_power_feedback) {  // SetOption63 - Don't scan relay power state at restart - #5594 and #5663
+          uint8_t power_state = 1 & gpio;
+          bitWrite(TasmotaGlobal.power, TasmotaGlobal.devices_present, power_state);
+          bitWrite(Settings->power, TasmotaGlobal.devices_present, power_state);
+        }
+        TasmotaGlobal.devices_present++;
+        ModIo2.connected_ports++;
+      }
+    }
+    AddLog(LOG_LEVEL_INFO, PSTR("MI2: Total devices %d, MODIO2 output ports %d"), ModIo2.devices, ModIo2.connected_ports);
+  }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv85(uint8_t function)
+{
+  if (!I2cEnabled(XI2C_64)) { return false; }
+
+  bool result = false;
+
+  if (FUNC_PRE_INIT == function) {
+    ModIo2Init();
+  }
+  else if (ModIo2.type) {
+    switch (function) {
+      case FUNC_SET_POWER:
+        ModIo2SwitchRelay();
+        break;
+    }
+  }
+  return result;
+}
+
+#endif  // USE_MODIO2
+#endif  // USE_I2C

LubOlimex

Thank you for this, maybe somebody will find it useful indeed.

> Hm, file upload is not allowed?

Some bots always find a way trough our forums' defense layers and upload dangerous stuff.
Technical support and documentation manager at Olimex