/*
xdrv_28_pcf8574.ino - PCF8574 I2C support for Tasmota
Copyright (C) 2020 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 .
*/
#ifdef USE_I2C
#ifdef USE_PCF8574
/*********************************************************************************************\
* PCF8574 - I2C IO Expander
*
* I2C Address: PCF8574 = 0x20 .. 0x27 (0x27 is not supported),
* PCF8574A = 0x39 .. 0x3F (0x38 is not supported)
\*********************************************************************************************/
#define XDRV_28 28
#define XI2C_02 2 // See I2CDEVICES.md
#define PCF8574_ADDR1 0x20 // PCF8574
#define PCF8574_ADDR2 0x38 // PCF8574A
struct PCF8574 {
int error;
uint8_t pin[64];
uint8_t address[MAX_PCF8574];
uint8_t pin_mask[MAX_PCF8574] = { 0 };
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;
} Pcf8574;
void Pcf8574SwitchRelay(void)
{
for (uint32_t i = 0; i < TasmotaGlobal.devices_present; i++) {
uint8_t relay_state = bitRead(XdrvMailbox.index, i);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574.max_devices %d requested pin %d"), Pcf8574.max_devices,Pcf8574.pin[i]);
if (Pcf8574.max_devices > 0 && Pcf8574.pin[i] < 99) {
uint8_t board = Pcf8574.pin[i]>>3;
uint8_t oldpinmask = Pcf8574.pin_mask[board];
uint8_t _val = bitRead(TasmotaGlobal.rel_inverted, i) ? !relay_state : relay_state;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574SwitchRelay %d on pin %d"), i,state);
if (_val) {
Pcf8574.pin_mask[board] |= _val << (Pcf8574.pin[i]&0x7);
} else {
Pcf8574.pin_mask[board] &= ~(1 << (Pcf8574.pin[i]&0x7));
}
if (oldpinmask != Pcf8574.pin_mask[board]) {
Wire.beginTransmission(Pcf8574.address[board]);
Wire.write(Pcf8574.pin_mask[board]);
Pcf8574.error = Wire.endTransmission();
}
//pcf8574.write(Pcf8574.pin[i]&0x7, TasmotaGlobal.rel_inverted[i] ? !state : state);
}
}
}
void Pcf8574Init(void)
{
uint8_t pcf8574_address = PCF8574_ADDR1;
while ((Pcf8574.max_devices < MAX_PCF8574) && (pcf8574_address < PCF8574_ADDR2 +8)) {
#ifdef USE_MCP230xx_ADDR
if (USE_MCP230xx_ADDR == pcf8574_address) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Address 0x%02x reserved for MCP320xx skipped"), pcf8574_address);
pcf8574_address++;
if ((PCF8574_ADDR1 +7) == pcf8574_address) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 +1;
}
}
#endif
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address);
if (I2cSetDevice(pcf8574_address)) {
Pcf8574.type = true;
Pcf8574.address[Pcf8574.max_devices] = pcf8574_address;
Pcf8574.max_devices++;
strcpy(Pcf8574.stype, "PCF8574");
if (pcf8574_address >= PCF8574_ADDR2) {
strcpy(Pcf8574.stype, "PCF8574A");
}
I2cSetActiveFound(pcf8574_address, Pcf8574.stype);
}
pcf8574_address++;
if ((PCF8574_ADDR1 +7) == pcf8574_address) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 +1;
}
}
if (Pcf8574.type) {
for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) {
Pcf8574.pin[i] = 99;
}
TasmotaGlobal.devices_present = TasmotaGlobal.devices_present - 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
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Device %d config 0x%02x"), idx +1, Settings.pcf8574_config[idx]);
for (uint32_t i = 0; i < 8; i++) {
uint8_t _result = Settings.pcf8574_config[idx] >> i &1;
//AddLog_P2(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
TasmotaGlobal.devices_present++;
Pcf8574.max_connected_ports++;
}
}
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), Pcf8574.max_devices, Pcf8574.max_connected_ports);
}
}
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
#ifdef USE_WEBSERVER
#define WEB_HANDLE_PCF8574 "pcf"
const char HTTP_BTN_MENU_PCF8574[] PROGMEM =
"