From aa5b5e891e6be031f231079bf927ccc8759b6e7b Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Tue, 1 Oct 2019 16:33:39 +0200 Subject: [PATCH] Add initial support for PCF8574 I2C I/O Expander Add initial support for PCF8574 I2C I/O Expander (currently output only) by Stefan Bode --- sonoff/_changelog.ino | 1 + sonoff/settings.h | 8 +- sonoff/sonoff.h | 1 + sonoff/xdrv_27_shutter.ino | 8 +- sonoff/xdrv_28_pcf8574.ino | 248 +++++++++++++++++++++++++++++++++++++ 5 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 sonoff/xdrv_28_pcf8574.ino diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino index 3f88ba690..7739e9a0b 100644 --- a/sonoff/_changelog.ino +++ b/sonoff/_changelog.ino @@ -6,6 +6,7 @@ * Add Zigbee more support - collect endpoints and clusters, added ZigbeeDump command * Add initial support for shutters by Stefan Bode (#288) * Add command to MCP230xx: sensor29 pin,0/1/2 for OFF/ON/TOGGLE + * Add initial support for PCF8574 I2C I/O Expander (currently output only) by Stefan Bode * * 6.6.0.13 20190922 * Add command EnergyReset4 x,x to initialize total usage for two tarrifs diff --git a/sonoff/settings.h b/sonoff/settings.h index c11926855..4f808586d 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -93,8 +93,8 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t spare27 : 1; uint32_t spare28 : 1; uint32_t spare29 : 1; - uint32_t shutter_mode : 1; // bit 30 (v6.6.0.15) - SetOption80 - Enable shutter support - uint32_t spare31 : 1; + uint32_t shutter_mode : 1; // bit 30 (v6.6.0.14) - SetOption80 - Enable shutter support + uint32_t pcf8574_ports_inverted : 1; // bit 31 (v6.6.0.14) - SetOption81 - Invert all ports on PCF8574 devices }; } SysBitfield3; @@ -376,7 +376,6 @@ struct SYSCFG { uint16_t ina226_r_shunt[4]; // E20 uint16_t ina226_i_fs[4]; // E28 uint16_t tariff[4][2]; // E30 - uint16_t shutter_opentime[MAX_SHUTTERS]; // E40 uint16_t shutter_closetime[MAX_SHUTTERS]; // E48 int16_t shuttercoeff[5][MAX_SHUTTERS]; // E50 @@ -384,8 +383,9 @@ struct SYSCFG { uint8_t shutter_set50percent[MAX_SHUTTERS]; // E7C uint8_t shutter_position[MAX_SHUTTERS]; // E80 uint8_t shutter_startrelay[MAX_SHUTTERS]; // E84 + uint8_t pcf8574_config[MAX_PCF8574]; // E88 - uint8_t free_e88[368]; // E88 + uint8_t free_e90[360]; // E90 uint32_t cfg_timestamp; // FF8 uint32_t cfg_crc32; // FFC diff --git a/sonoff/sonoff.h b/sonoff/sonoff.h index 5ee19873c..46c0868e2 100644 --- a/sonoff/sonoff.h +++ b/sonoff/sonoff.h @@ -68,6 +68,7 @@ const uint8_t MAX_XDSP_DRIVERS = 32; // Max number of allowed display dri const uint8_t MAX_XDRV_DRIVERS = 96; // Max number of allowed driver drivers const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor drivers const uint8_t MAX_SHUTTERS = 4; // Max number of shutters +const uint8_t MAX_PCF8574 = 8; // Max number of PCF8574 devices const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules diff --git a/sonoff/xdrv_27_shutter.ino b/sonoff/xdrv_27_shutter.ino index e7eaa4250..989a522bd 100644 --- a/sonoff/xdrv_27_shutter.ino +++ b/sonoff/xdrv_27_shutter.ino @@ -107,8 +107,6 @@ void ShutterInit(void) Shutter.mask = 0; //Initialize to get relay that changed Shutter.old_power = power; - char shutter_open_chr[10]; - char shutter_close_chr[10]; bool relay_in_interlock = false; AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy); @@ -164,9 +162,11 @@ void ShutterInit(void) Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i); //Shutter.real_position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i]; Shutter.start_position[i] = Shutter.real_position[i]; - dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr); - dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr); + char shutter_open_chr[10]; + dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr); + char shutter_close_chr[10]; + dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr); AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d"), i, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr, Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i], diff --git a/sonoff/xdrv_28_pcf8574.ino b/sonoff/xdrv_28_pcf8574.ino new file mode 100644 index 000000000..6b651a296 --- /dev/null +++ b/sonoff/xdrv_28_pcf8574.ino @@ -0,0 +1,248 @@ +/* + xdrv_28_pcf8574.ino - PCF8574 I2C support for Sonoff-Tasmota + + Copyright (C) 2019 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, PCF8574A = 0x38 .. 0x3F +\*********************************************************************************************/ + +#define XDRV_28 28 + +#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[8]; + bool type = true; +} Pcf8574; + +void Pcf8574SwitchRelay(void) +{ + for (uint32_t i = 0; i < 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(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, rel_inverted[i] ? !state : state); + } + } +} + +void Pcf8574Init() +{ + Pcf8574.type = false; + + uint8_t pcf8574_address = PCF8574_ADDR1; + for (uint32_t i = 0; i < MAX_PCF8574; i++) { + + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address); + + if (I2cDevice(pcf8574_address)) { + I2cSetActive(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"); + } + AddLog_P2(LOG_LEVEL_DEBUG, S_LOG_I2C_FOUND_AT, Pcf8574.stype, pcf8574_address); + } + pcf8574_address++; + if ((PCF8574_ADDR1 + 8) == pcf8574_address) { + pcf8574_address = PCF8574_ADDR2; + } + } + if (Pcf8574.max_devices) { + for (uint32_t i = 0; i < sizeof(Pcf8574.pin); i++) { + Pcf8574.pin[i] = 99; + } + devices_present = 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, devices_present: %d"), i,_result, Settings.power>>i&1, devices_present); + if (_result > 0) { + Pcf8574.pin[devices_present] = i + 8 * idx; + bitWrite(rel_inverted, devices_present, Settings.flag3.pcf8574_ports_inverted); + devices_present++; + Pcf8574.max_connected_ports++; + } + } + } + AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: Total devices %d, PCF8574 output ports %d"), devices_present, Pcf8574.max_connected_ports); + } +} + +/*********************************************************************************************\ + * Presentation +\*********************************************************************************************/ + +#ifdef USE_WEBSERVER + +#define WEB_HANDLE_PCF8574 "pcf" + +const char HTTP_BTN_MENU_PCF8574[] PROGMEM = + "

"; + +const char HTTP_FORM_I2C_PCF8574_1[] PROGMEM = + "
 " D_PCF8574_PARAMETERS " " + "
" + "

" D_INVERT_PORTS "


"; + +const char HTTP_FORM_I2C_PCF8574_2[] PROGMEM = + "" D_DEVICE " %d " D_PORT " %d"; + +void HandlePcf8574(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_PCF8574)); + + if (WebServer->hasArg("save")) { + Pcf8574SaveSettings(); + WebRestart(1); + return; + } + + WSContentStart_P(D_CONFIGURE_PCF8574); + WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : ""); + WSContentSend_P(HTTP_TABLE100); + for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { + for (uint32_t idx2 = 0; idx2 < 8; idx2++) { // 8 ports on PCF8574 + uint8_t helper = 1 << idx2; + WSContentSend_P(HTTP_FORM_I2C_PCF8574_2, + idx +1, idx2, + idx2 + 8*idx, + idx2 + 8*idx, + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ", + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " " + ); + } + } + WSContentSend_P(PSTR("")); + WSContentSend_P(HTTP_FORM_END); + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + +void Pcf8574SaveSettings() +{ + char stemp[7]; + char tmp[100]; + + //AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Start working on Save arguements: inverted:%d")), WebServer->hasArg("b1"); + + Settings.flag3.pcf8574_ports_inverted = WebServer->hasArg("b1"); + for (byte idx = 0; idx < Pcf8574.max_devices; idx++) { + byte count=0; + byte n = Settings.pcf8574_config[idx]; + while(n!=0) { + n = n&(n-1); + count++; + } + if (count <= devices_present) { + devices_present = devices_present - count; + } + for (byte i = 0; i < 8; i++) { + snprintf_P(stemp, sizeof(stemp), PSTR("i2cs%d"), i+8*idx); + WebGetArg(stemp, tmp, sizeof(tmp)); + byte _value = (!strlen(tmp)) ? 0 : atoi(tmp); + if (_value) { + Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] | 1 << i; + devices_present++; + Pcf8574.max_connected_ports++; + } else { + Settings.pcf8574_config[idx] = Settings.pcf8574_config[idx] & ~(1 << i ); + } + } + //Settings.pcf8574_config[0] = (!strlen(webServer->arg("i2cs0").c_str())) ? 0 : atoi(webServer->arg("i2cs0").c_str()); + //AddLog_P2(LOG_LEVEL_INFO, PSTR("PCF: I2C Board: %d, Config: %2x")), idx, Settings.pcf8574_config[idx]; + + } +} +#endif // USE_WEBSERVER + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv28(uint8_t function) +{ + bool result = false; + + if (i2c_flg && Pcf8574.type) { + switch (function) { + case FUNC_SET_POWER: + Pcf8574SwitchRelay(); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_MENU_PCF8574); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/" WEB_HANDLE_PCF8574, HandlePcf8574); + break; +#endif // USE_WEBSERVER + case FUNC_PRE_INIT: + Pcf8574Init(); + break; + } + } + return result; +} + +#endif // USE_PCF8574 +#endif // USE_I2C