diff --git a/CHANGELOG.md b/CHANGELOG.md
index 183c01d24..0d9bd1601 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
## [12.4.0.2]
### Added
- Support for multiple MCP23008 as switch/button/relay
+- Support for multiple PCF8574 as switch/button/relay
### Breaking Changed
- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index c8c2e175b..79bd9a651 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -113,6 +113,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
## Changelog v12.4.0.2
### Added
- Support for multiple MCP23008/MCP23017/MCP23S17 as switch/button/relay
+- Support for multiple PCF8574 as switch/button/relay
- NTP time request from gateway [#17984](https://github.com/arendst/Tasmota/issues/17984)
### Breaking Changed
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574.ino b/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574.ino
index a889fe3aa..f07355b1b 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574.ino
@@ -1,7 +1,7 @@
/*
xdrv_28_pcf8574.ino - PCF8574 I2C support for Tasmota
- Copyright (C) 2021 Stefan Bode
+ Copyright (C) 2021 Stefan Bode and 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
@@ -24,20 +24,60 @@
*
* I2C Address: PCF8574 = 0x20 .. 0x27 (0x27 is not supported),
* PCF8574A = 0x39 .. 0x3F (0x38 is not supported)
+ *
+ * Start address and count can be overriden in user_config_override.h to allow better
+ * sharing of the I2C address space. Still the covered range must remains valid.
+ * A count of 0 can be used totaly disable any of the 2 ranges.
+ * By default, the following addresses are explicitly excluded (as per the docs) :
+ * - 0x27 and 0x37 are reserved for USE_DISPLAY_LCD in xdsp_01_lcd.ino
+ * - 0X38 is reserved for other sensors
+ * If the respective drivers are not used, overrides allows to recover those addresses
+ * If defined, USE_MCP230xx_ADDR is also always excluded
+ *
+ * Mode 1:
+ * See documentation.
+ *
+ * Mode 2:
+ * Allows easy configuration by using a sequential list of pins configured as Tasmota
+ * template and handle any input and output as configured GPIOs.
+ *
+ * Restrictions:
+ * - Uses incremental I2C addresses until template pin count reached
+ * - Max support for 28 switches (input), 32 buttons (input), 32 relays (output)
+ *
+ * Supported template fields:
+ * NAME - Template name
+ * GPIO - Sequential list of pin 1 and up with configured GPIO function
+ * Function Code Description
+ * ------------------- -------- ----------------------------------------
+ * None 0 Not used
+ * Button1..32 B 32..63 Button to Gnd with internal pullup
+ * Button_n1..32 Bn 64..95 Button to Gnd without internal pullup
+ * Button_i1..32 Bi 96..127 Button inverted to Vcc with internal pullup
+ * Button_in1..32 Bin 128..159 Button inverted to Vcc without internal pullup
+ * Switch1..28 S 160..187 Switch to Gnd with internal pullup
+ * Switch_n1..28 Sn 192..219 Switch to Gnd without internal pullup
+ * Relay1..28 R 224..255 Relay
+ * Relay_i1..28 Ri 256..287 Relay inverted
+ * Output_Hi Oh 3840 Fixed output high
+ * Output_lo Ol 3872 Fixed output low
+ *
+ * Prepare a template to be loaded either by:
+ * - a rule like: rule3 on file#pcf8574.dat do {"NAME":"PCF8574 A=Ri8-1, B=B1-8","GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]} endon
+ * - a script like: -y{"NAME":"PCF8574 A=Ri8-1, B=B1-8","GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]}
+ * - file called pcf8574.dat with contents: {"NAME":"PCF8574 A=Ri8-1, B=B1-8","GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]}
+ *
+ * Inverted relays and buttons Ri8 Ri7 Ri6 Ri5 Ri4 Ri3 Ri2 Ri1 B1 B2 B3 B4 B5 B6 B7 B8
+ * {"NAME":"PCF8574 A=Ri8-1, B=B1-8","GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]}
+ * B1 B2 B3 B4 Ri4 Ri3 Ri2 Ri1 B5 B6 B7 B8 Ri8 Ri7 Ri6 Ri5
+ * {"NAME":"PCF8574 A=B1-4,Ri4-1, B=B5-8,Ri8-5","GPIO":[32,33,34,35,259,258,257,256,36,37,38,39,263,262,261,260]}
+ * B1 B2 B3 B4 Ri4 Ri3 Ri2 Ri1 B5 B6 B7 B8 Ri8 Ri7 Ri6 Ri5
+ * {"NAME":"PCF8574 A=Bi1-4,Ri4-1, B=Bi5-8,Ri8-5","GPIO":[64,65,66,67,259,258,257,256,68,69,70,71,263,262,261,260]}
\*********************************************************************************************/
#define XDRV_28 28
#define XI2C_02 2 // See I2CDEVICES.md
-// Start address and count can be overriden in user_config_override.h to allow better
-// sharing of the I2C address space. Still the covered range must remains valid.
-// A count of 0 can be used totaly disable any of the 2 ranges.
-// By default, the following addresses are explicitly excluded (as per the docs) :
-// - 0x27 and 0x37 are reserved for USE_DISPLAY_LCD in xdsp_01_lcd.ino
-// - 0X38 is reserved for other sensors
-// If the respective drivers are not used, overrides allows to recover those addresses
-// If defined, USE_MCP230xx_ADDR is also always excluded
-
// PCF8574 address range from 0x20 to 0x26
#ifndef PCF8574_ADDR1
#define PCF8574_ADDR1 0x20 // PCF8574
@@ -53,6 +93,12 @@
#define PCF8574_ADDR2_COUNT 6
#endif
+#define PCF8574_MAX_PINS 64
+
+/*********************************************************************************************\
+ * PCF8574 support
+\*********************************************************************************************/
+
// Consitency tests - Checked across the complete range for the PCF8574/PCF8574A to allow override
#if (PCF8574_ADDR1 < 0x20) || ((PCF8574_ADDR1 + PCF8574_ADDR1_COUNT - 1) > 0x27)
#error PCF8574_ADDR1 and/or PCF8574_ADDR1_COUNT badly overriden. Fix your user_config_override
@@ -63,7 +109,9 @@
struct PCF8574 {
int error;
- uint8_t pin[64];
+ uint32_t relay_inverted;
+ uint32_t button_inverted;
+ uint16_t pin[PCF8574_MAX_PINS];
uint8_t address[MAX_PCF8574];
uint8_t pin_mask[MAX_PCF8574] = { 0 };
#ifdef USE_PCF8574_MQTTINPUT
@@ -71,25 +119,308 @@ struct PCF8574 {
#endif
uint8_t max_connected_ports = 0; // Max numbers of devices comming from PCF8574 modules
uint8_t max_devices = 0; // Max numbers of PCF8574 modules
- char stype[9];
- bool type = false;
+ uint8_t mode;
+ uint8_t chip;
+ uint8_t relay_max;
+ uint8_t relay_offset;
+ uint8_t button_max;
+ uint8_t switch_max;
+ int8_t button_offset;
+ int8_t switch_offset;
+ bool interrupt;
} Pcf8574;
-uint8_t Pcf8574Read(uint8_t idx)
-{
+/*********************************************************************************************\
+ * PCF8574 - I2C
+\*********************************************************************************************/
+
+uint8_t Pcf8574Read(uint8_t idx) {
Wire.requestFrom(Pcf8574.address[idx],(uint8_t)1);
return Wire.read();
}
-uint8_t Pcf8574Write(uint8_t idx)
-{
+uint8_t Pcf8574Write(uint8_t idx) {
Wire.beginTransmission(Pcf8574.address[idx]);
Wire.write(Pcf8574.pin_mask[idx]);
return Wire.endTransmission();
}
-void Pcf8574SwitchRelay(void)
-{
+/*********************************************************************************************/
+
+uint8_t Pcf8574ReadByte(void) {
+ Wire.requestFrom(Pcf8574.address[Pcf8574.chip], (uint8_t)1);
+ return Wire.read();
+}
+
+uint8_t Pcf8574WriteByte(uint8_t value) {
+ Wire.beginTransmission(Pcf8574.address[Pcf8574.chip]);
+ Wire.write(value);
+ return Wire.endTransmission();
+}
+
+bool Pcf8574DigitalRead(uint8_t pin) {
+ // pin 0 - 63
+ Pcf8574.chip = pin / 8;
+ uint8_t bit = pin % 8;
+ uint8_t value = Pcf8574ReadByte();
+ return value & (1 << bit);
+}
+
+void Pcf8574DigitalWrite(uint8_t pin, bool pin_value) {
+ // pin 0 - 63
+ // INPUT or INPUT_PULLUP = Pcf8574DigitalWrite(pin, 1);
+ // OUTPUT = Pcf8574DigitalWrite(pin, 0); or Pcf8574DigitalWrite(pin, 1);
+ Pcf8574.chip = pin / 8;
+ uint8_t bit = pin % 8;
+ uint8_t reg_value = Pcf8574ReadByte();
+ if (pin_value) {
+ reg_value |= 1 << bit;
+ } else {
+ reg_value &= ~(1 << bit);
+ }
+ Pcf8574WriteByte(reg_value);
+}
+
+/*********************************************************************************************\
+ * PCF8574 Mode 2 - Theo Arends
+\*********************************************************************************************/
+
+int Pcf8574Pin(uint32_t gpio, uint32_t index = 0);
+int Pcf8574Pin(uint32_t gpio, uint32_t index) {
+ uint16_t real_gpio = gpio << 5;
+ uint16_t mask = 0xFFE0;
+ if (index < GPIO_ANY) {
+ real_gpio += index;
+ mask = 0xFFFF;
+ }
+ for (uint32_t i = 0; i < Pcf8574.max_connected_ports; i++) {
+ if ((Pcf8574.pin[i] & mask) == real_gpio) {
+ return i; // Pin number configured for gpio
+ }
+ }
+ return -1; // No pin used for gpio
+}
+
+bool Pcf8574PinUsed(uint32_t gpio, uint32_t index = 0);
+bool Pcf8574PinUsed(uint32_t gpio, uint32_t index) {
+ return (Pcf8574Pin(gpio, index) >= 0);
+}
+
+uint32_t Pcf8574GetPin(uint32_t lpin) {
+ if (lpin < Pcf8574.max_connected_ports) {
+ return Pcf8574.pin[lpin];
+ } else {
+ return GPIO_NONE;
+ }
+}
+
+/*********************************************************************************************/
+
+String Pcf8574TemplateLoadFile(void) {
+ String pcftmplt = "";
+#ifdef USE_UFILESYS
+ pcftmplt = TfsLoadString("/pcf8574.dat");
+#endif // USE_UFILESYS
+#ifdef USE_RULES
+ if (!pcftmplt.length()) {
+ pcftmplt = RuleLoadFile("PCF8574.DAT");
+ }
+#endif // USE_RULES
+#ifdef USE_SCRIPT
+ if (!pcftmplt.length()) {
+ pcftmplt = ScriptLoadSection(">y");
+ }
+#endif // USE_SCRIPT
+ return pcftmplt;
+}
+
+bool Pcf8574LoadTemplate(void) {
+ String pcftmplt = Pcf8574TemplateLoadFile();
+ uint32_t len = pcftmplt.length() +1;
+ if (len < 7) { return false; } // No PcfTmplt found
+
+ JsonParser parser((char*)pcftmplt.c_str());
+ JsonParserObject root = parser.getRootObject();
+ if (!root) { return false; }
+
+ // rule3 on file#pcf8574.dat do {"NAME":"PCF8574 A=B1-4,Ri4-1, B=B5-8,Ri8-5","GPIO":[32,33,34,35,259,258,257,256,36,37,38,39,263,262,261,260]} endon
+ // rule3 on file#pcf8574.dat do {"NAME":"PCF8574 A=Bi1-4,Ri4-1, B=Bi5-8,Ri8-5","GPIO":[64,65,66,67,259,258,257,256,68,69,70,71,263,262,261,260]} endon
+ JsonParserToken val = root[PSTR(D_JSON_NAME)];
+ if (val) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Template %s"), val.getStr());
+ }
+ JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
+ if (arr) {
+ uint32_t pin = 0;
+ for (pin; pin < Pcf8574.max_connected_ports; pin++) { // Max number of detected chip pins
+ JsonParserToken val = arr[pin];
+ if (!val) { break; }
+ uint16_t mpin = val.getUInt();
+ if (mpin) { // Above GPIO_NONE
+ if ((mpin >= AGPIO(GPIO_SWT1)) && (mpin < (AGPIO(GPIO_SWT1) + MAX_SWITCHES_SET))) {
+ Pcf8574.switch_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT_PULLUP
+ }
+ else if ((mpin >= AGPIO(GPIO_SWT1_NP)) && (mpin < (AGPIO(GPIO_SWT1_NP) + MAX_SWITCHES_SET))) {
+ mpin -= (AGPIO(GPIO_SWT1_NP) - AGPIO(GPIO_SWT1));
+ Pcf8574.switch_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT
+ }
+ else if ((mpin >= AGPIO(GPIO_KEY1)) && (mpin < (AGPIO(GPIO_KEY1) + MAX_KEYS_SET))) {
+ Pcf8574.button_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT_PULLUP
+ }
+ else if ((mpin >= AGPIO(GPIO_KEY1_NP)) && (mpin < (AGPIO(GPIO_KEY1_NP) + MAX_KEYS_SET))) {
+ mpin -= (AGPIO(GPIO_KEY1_NP) - AGPIO(GPIO_KEY1));
+ Pcf8574.button_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT
+ }
+ else if ((mpin >= AGPIO(GPIO_KEY1_INV)) && (mpin < (AGPIO(GPIO_KEY1_INV) + MAX_KEYS_SET))) {
+ bitSet(Pcf8574.button_inverted, mpin - AGPIO(GPIO_KEY1_INV));
+ mpin -= (AGPIO(GPIO_KEY1_INV) - AGPIO(GPIO_KEY1));
+ Pcf8574.button_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT_PULLUP
+ }
+ else if ((mpin >= AGPIO(GPIO_KEY1_INV_NP)) && (mpin < (AGPIO(GPIO_KEY1_INV_NP) + MAX_KEYS_SET))) {
+ bitSet(Pcf8574.button_inverted, mpin - AGPIO(GPIO_KEY1_INV_NP));
+ mpin -= (AGPIO(GPIO_KEY1_INV_NP) - AGPIO(GPIO_KEY1));
+ Pcf8574.button_max++;
+ Pcf8574DigitalWrite(pin, 1); // INPUT
+ }
+ else if ((mpin >= AGPIO(GPIO_REL1)) && (mpin < (AGPIO(GPIO_REL1) + MAX_RELAYS_SET))) {
+ Pcf8574.relay_max++;
+ Pcf8574DigitalWrite(pin, 1); // OUTPUT
+ }
+ else if ((mpin >= AGPIO(GPIO_REL1_INV)) && (mpin < (AGPIO(GPIO_REL1_INV) + MAX_RELAYS_SET))) {
+ bitSet(Pcf8574.relay_inverted, mpin - AGPIO(GPIO_REL1_INV));
+ mpin -= (AGPIO(GPIO_REL1_INV) - AGPIO(GPIO_REL1));
+ Pcf8574.relay_max++;
+ Pcf8574DigitalWrite(pin, 0); // OUTPUT
+ }
+ else if (mpin == AGPIO(GPIO_OUTPUT_HI)) {
+ Pcf8574DigitalWrite(pin, 1); // OUTPUT
+ }
+ else if (mpin == AGPIO(GPIO_OUTPUT_LO)) {
+ Pcf8574DigitalWrite(pin, 0); // OUTPUT
+ }
+ else { mpin = 0; }
+ Pcf8574.pin[pin] = mpin;
+ }
+ if ((Pcf8574.switch_max >= MAX_SWITCHES_SET) ||
+ (Pcf8574.button_max >= MAX_KEYS_SET) ||
+ (Pcf8574.relay_max >= MAX_RELAYS_SET)) {
+ AddLog(LOG_LEVEL_INFO, PSTR("PCF: Max reached (S%d/B%d/R%d)"), Pcf8574.switch_max, Pcf8574.button_max, Pcf8574.relay_max);
+ break;
+ }
+ }
+ Pcf8574.max_connected_ports = pin; // Max number of configured pins
+ }
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Pins %d, pin %*_V"), Pcf8574.max_connected_ports, Pcf8574.max_connected_ports, (uint8_t*)Pcf8574.pin);
+
+ return true;
+}
+
+uint32_t Pcf8574TemplateGpio(void) {
+ String pcftmplt = Pcf8574TemplateLoadFile();
+ uint32_t len = pcftmplt.length() +1;
+ if (len < 7) { return 0; } // No PcfTmplt found
+
+ JsonParser parser((char*)pcftmplt.c_str());
+ JsonParserObject root = parser.getRootObject();
+ if (!root) { return 0; }
+
+ JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
+ if (arr.isArray()) {
+ return arr.size(); // Number of requested pins
+ }
+ return 0;
+}
+
+void Pcf8574ServiceInput(void) {
+ Pcf8574.interrupt = false;
+ // This works with no interrupt
+ uint32_t pin_offset = 0;
+ uint32_t gpio;
+ for (Pcf8574.chip = 0; Pcf8574.chip < Pcf8574.max_devices; Pcf8574.chip++) {
+ gpio = Pcf8574ReadByte();
+
+// AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("PCF: Chip %d, State %02X"), Pcf8574.chip, gpio);
+
+ uint32_t mask = 1;
+ for (uint32_t pin = 0; pin < 8; pin++) {
+ uint32_t state = ((gpio & mask) != 0);
+ uint32_t lpin = Pcf8574GetPin(pin_offset + pin); // 0 for None, 32 for KEY1, 160 for SWT1, 224 for REL1
+ uint32_t index = lpin & 0x001F; // Max 32 buttons or switches
+ lpin = BGPIO(lpin); // UserSelectablePins number
+ if (GPIO_KEY1 == lpin) {
+ ButtonSetVirtualPinState(Pcf8574.button_offset + index, (state != bitRead(Pcf8574.button_inverted, index)));
+ Pcf8574DigitalWrite(pin_offset + pin, 1); // INPUT and reset interrupt
+ }
+ else if (GPIO_SWT1 == lpin) {
+ SwitchSetVirtualPinState(Pcf8574.switch_offset + index, state);
+ Pcf8574DigitalWrite(pin_offset + pin, 1); // INPUT and reset interrupt
+ }
+ mask <<= 1;
+ }
+ pin_offset += 8;
+ }
+}
+
+void IRAM_ATTR Pcf8574InputIsr(void) {
+ Pcf8574.interrupt = true;
+}
+
+void Pcf8574Init(void) {
+ if (Pcf8574.button_max || Pcf8574.switch_max) {
+ if (PinUsed(GPIO_PCF8574_INT)) {
+ pinMode(Pin(GPIO_PCF8574_INT), INPUT_PULLUP);
+ attachInterrupt(Pin(GPIO_PCF8574_INT), Pcf8574InputIsr, CHANGE);
+ }
+ }
+}
+
+void Pcf8574Power(void) {
+ power_t rpower = XdrvMailbox.index >> Pcf8574.relay_offset;
+ for (uint32_t index = 0; index < Pcf8574.relay_max; index++) {
+ power_t state = rpower &1;
+ if (Pcf8574PinUsed(GPIO_REL1, index)) {
+ uint32_t pin = Pcf8574Pin(GPIO_REL1, index) & 0x3F; // Fix possible overflow over 63 gpios
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Power pin %d, state %d(%d)"), pin, state, bitRead(Pcf8574.relay_inverted, index));
+
+ Pcf8574DigitalWrite(pin, bitRead(Pcf8574.relay_inverted, index) ? !state : state);
+ }
+ rpower >>= 1; // Select next power
+ }
+}
+
+bool Pcf8574AddButton(void) {
+ // XdrvMailbox.index = button/switch index
+ if (Pcf8574.button_offset < 0) { Pcf8574.button_offset = XdrvMailbox.index; }
+ uint32_t index = XdrvMailbox.index - Pcf8574.button_offset;
+ if (index >= Pcf8574.button_max) { return false; }
+ XdrvMailbox.index = (Pcf8574DigitalRead(Pcf8574Pin(GPIO_KEY1, index)) != bitRead(Pcf8574.button_inverted, index));
+
+// AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: AddButton index %d, state %d"), index, XdrvMailbox.index);
+
+ return true;
+}
+
+bool Pcf8574AddSwitch(void) {
+ // XdrvMailbox.index = button/switch index
+ if (Pcf8574.switch_offset < 0) { Pcf8574.switch_offset = XdrvMailbox.index; }
+ uint32_t index = XdrvMailbox.index - Pcf8574.switch_offset;
+ if (index >= Pcf8574.switch_max) { return false; }
+ XdrvMailbox.index = Pcf8574DigitalRead(Pcf8574Pin(GPIO_SWT1, index));
+ return true;
+}
+
+/*********************************************************************************************\
+ * PCF8574 Mode 1 - Stefan Bode
+\*********************************************************************************************/
+
+void Pcf8574SwitchRelay(void) {
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
uint8_t relay_state = bitRead(XdrvMailbox.index, i);
@@ -109,8 +440,7 @@ void Pcf8574SwitchRelay(void)
}
}
-void Pcf8574Init(void)
-{
+void Pcf8574ModuleInit(void) {
uint8_t pcf8574_address = (PCF8574_ADDR1_COUNT > 0) ? PCF8574_ADDR1 : PCF8574_ADDR2;
while ((Pcf8574.max_devices < MAX_PCF8574) && (pcf8574_address < PCF8574_ADDR2 +PCF8574_ADDR2_COUNT)) {
@@ -127,16 +457,18 @@ void Pcf8574Init(void)
// AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address);
if (I2cSetDevice(pcf8574_address)) {
- Pcf8574.type = true;
+ Pcf8574.mode = 1;
Pcf8574.address[Pcf8574.max_devices] = pcf8574_address;
+ Pcf8574.max_connected_ports += 8;
Pcf8574.max_devices++;
- strcpy(Pcf8574.stype, "PCF8574");
+ char stype[9];
+ strcpy(stype, "PCF8574");
if (pcf8574_address >= PCF8574_ADDR2) {
- strcpy(Pcf8574.stype, "PCF8574A");
+ strcpy(stype, "PCF8574A");
}
- I2cSetActiveFound(pcf8574_address, Pcf8574.stype);
+ I2cSetActiveFound(pcf8574_address, stype);
}
pcf8574_address++;
@@ -144,43 +476,53 @@ void Pcf8574Init(void)
pcf8574_address = PCF8574_ADDR2;
}
}
- if (Pcf8574.type) {
- for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) {
- Pcf8574.pin[i] = 99;
- }
- UpdateDevicesPresent(-Pcf8574.max_connected_ports); // reset no of devices to avoid duplicate ports on duplicate init.
- Pcf8574.max_connected_ports = 0; // reset no of devices to avoid duplicate ports on duplicate init.
- for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { // suport up to 8 boards PCF8574
- uint8_t gpio = Pcf8574Read(idx);
- // Insure the input pins are actually writen a 1 for proper input operation
- Pcf8574.pin_mask[idx] = gpio | ~Settings->pcf8574_config[idx];
- Pcf8574Write(idx); // Write back to the register
+ if (Pcf8574.mode) {
+ if (Pcf8574LoadTemplate()) {
+ Pcf8574.mode = 2;
+
+ Pcf8574.relay_offset = TasmotaGlobal.devices_present;
+ Pcf8574.relay_max -= UpdateDevicesPresent(Pcf8574.relay_max);
+
+ Pcf8574.button_offset = -1;
+ Pcf8574.switch_offset = -1;
+ } else {
+ Pcf8574.max_connected_ports = 0; // Reset no of devices
+ for (uint32_t i = 0; i < PCF8574_MAX_PINS; i++) {
+ Pcf8574.pin[i] = 99;
+ }
+
+ for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { // suport up to 8 boards PCF8574
+ uint8_t gpio = Pcf8574Read(idx);
+ // Insure the input pins are actually writen a 1 for proper input operation
+ Pcf8574.pin_mask[idx] = gpio | ~Settings->pcf8574_config[idx];
+ Pcf8574Write(idx); // Write back to the register
#ifdef USE_PCF8574_MQTTINPUT
- Pcf8574.last_input[idx] = gpio & ~Settings->pcf8574_config[idx];
+ Pcf8574.last_input[idx] = gpio & ~Settings->pcf8574_config[idx];
#endif // #ifdef USE_PCF8574_MQTTINPUT
- //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: PCF-%d config=0x%02x, gpio=0x%02X"), idx +1, Settings->pcf8574_config[idx], gpio);
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: PCF-%d config=0x%02x, gpio=0x%02X"), idx +1, Settings->pcf8574_config[idx], gpio);
- for (uint32_t i = 0; i < 8; i++, gpio>>=1) {
- uint8_t _result = Settings->pcf8574_config[idx] >> i &1;
- //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: I2C shift i %d: %d. Powerstate: %d, TasmotaGlobal.devices_present: %d"), i,_result, Settings->power>>i&1, TasmotaGlobal.devices_present);
- if (_result > 0) {
- Pcf8574.pin[TasmotaGlobal.devices_present] = i + 8 * idx;
- bitWrite(TasmotaGlobal.rel_inverted, TasmotaGlobal.devices_present, Settings->flag3.pcf8574_ports_inverted); // SetOption81 - Invert all ports on PCF8574 devices
- if (!Settings->flag.save_state && !Settings->flag3.no_power_feedback) { // SetOption63 - Don't scan relay power state at restart - #5594 and #5663
- //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Set power from from chip state"));
- uint8_t power_state = Settings->flag3.pcf8574_ports_inverted ? 1 & ~gpio : 1 & gpio;
- bitWrite(TasmotaGlobal.power, TasmotaGlobal.devices_present, power_state);
- bitWrite(Settings->power, TasmotaGlobal.devices_present, power_state);
+ for (uint32_t i = 0; i < 8; i++, gpio>>=1) {
+ uint8_t _result = Settings->pcf8574_config[idx] >> i &1;
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: I2C shift i %d: %d. Powerstate: %d, TasmotaGlobal.devices_present: %d"), i,_result, Settings->power>>i&1, TasmotaGlobal.devices_present);
+ if (_result > 0) {
+ Pcf8574.pin[TasmotaGlobal.devices_present] = i + 8 * idx;
+ bitWrite(TasmotaGlobal.rel_inverted, TasmotaGlobal.devices_present, Settings->flag3.pcf8574_ports_inverted); // SetOption81 - Invert all ports on PCF8574 devices
+ if (!Settings->flag.save_state && !Settings->flag3.no_power_feedback) { // SetOption63 - Don't scan relay power state at restart - #5594 and #5663
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Set power from from chip state"));
+ uint8_t power_state = Settings->flag3.pcf8574_ports_inverted ? 1 & ~gpio : 1 & gpio;
+ bitWrite(TasmotaGlobal.power, TasmotaGlobal.devices_present, power_state);
+ bitWrite(Settings->power, TasmotaGlobal.devices_present, power_state);
+ }
+ //else AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: DON'T set power from chip state"));
+ UpdateDevicesPresent(1);
+ Pcf8574.max_connected_ports++;
}
- //else AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: DON'T set power from chip state"));
- UpdateDevicesPresent(1);
- Pcf8574.max_connected_ports++;
}
}
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Settings->power=0x%08X, TasmotaGlobal.power=0x%08X"), Settings->power, TasmotaGlobal.power);
+ AddLog(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), Pcf8574.max_devices, Pcf8574.max_connected_ports);
}
- //AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Settings->power=0x%08X, TasmotaGlobal.power=0x%08X"), Settings->power, TasmotaGlobal.power);
- AddLog(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), Pcf8574.max_devices, Pcf8574.max_connected_ports);
}
}
@@ -340,25 +682,23 @@ void Pcf8574SaveSettings(void)
* Interface
\*********************************************************************************************/
-bool Xdrv28(uint32_t function)
-{
+bool Xdrv28(uint32_t function) {
if (!I2cEnabled(XI2C_02)) { return false; }
bool result = false;
- if (FUNC_PRE_INIT == function) {
- Pcf8574Init();
- }
- else if (Pcf8574.type) {
+ if (FUNC_MODULE_INIT == function) {
+ Pcf8574ModuleInit();
+ } else if (1 == Pcf8574.mode) {
switch (function) {
- case FUNC_SET_POWER:
- Pcf8574SwitchRelay();
- break;
#ifdef USE_PCF8574_MQTTINPUT
case FUNC_EVERY_50_MSECOND:
Pcf8574CheckForInputChange();
break;
#endif // #ifdef USE_PCF8574_MQTTINPUT
+ case FUNC_SET_POWER:
+ Pcf8574SwitchRelay();
+ break;
#ifdef USE_PCF8574_SENSOR
case FUNC_JSON_APPEND:
Pcf8574Show(1);
@@ -378,6 +718,32 @@ bool Xdrv28(uint32_t function)
#endif // #ifdef USE_PCF8574_DISPLAYINPUT
#endif // USE_WEBSERVER
}
+ } else if (2 == Pcf8574.mode) {
+ switch (function) {
+ case FUNC_LOOP:
+ case FUNC_SLEEP_LOOP:
+ if (!Pcf8574.interrupt) { return false; }
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("PCF: Interrupt"));
+ Pcf8574ServiceInput();
+ break;
+ case FUNC_EVERY_100_MSECOND:
+ if (Pcf8574.button_max || Pcf8574.switch_max) {
+ Pcf8574ServiceInput();
+ }
+ break;
+ case FUNC_SET_POWER:
+ Pcf8574Power();
+ break;
+ case FUNC_INIT:
+ Pcf8574Init();
+ break;
+ case FUNC_ADD_BUTTON:
+ result = Pcf8574AddButton();
+ break;
+ case FUNC_ADD_SWITCH:
+ result = Pcf8574AddSwitch();
+ break;
+ }
}
return result;
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574_v1.ino b/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574_v1.ino
new file mode 100644
index 000000000..841da4e70
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_28_pcf8574_v1.ino
@@ -0,0 +1,386 @@
+/*
+ xdrv_28_pcf8574.ino - PCF8574 I2C support for Tasmota
+
+ Copyright (C) 2021 Stefan Bode
+
+ 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