Breaking change Shelly Pro 4PM

- Add support for multiple MCP23017/MCP23S17 as switch/button/relay
- Breaking change Shelly Pro 4PM using standard MCP23xxx driver
This commit is contained in:
Theo Arends 2023-03-01 16:52:34 +01:00
parent 63408f3a7d
commit 159d1ae312
6 changed files with 642 additions and 324 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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