diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0eeaeeb..b5aab9494 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ All notable changes to this project will be documented in this file. - Matter read/write and commands (#18000) - Matter subscriptions (#18017, #18018) - Matter multi-fabric (#18019) -- Support for multiple MCP23017 as switch/button/relay +- Support for multiple MCP23017/MCP23S17 as switch/button/relay - NTP time request from gateway (#17984) ### Breaking Changed +- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration ### Changed - ADC Range oversample from 2 to 32 (#17975) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d25d6ba87..a2abd76b2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -112,10 +112,11 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm ## Changelog v12.4.0.1 ### Added -- Support for multiple MCP23017 as switch/button/relay +- Support for multiple MCP23017/MCP23S17 as switch/button/relay - NTP time request from gateway [#17984](https://github.com/arendst/Tasmota/issues/17984) ### Breaking Changed +- Shelly Pro 4PM using standard MCP23xxx driver and needs one time Auto-Configuration ### Changed - ESP32 Framework (Core) from v2.0.6 to v2.0.7 diff --git a/tasmota/include/tasmota_configurations_ESP32.h b/tasmota/include/tasmota_configurations_ESP32.h index e6ba3e188..c351f1f01 100644 --- a/tasmota/include/tasmota_configurations_ESP32.h +++ b/tasmota/include/tasmota_configurations_ESP32.h @@ -353,6 +353,7 @@ #define USE_ENERGY_SENSOR // Add energy to support Shelly Pro 4PM display (+38k code) #define USE_ADE7953 +#define USE_MCP23XXX_DRV #define USE_SHELLY_PRO @@ -664,6 +665,7 @@ //#define USE_RC522 // Add support for MFRC522 13.56Mhz Rfid reader (+6k code) //#define USE_MCP2515 // Add support for can bus using MCP2515 (+7k code) //#define USE_CANSNIFFER // Add support for can bus sniffer using MCP2515 (+5k code) +#define USE_MCP23XXX_DRV #define USE_SHELLY_PRO // Add support for Shelly Pro #define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_67_mcp23xxx.ino b/tasmota/tasmota_xdrv_driver/xdrv_67_mcp23xxx.ino index 5a2a263ac..a120ae5bf 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_67_mcp23xxx.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_67_mcp23xxx.ino @@ -6,8 +6,7 @@ SPDX-License-Identifier: GPL-3.0-only */ -#ifdef USE_I2C -//#if defined(USE_I2C) || defined(USE_SPI) +#if defined(USE_I2C) || defined(USE_SPI) #ifdef USE_MCP23XXX_DRV /*********************************************************************************************\ * MCP23008/17 - I2C GPIO Expander to be used as virtual button/switch/relay only @@ -17,11 +16,11 @@ * * I2C Address: 0x20 - 0x26 (0x27 is not supported) * - * The goal of the driver is to provide a sequential list of pins configured like Tasmota template + * The goal of the driver is to provide a sequential list of pins configured as Tasmota template * and handle any input and output as configured GPIOs. * * Restrictions: - * - Only MCP23017 (=I2C) + * - Supports MCP23017 (=I2C) and MCP23S17 (=SPI) * - Max support for 28 switches (input), 32 buttons (input), 32 relays (output) * * Supported template fields: @@ -45,6 +44,9 @@ * - a script like: -y{"NAME":"MCP23017 A=Ri8-1, B=B1-8","ADDR":32,"GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]} * - file called mcp23x.dat with contents: {"NAME":"MCP23017 A=Ri8-1, B=B1-8","ADDR":32,"GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]} * + * S3 S2 B2 B3 B1 S1 R1 R4 R2 R3 S4 + * {"NAME":"MCP23S17 Shelly Pro 4PM","GPIO":[194,193,65,66,0,64,192,0,224,0,0,0,227,225,226,195]} + * * Inverted relays and buttons Ri8 Ri7 Ri6 Ri5 Ri4 Ri3 Ri2 Ri1 B1 B2 B3 B4 B5 B6 B7 B8 * {"NAME":"MCP23017 A=Ri8-1, B=B1-8","ADDR":32,"GPIO":[263,262,261,260,259,258,257,256,32,33,34,35,36,37,38,39]} * @@ -156,15 +158,6 @@ uint16_t *Mcp23x_gpio_pin = nullptr; * MCP23x17 - SPI and I2C \*********************************************************************************************/ -void MCP23xDumpRegs(void) { - uint8_t data[22]; - for (Mcp23x.chip = 0; Mcp23x.chip < Mcp23x.max_devices; Mcp23x.chip++) { - I2cReadBuffer(Mcp23x.device[Mcp23x.chip].address, 0, data, 22); - AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: Address %d, Regs %22_H"), Mcp23x.device[Mcp23x.chip].address, data); - } -} - -/* #ifdef USE_SPI void MCP23xEnable(void) { SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); @@ -176,11 +169,33 @@ void MCP23xDisable(void) { digitalWrite(Mcp23x.device[Mcp23x.chip].pin_cs, 1); } #endif -*/ + +void MCP23xDumpRegs(void) { + uint8_t data[22]; + for (Mcp23x.chip = 0; Mcp23x.chip < Mcp23x.max_devices; Mcp23x.chip++) { +#ifdef USE_SPI + if (MCP23X_SPI == Mcp23x.device[Mcp23x.chip].interface) { + MCP23xEnable(); + SPI.transfer(Mcp23x.device[Mcp23x.chip].address | 1); + SPI.transfer(0); + for (uint32_t i = 0; i < sizeof(data); i++) { + data[i] = SPI.transfer(0xFF); + } + MCP23xDisable(); + } +#endif +#ifdef USE_I2C + if (MCP23X_I2C == Mcp23x.device[Mcp23x.chip].interface) { + I2cReadBuffer(Mcp23x.device[Mcp23x.chip].address, 0, data, sizeof(data)); + } +#endif + AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: Intf %d, Address %02X, Regs %*_H"), Mcp23x.device[Mcp23x.chip].interface, Mcp23x.device[Mcp23x.chip].address, sizeof(data), data); + } +} + uint32_t MCP23xRead16(uint8_t reg) { // Read 16-bit registers: (regb << 8) | rega uint32_t value = 0; -/* #ifdef USE_SPI if (MCP23X_SPI == Mcp23x.device[Mcp23x.chip].interface) { MCP23xEnable(); @@ -191,7 +206,6 @@ uint32_t MCP23xRead16(uint8_t reg) { MCP23xDisable(); } #endif -*/ #ifdef USE_I2C if (MCP23X_I2C == Mcp23x.device[Mcp23x.chip].interface) { value = I2cRead16LE(Mcp23x.device[Mcp23x.chip].address, reg); @@ -202,7 +216,6 @@ uint32_t MCP23xRead16(uint8_t reg) { uint32_t MCP23xRead(uint8_t reg) { uint32_t value = 0; -/* #ifdef USE_SPI if (MCP23X_SPI == Mcp23x.device[Mcp23x.chip].interface) { MCP23xEnable(); @@ -212,7 +225,6 @@ uint32_t MCP23xRead(uint8_t reg) { MCP23xDisable(); } #endif -*/ #ifdef USE_I2C if (MCP23X_I2C == Mcp23x.device[Mcp23x.chip].interface) { value = I2cRead8(Mcp23x.device[Mcp23x.chip].address, reg); @@ -222,7 +234,6 @@ uint32_t MCP23xRead(uint8_t reg) { } bool MCP23xValidRead(uint8_t reg, uint8_t *data) { -/* #ifdef USE_SPI if (MCP23X_SPI == Mcp23x.device[Mcp23x.chip].interface) { MCP23xEnable(); @@ -233,15 +244,15 @@ bool MCP23xValidRead(uint8_t reg, uint8_t *data) { return true; } #endif -*/ +#ifdef USE_I2C if (MCP23X_I2C == Mcp23x.device[Mcp23x.chip].interface) { return I2cValidRead8(data, Mcp23x.device[Mcp23x.chip].address, reg); } return false; +#endif } void MCP23xWrite(uint8_t reg, uint8_t value) { -/* #ifdef USE_SPI if (MCP23X_SPI == Mcp23x.device[Mcp23x.chip].interface) { MCP23xEnable(); @@ -251,7 +262,6 @@ void MCP23xWrite(uint8_t reg, uint8_t value) { MCP23xDisable(); } #endif -*/ #ifdef USE_I2C if (MCP23X_I2C == Mcp23x.device[Mcp23x.chip].interface) { I2cWrite8(Mcp23x.device[Mcp23x.chip].address, reg, value); @@ -546,44 +556,71 @@ void MCP23xModuleInit(void) { return; } - uint8_t mcp23xxx_address = MCP23XXX_ADDR_START; - while ((Mcp23x.max_devices < MCP23XXX_MAX_DEVICES) && (mcp23xxx_address < MCP23XXX_ADDR_END)) { - Mcp23x.chip = Mcp23x.max_devices; - if (I2cSetDevice(mcp23xxx_address)) { - Mcp23x.device[Mcp23x.chip].interface = MCP23X_I2C; - Mcp23x.device[Mcp23x.chip].address = mcp23xxx_address; +#ifdef USE_SPI + if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) && PinUsed(GPIO_MCP23SXX_CS, GPIO_ANY)) { + SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1); + while ((Mcp23x.max_devices < MCP23XXX_MAX_DEVICES) && PinUsed(GPIO_MCP23SXX_CS, Mcp23x.max_devices)) { + Mcp23x.device[Mcp23x.chip].pin_cs = Pin(GPIO_MCP23SXX_CS, Mcp23x.max_devices); + digitalWrite(Mcp23x.device[Mcp23x.chip].pin_cs, 1); + pinMode(Mcp23x.device[Mcp23x.chip].pin_cs, OUTPUT); + Mcp23x.device[Mcp23x.chip].interface = MCP23X_SPI; + Mcp23x.device[Mcp23x.chip].address = MCP23XXX_ADDR_START << 1; + AddLog(LOG_LEVEL_INFO, PSTR("SPI: MCP23S17 found")); + Mcp23x.device[Mcp23x.chip].type = 3; + Mcp23x.device[Mcp23x.chip].pins = 16; + MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing + Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X17_OLATA); + Mcp23x.device[Mcp23x.chip].olatb = MCP23xRead(MCP23X17_OLATB); + Mcp23x.max_devices++; - MCP23xWrite(MCP23X08_IOCON, 0x80); // Attempt to set bank mode - this will only work on MCP23017, so its the best way to detect the different chips 23008 vs 23017 - uint8_t buffer; - if (MCP23xValidRead(MCP23X08_IOCON, &buffer)) { - if (0x00 == buffer) { -/* - I2cSetActiveFound(mcp23xxx_address, "MCP23008"); - Mcp23x.device[Mcp23x.chip].type = 1; - Mcp23x.device[Mcp23x.chip].pins = 8; - Mcp23x.max_devices++; -*/ + Mcp23x.max_pins += Mcp23x.device[Mcp23x.chip].pins; + pins_needed -= Mcp23x.device[Mcp23x.chip].pins; + if (!pins_needed) { break; } + } + } else { +#endif // USE_SPI + uint8_t mcp23xxx_address = MCP23XXX_ADDR_START; + while ((Mcp23x.max_devices < MCP23XXX_MAX_DEVICES) && (mcp23xxx_address < MCP23XXX_ADDR_END)) { + Mcp23x.chip = Mcp23x.max_devices; + if (I2cSetDevice(mcp23xxx_address)) { + Mcp23x.device[Mcp23x.chip].interface = MCP23X_I2C; + Mcp23x.device[Mcp23x.chip].address = mcp23xxx_address; + + MCP23xWrite(MCP23X08_IOCON, 0x80); // Attempt to set bank mode - this will only work on MCP23017, so its the best way to detect the different chips 23008 vs 23017 + uint8_t buffer; + if (MCP23xValidRead(MCP23X08_IOCON, &buffer)) { + if (0x00 == buffer) { + /* + I2cSetActiveFound(mcp23xxx_address, "MCP23008"); + Mcp23x.device[Mcp23x.chip].type = 1; + Mcp23x.device[Mcp23x.chip].pins = 8; + Mcp23x.max_devices++; + */ + } + else if (0x80 == buffer) { + I2cSetActiveFound(mcp23xxx_address, "MCP23017"); + Mcp23x.device[Mcp23x.chip].type = 2; + Mcp23x.device[Mcp23x.chip].pins = 16; + MCP23xWrite(MCP23X08_IOCON, 0x00); // Reset bank mode to 0 (MCP23X17_GPINTENB) + MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing + Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X17_OLATA); + Mcp23x.device[Mcp23x.chip].olatb = MCP23xRead(MCP23X17_OLATB); + Mcp23x.max_devices++; + } + Mcp23x.max_pins += Mcp23x.device[Mcp23x.chip].pins; + pins_needed -= Mcp23x.device[Mcp23x.chip].pins; } - else if (0x80 == buffer) { - I2cSetActiveFound(mcp23xxx_address, "MCP23017"); - Mcp23x.device[Mcp23x.chip].type = 2; - Mcp23x.device[Mcp23x.chip].pins = 16; - MCP23xWrite(MCP23X08_IOCON, 0x00); // Reset bank mode to 0 (MCP23X17_GPINTENB) - MCP23xWrite(MCP23X17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing - Mcp23x.device[Mcp23x.chip].olata = MCP23xRead(MCP23X17_OLATA); - Mcp23x.device[Mcp23x.chip].olatb = MCP23xRead(MCP23X17_OLATB); - Mcp23x.max_devices++; - } - Mcp23x.max_pins += Mcp23x.device[Mcp23x.chip].pins; - pins_needed -= Mcp23x.device[Mcp23x.chip].pins; + } + if (pins_needed) { + mcp23xxx_address++; + } else { + mcp23xxx_address = MCP23XXX_ADDR_END; } } - if (pins_needed) { - mcp23xxx_address++; - } else { - mcp23xxx_address = MCP23XXX_ADDR_END; - } +#ifdef USE_SPI } +#endif // USE_SPI + if (!Mcp23x.max_devices) { return; } Mcp23x_gpio_pin = (uint16_t*)calloc(Mcp23x.max_pins, 2); @@ -670,8 +707,15 @@ bool MCP23xAddSwitch(void) { \*********************************************************************************************/ bool Xdrv67(uint32_t function) { -// if (!I2cEnabled(XI2C_77) && (SPI_MOSI_MISO != TasmotaGlobal.spi_enabled)) { return false; } - if (!I2cEnabled(XI2C_77)) { return false; } + bool spi_enabled = false; + bool i2c_enabled = false; +#ifdef USE_SPI + spi_enabled = (SPI_MOSI_MISO == TasmotaGlobal.spi_enabled); +#endif // USE_SPI +#ifdef USE_I2C + i2c_enabled = I2cEnabled(XI2C_77); +#endif // USE_I2C + if (!spi_enabled && !i2c_enabled) { return false; } bool result = false; diff --git a/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro.ino b/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro.ino index 05ca7aa3f..94241e23d 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro.ino @@ -19,6 +19,7 @@ #ifdef ESP32 #ifdef USE_SPI +#ifdef USE_MCP23XXX_DRV #ifdef USE_SHELLY_PRO /*********************************************************************************************\ * Shelly Pro support @@ -27,7 +28,7 @@ * {"NAME":"Shelly Pro 1PM","GPIO":[9568,1,9472,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,3459,0,0,32,4736,0,160,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350"} * {"NAME":"Shelly Pro 2","GPIO":[0,1,0,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,0,0,0,32,4736,4737,160,161],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350;AdcParam2 2,5600,4700,3350"} * {"NAME":"Shelly Pro 2PM","GPIO":[9568,1,9472,1,768,0,0,0,672,704,736,9569,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,3460,0,0,32,4736,4737,160,161],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350;AdcParam2 2,5600,4700,3350"} - * {"NAME":"Shelly Pro 4PM","GPIO":[0,6210,0,6214,9568,0,0,0,0,0,9569,0,768,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,736,704,3461,0,4736,0,0,672],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350"} + * {"NAME":"Shelly Pro 4PM","GPIO":[0,6210,0,6214,9568,0,0,0,0,0,9569,0,10272,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,736,704,3461,0,4736,0,0,672],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350;rule3 on file#mcp23x.dat do {\"NAME\":\"MCP23S17 Shelly Pro 4PM\",\"GPIO\":[194,193,65,66,0,64,192,0,224,0,0,0,227,225,226,195]} endon"} * * Shelly Pro 1/2 uses SPI to control one 74HC595 for relays/leds and one ADE7953 (1PM) or two ADE7953 (2PM) for energy monitoring * Shelly Pro 4 uses an SPI to control one MCP23S17 for buttons/switches/relays/leds and two ADE7953 for energy monitoring and a second SPI for the display @@ -37,9 +38,6 @@ #define XDRV_88 88 #define SHELLY_PRO_PIN_LAN8720_RESET 5 -#define SHELLY_PRO_4_PIN_SPI_CS 16 -#define SHELLY_PRO_4_PIN_MCP23S17_INT 35 -#define SHELLY_PRO_4_MCP23S17_ADDRESS 0x40 struct SPro { uint32_t last_update; @@ -47,146 +45,16 @@ struct SPro { int8_t switch_offset; int8_t button_offset; uint8_t pin_register_cs; - uint8_t pin_mcp23s17_int; uint8_t ledlink; uint8_t power; bool init_done; uint8_t detected; } SPro; -/*********************************************************************************************\ - * Shelly Pro MCP23S17 support -\*********************************************************************************************/ - -enum SP4MCP23X17GPIORegisters { - // A side - SP4_MCP23S17_IODIRA = 0x00, - SP4_MCP23S17_IPOLA = 0x02, - SP4_MCP23S17_GPINTENA = 0x04, - SP4_MCP23S17_DEFVALA = 0x06, - SP4_MCP23S17_INTCONA = 0x08, - SP4_MCP23S17_IOCONA = 0x0A, - SP4_MCP23S17_GPPUA = 0x0C, - SP4_MCP23S17_INTFA = 0x0E, - SP4_MCP23S17_INTCAPA = 0x10, - SP4_MCP23S17_GPIOA = 0x12, - SP4_MCP23S17_OLATA = 0x14, - // B side - SP4_MCP23S17_IODIRB = 0x01, - SP4_MCP23S17_IPOLB = 0x03, - SP4_MCP23S17_GPINTENB = 0x05, - SP4_MCP23S17_DEFVALB = 0x07, - SP4_MCP23S17_INTCONB = 0x09, - SP4_MCP23S17_IOCONB = 0x0B, - SP4_MCP23S17_GPPUB = 0x0D, - SP4_MCP23S17_INTFB = 0x0F, - SP4_MCP23S17_INTCAPB = 0x11, - SP4_MCP23S17_GPIOB = 0x13, - SP4_MCP23S17_OLATB = 0x15, -}; - -uint8_t sp4_mcp23s17_olata = 0; -uint8_t sp4_mcp23s17_olatb = 0; - -bool sp4_spi_busy; - -void SP4Mcp23S17Enable(void) { - sp4_spi_busy = true; - SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); - digitalWrite(SPro.pin_register_cs, 0); -} - -void SP4Mcp23S17Disable(void) { - SPI.endTransaction(); - digitalWrite(SPro.pin_register_cs, 1); - sp4_spi_busy = false; -} - -uint32_t SP4Mcp23S17Read16(uint8_t reg) { - // Read 16-bit registers: (regb << 8) | rega - SP4Mcp23S17Enable(); - SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS | 1); - SPI.transfer(reg); - uint32_t value = SPI.transfer(0xFF); // RegA - value |= (SPI.transfer(0xFF) << 8); // RegB - SP4Mcp23S17Disable(); - return value; -} - -uint32_t SP4Mcp23S17Read(uint8_t reg) { - SP4Mcp23S17Enable(); - SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS | 1); - SPI.transfer(reg); - uint32_t value = SPI.transfer(0xFF); - SP4Mcp23S17Disable(); - return value; -} - -void SP4Mcp23S17Write(uint8_t reg, uint8_t value) { - SP4Mcp23S17Enable(); - SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS); - SPI.transfer(reg); - SPI.transfer(value); - SP4Mcp23S17Disable(); -} - -void SP4Mcp23S17Update(uint8_t pin, bool pin_value, uint8_t reg_addr) { - uint8_t bit = pin % 8; - uint8_t reg_value = 0; - if (reg_addr == SP4_MCP23S17_OLATA) { - reg_value = sp4_mcp23s17_olata; - } else if (reg_addr == SP4_MCP23S17_OLATB) { - reg_value = sp4_mcp23s17_olatb; - } else { - reg_value = SP4Mcp23S17Read(reg_addr); - } - if (pin_value) { - reg_value |= 1 << bit; - } else { - reg_value &= ~(1 << bit); - } - SP4Mcp23S17Write(reg_addr, reg_value); - if (reg_addr == SP4_MCP23S17_OLATA) { - sp4_mcp23s17_olata = reg_value; - } else if (reg_addr == SP4_MCP23S17_OLATB) { - sp4_mcp23s17_olatb = reg_value; - } -} - -void SP4Mcp23S17PinMode(uint8_t pin, uint8_t flags) { - uint8_t iodir = pin < 8 ? SP4_MCP23S17_IODIRA : SP4_MCP23S17_IODIRB; - uint8_t gppu = pin < 8 ? SP4_MCP23S17_GPPUA : SP4_MCP23S17_GPPUB; - if (flags == INPUT) { - SP4Mcp23S17Update(pin, true, iodir); - SP4Mcp23S17Update(pin, false, gppu); - } else if (flags == (INPUT | PULLUP)) { - SP4Mcp23S17Update(pin, true, iodir); - SP4Mcp23S17Update(pin, true, gppu); - } else if (flags == OUTPUT) { - SP4Mcp23S17Update(pin, false, iodir); - } -} - -bool SP4Mcp23S17DigitalRead(uint8_t pin) { - uint8_t bit = pin % 8; - uint8_t reg_addr = pin < 8 ? SP4_MCP23S17_GPIOA : SP4_MCP23S17_GPIOB; - uint8_t value = SP4Mcp23S17Read(reg_addr); - return value & (1 << bit); -} - -void SP4Mcp23S17DigitalWrite(uint8_t pin, bool value) { - uint8_t reg_addr = pin < 8 ? SP4_MCP23S17_OLATA : SP4_MCP23S17_OLATB; - SP4Mcp23S17Update(pin, value, reg_addr); -} - /*********************************************************************************************\ * Shelly Pro 4 \*********************************************************************************************/ -const uint8_t sp4_relay_pin[] = { 8, 13, 14, 12 }; -const uint8_t sp4_switch_pin[] = { 6, 1, 0, 15 }; -const uint8_t sp4_button_pin[] = { 5, 2, 3 }; - void ShellyPro4Init(void) { /* Shelly Pro 4PM MCP23S17 registers @@ -207,97 +75,13 @@ void ShellyPro4Init(void) { bit 14 = output - Relay O3 bit 15 = input, inverted - Switch4 */ - SP4Mcp23S17Write(SP4_MCP23S17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing - SP4Mcp23S17Write(SP4_MCP23S17_GPINTENA, 0x6F); // Enable interrupt on change - SP4Mcp23S17Write(SP4_MCP23S17_GPINTENB, 0x80); // Enable interrupt on change - - // Read current output register state - sp4_mcp23s17_olata = SP4Mcp23S17Read(SP4_MCP23S17_OLATA); - sp4_mcp23s17_olatb = SP4Mcp23S17Read(SP4_MCP23S17_OLATB); - - SP4Mcp23S17PinMode(4, OUTPUT); // Reset display, ADE7943 - SP4Mcp23S17DigitalWrite(4, 1); - - for (uint32_t i = 0; i < 3; i++) { - SP4Mcp23S17PinMode(sp4_button_pin[i], INPUT); // Button Up, Down, OK (RC with 10k to 3V3 and button shorting C) - } - SPro.button_offset = -1; - - for (uint32_t i = 0; i < 4; i++) { - SP4Mcp23S17PinMode(sp4_switch_pin[i], INPUT); // Switch1..4 - SP4Mcp23S17PinMode(sp4_relay_pin[i], OUTPUT); // Relay O1..O4 - } - SPro.switch_offset = -1; - - // Read current input register state - SPro.input_state = SP4Mcp23S17Read16(SP4_MCP23S17_GPIOA) & 0x806F; // Read gpio and clear interrupt - attachInterrupt(SPro.pin_mcp23s17_int, ShellyProUpdateIsr, CHANGE); } void ShellyPro4Reset(void) { - SP4Mcp23S17DigitalWrite(4, 0); // Reset pin display, ADE7953 + MCP23xPinMode(4, OUTPUT); + MCP23xDigitalWrite(4, 0); // Reset pin display, ADE7953 delay(1); // To initiate a hardware reset, this pin must be brought low for a minimum of 10 μs. - SP4Mcp23S17DigitalWrite(4, 1); -} - -bool ShellyProAddButton(void) { - if (SPro.detected != 4) { return false; } // Only support Shelly Pro 4 - if (SPro.button_offset < 0) { SPro.button_offset = XdrvMailbox.index; } - uint32_t index = XdrvMailbox.index - SPro.button_offset; - if (index > 2) { return false; } // Support three buttons -/* - uint32_t state = bitRead(SPro.input_state, sp4_button_pin[index]); // 1 on power on and restart - AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Button default state %d"), state); - XdrvMailbox.index = state; -*/ - XdrvMailbox.index = 1; // 1 on power on and restart - return true; -} - -bool ShellyProAddSwitch(void) { - if (SPro.detected != 4) { return false; } // Only support Shelly Pro 4 - if (SPro.switch_offset < 0) { SPro.switch_offset = XdrvMailbox.index; } - uint32_t index = XdrvMailbox.index - SPro.switch_offset; - if (index > 3) { return false; } // Support four switches -/* - uint32_t state = bitRead(SPro.input_state, sp4_switch_pin[index]); // 0 on power on and restart - AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Switch default state %d"), state); - XdrvMailbox.index = state; -*/ - XdrvMailbox.index = 0; // 0 on power on and restart - return true; -} - -void ShellyProUpdateIsr(void) { - /* - The goal if this function is to minimize SPI and SetVirtualPinState calls - */ - uint32_t input_state = SP4Mcp23S17Read16(SP4_MCP23S17_INTCAPA); // Read intcap and clear interrupt - input_state &= 0x806F; // Only test input bits - -// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Input from %04X to %04X"), SPro.input_state, input_state); - - if (TasmotaGlobal.uptime < 3) { return; } // Flush interrupt for 3 seconds after poweron - - uint32_t mask = 1; - for (uint32_t j = 0; j < 16; j++) { - if ((input_state & mask) != (SPro.input_state & mask)) { - uint32_t state = (input_state >> j) &1; - -// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Change pin %d to %d"), j, state); - - for (uint32_t i = 0; i < 4; i++) { - if (j == sp4_switch_pin[i]) { - SwitchSetVirtualPinState(SPro.switch_offset +i, state); - } - else if ((i < 3) && (j == sp4_button_pin[i])) { - ButtonSetVirtualPinState(SPro.button_offset +i, state); - } - } - } - mask <<= 1; - } - SPro.input_state = input_state; + MCP23xDigitalWrite(4, 1); } bool ShellyProButton(void) { @@ -357,7 +141,7 @@ void ShellyProUpdate(void) { void ShellyProPreInit(void) { if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) && - PinUsed(GPIO_SPI_CS) && // 74HC595 rclk / MCP23S17 + (PinUsed(GPIO_SPI_CS) || PinUsed(GPIO_MCP23SXX_CS)) && // 74HC595 rclk / MCP23S17 TasmotaGlobal.gpio_optiona.shelly_pro) { // Option_A7 if (PinUsed(GPIO_SWT1) || PinUsed(GPIO_KEY1)) { @@ -365,26 +149,16 @@ void ShellyProPreInit(void) { if (PinUsed(GPIO_SWT1, 1) || PinUsed(GPIO_KEY1, 1)) { SPro.detected = 2; // Shelly Pro 2 } - SPro.ledlink = 0x18; // Blue led on - set by first call ShellyProPower() - Shelly 1/2 - } - if (SHELLY_PRO_4_PIN_SPI_CS == Pin(GPIO_SPI_CS)) { - SPro.detected = 4; // Shelly Pro 4PM (No SWT or KEY) - } - - if (SPro.detected) { - UpdateDevicesPresent(SPro.detected); // Shelly Pro 1 - + UpdateDevicesPresent(SPro.detected); // Shelly Pro 1/2 SPro.pin_register_cs = Pin(GPIO_SPI_CS); - digitalWrite(SPro.pin_register_cs, (4 == SPro.detected) ? 1 : 0); // Prep 74HC595 rclk + digitalWrite(SPro.pin_register_cs, 0); // Prep 74HC595 rclk pinMode(SPro.pin_register_cs, OUTPUT); + SPro.ledlink = 0x18; // Blue led on - set by first call ShellyProPower() - Shelly 1/2 // Does nothing if SPI is already initiated (by ADE7953) so no harm done SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1); - - if (4 == SPro.detected) { - SPro.pin_mcp23s17_int = SHELLY_PRO_4_PIN_MCP23S17_INT; // GPIO35 = MCP23S17 common interrupt - pinMode(SPro.pin_mcp23s17_int, INPUT); - ShellyPro4Init(); // Init MCP23S17 - } + } + if (PinUsed(GPIO_MCP23SXX_CS)) { + SPro.detected = 4; // Shelly Pro 4PM (No SWT or KEY) } } } @@ -407,26 +181,6 @@ void ShellyProPower(void) { if (SPro.detected != 4) { SPro.power = XdrvMailbox.index &3; ShellyProUpdate(); - } else { - -// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Set Power 0x%08X"), XdrvMailbox.index); - - power_t rpower = XdrvMailbox.index; -/* - for (uint32_t i = 0; i < 4; i++) { - power_t state = rpower &1; - SP4Mcp23S17DigitalWrite(sp4_relay_pin[i], state); // 4 SPI writes - rpower >>= 1; // Select next power - } -*/ - for (uint32_t i = 0; i < 4; i++) { - power_t state = rpower &1; - uint32_t bit = sp4_relay_pin[i] -8; // Adjust by 8 bits - bitWrite(sp4_mcp23s17_olatb, bit, state); - rpower >>= 1; // Select next power - } - SP4Mcp23S17Write(SP4_MCP23S17_OLATB, sp4_mcp23s17_olatb); // 1 SPI write - } } @@ -484,7 +238,7 @@ void ShellyProLedLinkWifiOff(void) { bool Xdrv88(uint32_t function) { bool result = false; - if (FUNC_MODULE_INIT == function) { + if (FUNC_PRE_INIT == function) { ShellyProPreInit(); } else if (SPro.detected) { switch (function) { @@ -502,12 +256,6 @@ bool Xdrv88(uint32_t function) { case FUNC_INIT: ShellyProInit(); break; - case FUNC_ADD_BUTTON: - result = ShellyProAddButton(); - break; - case FUNC_ADD_SWITCH: - result = ShellyProAddSwitch(); - break; case FUNC_LED_LINK: ShellyProLedLink(); break; @@ -517,5 +265,6 @@ bool Xdrv88(uint32_t function) { } #endif // USE_SHELLY_PRO +#endif // USE_MCP23XXX_DRV #endif // USE_SPI #endif // ESP32 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro_v2.ino b/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro_v2.ino new file mode 100644 index 000000000..d9f7dd4a0 --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_88_esp32_shelly_pro_v2.ino @@ -0,0 +1,521 @@ +/* + xdrv_88_esp32_shelly_pro.ino - Shelly pro family support for Tasmota + + Copyright (C) 2022 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 . +*/ + +#ifdef ESP32 +#ifdef USE_SPI +#ifdef USE_SHELLY_PRO_V2 +/*********************************************************************************************\ + * Shelly Pro support + * + * {"NAME":"Shelly Pro 1","GPIO":[0,1,0,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,0,0,0,32,4736,0,160,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350"} + * {"NAME":"Shelly Pro 1PM","GPIO":[9568,1,9472,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,3459,0,0,32,4736,0,160,0],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350"} + * {"NAME":"Shelly Pro 2","GPIO":[0,1,0,1,768,0,0,0,672,704,736,0,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,0,0,0,32,4736,4737,160,161],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350;AdcParam2 2,5600,4700,3350"} + * {"NAME":"Shelly Pro 2PM","GPIO":[9568,1,9472,1,768,0,0,0,672,704,736,9569,0,0,5600,6214,0,0,0,5568,0,0,0,0,0,0,0,0,3460,0,0,32,4736,4737,160,161],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350;AdcParam2 2,5600,4700,3350"} + * {"NAME":"Shelly Pro 4PM","GPIO":[0,6210,0,6214,9568,0,0,0,0,0,9569,0,768,0,5600,0,0,0,0,5568,0,0,0,0,0,0,0,0,736,704,3461,0,4736,0,0,672],"FLAG":0,"BASE":1,"CMND":"AdcParam1 2,5600,4700,3350"} + * + * Shelly Pro 1/2 uses SPI to control one 74HC595 for relays/leds and one ADE7953 (1PM) or two ADE7953 (2PM) for energy monitoring + * Shelly Pro 4 uses an SPI to control one MCP23S17 for buttons/switches/relays/leds and two ADE7953 for energy monitoring and a second SPI for the display + * To use display enable defines USE_DISPLAY, USE_UNIVERSAL_DISPLAY and SHOW_SPLASH. Load file ST7735S_Pro4PM_display.ini as display.ini +\*********************************************************************************************/ + +#define XDRV_88 88 + +#define SHELLY_PRO_PIN_LAN8720_RESET 5 +#define SHELLY_PRO_4_PIN_SPI_CS 16 +#define SHELLY_PRO_4_PIN_MCP23S17_INT 35 +#define SHELLY_PRO_4_MCP23S17_ADDRESS 0x40 + +struct SPro { + uint32_t last_update; + uint16_t input_state; + int8_t switch_offset; + int8_t button_offset; + uint8_t pin_register_cs; + uint8_t pin_mcp23s17_int; + uint8_t ledlink; + uint8_t power; + bool init_done; + uint8_t detected; +} SPro; + +/*********************************************************************************************\ + * Shelly Pro MCP23S17 support +\*********************************************************************************************/ + +enum SP4MCP23X17GPIORegisters { + // A side + SP4_MCP23S17_IODIRA = 0x00, + SP4_MCP23S17_IPOLA = 0x02, + SP4_MCP23S17_GPINTENA = 0x04, + SP4_MCP23S17_DEFVALA = 0x06, + SP4_MCP23S17_INTCONA = 0x08, + SP4_MCP23S17_IOCONA = 0x0A, + SP4_MCP23S17_GPPUA = 0x0C, + SP4_MCP23S17_INTFA = 0x0E, + SP4_MCP23S17_INTCAPA = 0x10, + SP4_MCP23S17_GPIOA = 0x12, + SP4_MCP23S17_OLATA = 0x14, + // B side + SP4_MCP23S17_IODIRB = 0x01, + SP4_MCP23S17_IPOLB = 0x03, + SP4_MCP23S17_GPINTENB = 0x05, + SP4_MCP23S17_DEFVALB = 0x07, + SP4_MCP23S17_INTCONB = 0x09, + SP4_MCP23S17_IOCONB = 0x0B, + SP4_MCP23S17_GPPUB = 0x0D, + SP4_MCP23S17_INTFB = 0x0F, + SP4_MCP23S17_INTCAPB = 0x11, + SP4_MCP23S17_GPIOB = 0x13, + SP4_MCP23S17_OLATB = 0x15, +}; + +uint8_t sp4_mcp23s17_olata = 0; +uint8_t sp4_mcp23s17_olatb = 0; + +bool sp4_spi_busy; + +void SP4Mcp23S17Enable(void) { + sp4_spi_busy = true; + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); + digitalWrite(SPro.pin_register_cs, 0); +} + +void SP4Mcp23S17Disable(void) { + SPI.endTransaction(); + digitalWrite(SPro.pin_register_cs, 1); + sp4_spi_busy = false; +} + +uint32_t SP4Mcp23S17Read16(uint8_t reg) { + // Read 16-bit registers: (regb << 8) | rega + SP4Mcp23S17Enable(); + SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS | 1); + SPI.transfer(reg); + uint32_t value = SPI.transfer(0xFF); // RegA + value |= (SPI.transfer(0xFF) << 8); // RegB + SP4Mcp23S17Disable(); + return value; +} + +uint32_t SP4Mcp23S17Read(uint8_t reg) { + SP4Mcp23S17Enable(); + SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS | 1); + SPI.transfer(reg); + uint32_t value = SPI.transfer(0xFF); + SP4Mcp23S17Disable(); + return value; +} + +void SP4Mcp23S17Write(uint8_t reg, uint8_t value) { + SP4Mcp23S17Enable(); + SPI.transfer(SHELLY_PRO_4_MCP23S17_ADDRESS); + SPI.transfer(reg); + SPI.transfer(value); + SP4Mcp23S17Disable(); +} + +void SP4Mcp23S17Update(uint8_t pin, bool pin_value, uint8_t reg_addr) { + uint8_t bit = pin % 8; + uint8_t reg_value = 0; + if (reg_addr == SP4_MCP23S17_OLATA) { + reg_value = sp4_mcp23s17_olata; + } else if (reg_addr == SP4_MCP23S17_OLATB) { + reg_value = sp4_mcp23s17_olatb; + } else { + reg_value = SP4Mcp23S17Read(reg_addr); + } + if (pin_value) { + reg_value |= 1 << bit; + } else { + reg_value &= ~(1 << bit); + } + SP4Mcp23S17Write(reg_addr, reg_value); + if (reg_addr == SP4_MCP23S17_OLATA) { + sp4_mcp23s17_olata = reg_value; + } else if (reg_addr == SP4_MCP23S17_OLATB) { + sp4_mcp23s17_olatb = reg_value; + } +} + +void SP4Mcp23S17PinMode(uint8_t pin, uint8_t flags) { + uint8_t iodir = pin < 8 ? SP4_MCP23S17_IODIRA : SP4_MCP23S17_IODIRB; + uint8_t gppu = pin < 8 ? SP4_MCP23S17_GPPUA : SP4_MCP23S17_GPPUB; + if (flags == INPUT) { + SP4Mcp23S17Update(pin, true, iodir); + SP4Mcp23S17Update(pin, false, gppu); + } else if (flags == (INPUT | PULLUP)) { + SP4Mcp23S17Update(pin, true, iodir); + SP4Mcp23S17Update(pin, true, gppu); + } else if (flags == OUTPUT) { + SP4Mcp23S17Update(pin, false, iodir); + } +} + +bool SP4Mcp23S17DigitalRead(uint8_t pin) { + uint8_t bit = pin % 8; + uint8_t reg_addr = pin < 8 ? SP4_MCP23S17_GPIOA : SP4_MCP23S17_GPIOB; + uint8_t value = SP4Mcp23S17Read(reg_addr); + return value & (1 << bit); +} + +void SP4Mcp23S17DigitalWrite(uint8_t pin, bool value) { + uint8_t reg_addr = pin < 8 ? SP4_MCP23S17_OLATA : SP4_MCP23S17_OLATB; + SP4Mcp23S17Update(pin, value, reg_addr); +} + +/*********************************************************************************************\ + * Shelly Pro 4 +\*********************************************************************************************/ + +const uint8_t sp4_relay_pin[] = { 8, 13, 14, 12 }; +const uint8_t sp4_switch_pin[] = { 6, 1, 0, 15 }; +const uint8_t sp4_button_pin[] = { 5, 2, 3 }; + +void ShellyPro4Init(void) { + /* + Shelly Pro 4PM MCP23S17 registers + bit 0 = input, inverted - Switch3 + bit 1 = input, inverted - Switch2 + bit 2 = input - Button Down + bit 3 = input - Button OK + bit 4 = output - Reset, display, ADE7953 + bit 5 = input - Button Up + bit 6 = input, inverted - Switch1 + bit 7 + bit 8 = output - Relay O1 + bit 9 + bit 10 + bit 11 + bit 12 = output - Relay O4 + bit 13 = output - Relay O2 + bit 14 = output - Relay O3 + bit 15 = input, inverted - Switch4 + */ + SP4Mcp23S17Write(SP4_MCP23S17_IOCONA, 0b01011000); // Enable INT mirror, Slew rate disabled, HAEN pins for addressing + SP4Mcp23S17Write(SP4_MCP23S17_GPINTENA, 0x6F); // Enable interrupt on change + SP4Mcp23S17Write(SP4_MCP23S17_GPINTENB, 0x80); // Enable interrupt on change + + // Read current output register state + sp4_mcp23s17_olata = SP4Mcp23S17Read(SP4_MCP23S17_OLATA); + sp4_mcp23s17_olatb = SP4Mcp23S17Read(SP4_MCP23S17_OLATB); + + SP4Mcp23S17PinMode(4, OUTPUT); // Reset display, ADE7943 + SP4Mcp23S17DigitalWrite(4, 1); + + for (uint32_t i = 0; i < 3; i++) { + SP4Mcp23S17PinMode(sp4_button_pin[i], INPUT); // Button Up, Down, OK (RC with 10k to 3V3 and button shorting C) + } + SPro.button_offset = -1; + + for (uint32_t i = 0; i < 4; i++) { + SP4Mcp23S17PinMode(sp4_switch_pin[i], INPUT); // Switch1..4 + SP4Mcp23S17PinMode(sp4_relay_pin[i], OUTPUT); // Relay O1..O4 + } + SPro.switch_offset = -1; + + // Read current input register state + SPro.input_state = SP4Mcp23S17Read16(SP4_MCP23S17_GPIOA) & 0x806F; // Read gpio and clear interrupt + attachInterrupt(SPro.pin_mcp23s17_int, ShellyProUpdateIsr, CHANGE); +} + +void ShellyPro4Reset(void) { + SP4Mcp23S17DigitalWrite(4, 0); // Reset pin display, ADE7953 + delay(1); // To initiate a hardware reset, this pin must be brought low for a minimum of 10 μs. + SP4Mcp23S17DigitalWrite(4, 1); +} + +bool ShellyProAddButton(void) { + if (SPro.detected != 4) { return false; } // Only support Shelly Pro 4 + if (SPro.button_offset < 0) { SPro.button_offset = XdrvMailbox.index; } + uint32_t index = XdrvMailbox.index - SPro.button_offset; + if (index > 2) { return false; } // Support three buttons +/* + uint32_t state = bitRead(SPro.input_state, sp4_button_pin[index]); // 1 on power on and restart + AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Button default state %d"), state); + XdrvMailbox.index = state; +*/ + XdrvMailbox.index = 1; // 1 on power on and restart + return true; +} + +bool ShellyProAddSwitch(void) { + if (SPro.detected != 4) { return false; } // Only support Shelly Pro 4 + if (SPro.switch_offset < 0) { SPro.switch_offset = XdrvMailbox.index; } + uint32_t index = XdrvMailbox.index - SPro.switch_offset; + if (index > 3) { return false; } // Support four switches +/* + uint32_t state = bitRead(SPro.input_state, sp4_switch_pin[index]); // 0 on power on and restart + AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Switch default state %d"), state); + XdrvMailbox.index = state; +*/ + XdrvMailbox.index = 0; // 0 on power on and restart + return true; +} + +void ShellyProUpdateIsr(void) { + /* + The goal if this function is to minimize SPI and SetVirtualPinState calls + */ + uint32_t input_state = SP4Mcp23S17Read16(SP4_MCP23S17_INTCAPA); // Read intcap and clear interrupt + input_state &= 0x806F; // Only test input bits + +// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Input from %04X to %04X"), SPro.input_state, input_state); + + if (TasmotaGlobal.uptime < 3) { return; } // Flush interrupt for 3 seconds after poweron + + uint32_t mask = 1; + for (uint32_t j = 0; j < 16; j++) { + if ((input_state & mask) != (SPro.input_state & mask)) { + uint32_t state = (input_state >> j) &1; + +// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Change pin %d to %d"), j, state); + + for (uint32_t i = 0; i < 4; i++) { + if (j == sp4_switch_pin[i]) { + SwitchSetVirtualPinState(SPro.switch_offset +i, state); + } + else if ((i < 3) && (j == sp4_button_pin[i])) { + ButtonSetVirtualPinState(SPro.button_offset +i, state); + } + } + } + mask <<= 1; + } + SPro.input_state = input_state; +} + +bool ShellyProButton(void) { + if (SPro.detected != 4) { return false; } // Only support Shelly Pro 4 + + uint32_t button_index = XdrvMailbox.index - SPro.button_offset; + if (button_index > 2) { return false; } // Only support Up, Down, Ok + + uint32_t button = XdrvMailbox.payload; + uint32_t last_state = XdrvMailbox.command_code; + if ((PRESSED == button) && (NOT_PRESSED == last_state)) { // Button pressed + + AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Button %d pressed"), button_index +1); + + // Do something with the Up,Down,Ok button + switch (button_index) { + case 0: // Up + break; + case 1: // Down + break; + case 2: // Ok + break; + } + } + return true; // Disable further button processing +} + +/*********************************************************************************************\ + * Shelly Pro 1/2 +\*********************************************************************************************/ + +void ShellyProUpdate(void) { + /* + Shelly Pro 1/2/PM 74HC595 register + bit 0 = relay/led 1 + bit 1 = relay/led 2 + bit 2 = wifi led blue + bit 3 = wifi led green + bit 4 = wifi led red + bit 5 - 7 = nc + OE is connected to Gnd with 470 ohm resistor R62 AND a capacitor C81 to 3V3 + - this inhibits output of signals (also relay state) during power on for a few seconds + */ + uint8_t val = SPro.power | SPro.ledlink; + SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); + SPI.transfer(val); // Write 74HC595 shift register + SPI.endTransaction(); +// delayMicroseconds(2); // Wait for SPI clock to stop + digitalWrite(SPro.pin_register_cs, 1); // Latch data + delayMicroseconds(1); // Shelly 10mS + digitalWrite(SPro.pin_register_cs, 0); +} + +/*********************************************************************************************\ + * Shelly Pro +\*********************************************************************************************/ + +void ShellyProPreInit(void) { + if ((SPI_MOSI_MISO == TasmotaGlobal.spi_enabled) && + PinUsed(GPIO_SPI_CS) && // 74HC595 rclk / MCP23S17 + TasmotaGlobal.gpio_optiona.shelly_pro) { // Option_A7 + + if (PinUsed(GPIO_SWT1) || PinUsed(GPIO_KEY1)) { + SPro.detected = 1; // Shelly Pro 1 + if (PinUsed(GPIO_SWT1, 1) || PinUsed(GPIO_KEY1, 1)) { + SPro.detected = 2; // Shelly Pro 2 + } + SPro.ledlink = 0x18; // Blue led on - set by first call ShellyProPower() - Shelly 1/2 + } + if (SHELLY_PRO_4_PIN_SPI_CS == Pin(GPIO_SPI_CS)) { + SPro.detected = 4; // Shelly Pro 4PM (No SWT or KEY) + } + + if (SPro.detected) { + UpdateDevicesPresent(SPro.detected); // Shelly Pro 1 + + SPro.pin_register_cs = Pin(GPIO_SPI_CS); + digitalWrite(SPro.pin_register_cs, (4 == SPro.detected) ? 1 : 0); // Prep 74HC595 rclk + pinMode(SPro.pin_register_cs, OUTPUT); + // Does nothing if SPI is already initiated (by ADE7953) so no harm done + SPI.begin(Pin(GPIO_SPI_CLK), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_MOSI), -1); + + if (4 == SPro.detected) { + SPro.pin_mcp23s17_int = SHELLY_PRO_4_PIN_MCP23S17_INT; // GPIO35 = MCP23S17 common interrupt + pinMode(SPro.pin_mcp23s17_int, INPUT); + ShellyPro4Init(); // Init MCP23S17 + } + } + } +} + +void ShellyProInit(void) { + int pin_lan_reset = SHELLY_PRO_PIN_LAN8720_RESET; // GPIO5 = LAN8720 nRST +// delay(30); // (t-purstd) This pin must be brought low for a minimum of 25 mS after power on + digitalWrite(pin_lan_reset, 0); + pinMode(pin_lan_reset, OUTPUT); + delay(1); // (t-rstia) This pin must be brought low for a minimum of 100 uS + digitalWrite(pin_lan_reset, 1); + + AddLog(LOG_LEVEL_INFO, PSTR("HDW: Shelly Pro %d%s initialized"), + SPro.detected, (PinUsed(GPIO_ADE7953_CS))?"PM":""); + + SPro.init_done = true; +} + +void ShellyProPower(void) { + if (SPro.detected != 4) { + SPro.power = XdrvMailbox.index &3; + ShellyProUpdate(); + } else { + +// AddLog(LOG_LEVEL_DEBUG, PSTR("SHP: Set Power 0x%08X"), XdrvMailbox.index); + + power_t rpower = XdrvMailbox.index; +/* + for (uint32_t i = 0; i < 4; i++) { + power_t state = rpower &1; + SP4Mcp23S17DigitalWrite(sp4_relay_pin[i], state); // 4 SPI writes + rpower >>= 1; // Select next power + } +*/ + for (uint32_t i = 0; i < 4; i++) { + power_t state = rpower &1; + uint32_t bit = sp4_relay_pin[i] -8; // Adjust by 8 bits + bitWrite(sp4_mcp23s17_olatb, bit, state); + rpower >>= 1; // Select next power + } + SP4Mcp23S17Write(SP4_MCP23S17_OLATB, sp4_mcp23s17_olatb); // 1 SPI write + + } +} + +void ShellyProUpdateLedLink(uint32_t ledlink) { + if (ledlink != SPro.ledlink) { + SPro.ledlink = ledlink; + ShellyProUpdate(); + } +} + +void ShellyProLedLink(void) { + if (!SPro.init_done) { return; } // Block write before first power update + if (SPro.detected != 4) { + /* + bit 2 = blue, 3 = green, 4 = red + Shelly Pro documentation + - Blue light indicator will be on if in AP mode. + - Red light indicator will be on if in STA mode and not connected to a Wi-Fi network. + - Yellow light indicator will be on if in STA mode and connected to a Wi-Fi network. + - Green light indicator will be on if in STA mode and connected to a Wi-Fi network and to the Shelly Cloud. + - The light indicator will be flashing Red/Blue if OTA update is in progress. + Tasmota behaviour + - Blue light indicator will blink if no wifi or mqtt. + - Green light indicator will be on if in STA mode and connected to a Wi-Fi network. + */ + SPro.last_update = TasmotaGlobal.uptime; + uint32_t ledlink = 0x1C; // All leds off + if (XdrvMailbox.index) { + ledlink &= 0xFB; // Blue blinks if wifi/mqtt lost + } + else if (!TasmotaGlobal.global_state.wifi_down) { + ledlink &= 0xF7; // Green On + } + ShellyProUpdateLedLink(ledlink); + } +} + +void ShellyProLedLinkWifiOff(void) { + if (!SPro.init_done) { return; } + if (SPro.detected != 4) { + /* + bit 2 = blue, 3 = green, 4 = red + - Green light indicator will be on if in STA mode and connected to a Wi-Fi network. + */ + if (SPro.last_update +1 < TasmotaGlobal.uptime) { + ShellyProUpdateLedLink((TasmotaGlobal.global_state.wifi_down) ? 0x1C : 0x14); // Green off if wifi OFF + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv88(uint32_t function) { + bool result = false; + + if (FUNC_MODULE_INIT == function) { + ShellyProPreInit(); + } else if (SPro.detected) { + switch (function) { +/* + case FUNC_BUTTON_PRESSED: + result = ShellyProButton(); + break; +*/ + case FUNC_EVERY_SECOND: + ShellyProLedLinkWifiOff(); + break; + case FUNC_SET_POWER: + ShellyProPower(); + break; + case FUNC_INIT: + ShellyProInit(); + break; + case FUNC_ADD_BUTTON: + result = ShellyProAddButton(); + break; + case FUNC_ADD_SWITCH: + result = ShellyProAddSwitch(); + break; + case FUNC_LED_LINK: + ShellyProLedLink(); + break; + } + } + return result; +} + +#endif // USE_SHELLY_PRO +#endif // USE_SPI +#endif // ESP32