2019-10-01 15:33:39 +01:00
/*
2019-10-27 10:13:24 +00:00
xdrv_28_pcf8574 . ino - PCF8574 I2C support for Tasmota
2019-10-01 15:33:39 +01:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Stefan Bode
2019-10-01 15:33:39 +01:00
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 USE_I2C
# ifdef USE_PCF8574
/*********************************************************************************************\
* PCF8574 - I2C IO Expander
*
2019-11-08 16:48:19 +00:00
* I2C Address : PCF8574 = 0x20 . . 0x27 ( 0x27 is not supported ) ,
* PCF8574A = 0x39 . . 0x3F ( 0x38 is not supported )
2019-10-01 15:33:39 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define XDRV_28 28
2019-11-03 16:54:39 +00:00
# define XI2C_02 2 // See I2CDEVICES.md
2019-10-01 15:33:39 +01:00
# 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
2019-10-13 16:54:06 +01:00
char stype [ 9 ] ;
2019-11-11 16:32:44 +00:00
bool type = false ;
2019-10-01 15:33:39 +01:00
} Pcf8574 ;
void Pcf8574SwitchRelay ( void )
{
2020-10-30 11:29:48 +00:00
for ( uint32_t i = 0 ; i < TasmotaGlobal . devices_present ; i + + ) {
2019-10-01 15:33:39 +01:00
uint8_t relay_state = bitRead ( XdrvMailbox . index , i ) ;
2020-11-06 16:09:13 +00:00
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574.max_devices %d requested pin %d"), Pcf8574.max_devices,Pcf8574.pin[i]);
2019-10-01 15:33:39 +01:00
if ( Pcf8574 . max_devices > 0 & & Pcf8574 . pin [ i ] < 99 ) {
uint8_t board = Pcf8574 . pin [ i ] > > 3 ;
uint8_t oldpinmask = Pcf8574 . pin_mask [ board ] ;
2020-10-28 18:03:39 +00:00
uint8_t _val = bitRead ( TasmotaGlobal . rel_inverted , i ) ? ! relay_state : relay_state ;
2019-10-01 15:33:39 +01:00
2020-11-06 16:09:13 +00:00
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Pcf8574SwitchRelay %d on pin %d"), i,state);
2019-10-01 15:33:39 +01:00
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 ( ) ;
}
2020-10-28 18:03:39 +00:00
//pcf8574.write(Pcf8574.pin[i]&0x7, TasmotaGlobal.rel_inverted[i] ? !state : state);
2019-10-01 15:33:39 +01:00
}
}
}
2019-11-20 19:53:12 +00:00
void Pcf8574Init ( void )
2019-10-01 15:33:39 +01:00
{
uint8_t pcf8574_address = PCF8574_ADDR1 ;
2019-11-08 16:48:19 +00:00
while ( ( Pcf8574 . max_devices < MAX_PCF8574 ) & & ( pcf8574_address < PCF8574_ADDR2 + 8 ) ) {
2019-10-01 15:33:39 +01:00
2020-03-27 15:34:00 +00:00
# ifdef USE_MCP230xx_ADDR
if ( USE_MCP230xx_ADDR = = pcf8574_address ) {
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_INFO , PSTR ( " PCF: Address 0x%02x reserved for MCP320xx skipped " ) , pcf8574_address ) ;
2020-03-27 15:34:00 +00:00
pcf8574_address + + ;
if ( ( PCF8574_ADDR1 + 7 ) = = pcf8574_address ) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 + 1 ;
}
}
# endif
2020-11-06 16:09:13 +00:00
// AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Probing addr: 0x%x for PCF8574"), pcf8574_address);
2019-10-01 15:33:39 +01:00
2019-11-06 16:48:38 +00:00
if ( I2cSetDevice ( pcf8574_address ) ) {
2019-10-01 15:33:39 +01:00
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 " ) ;
}
2019-11-09 17:34:22 +00:00
I2cSetActiveFound ( pcf8574_address , Pcf8574 . stype ) ;
2019-10-01 15:33:39 +01:00
}
2019-11-08 16:48:19 +00:00
2019-10-01 15:33:39 +01:00
pcf8574_address + + ;
2019-11-08 16:48:19 +00:00
if ( ( PCF8574_ADDR1 + 7 ) = = pcf8574_address ) { // Support I2C addresses 0x20 to 0x26 and 0x39 to 0x3F
pcf8574_address = PCF8574_ADDR2 + 1 ;
2019-10-01 15:33:39 +01:00
}
}
2019-11-08 16:48:19 +00:00
if ( Pcf8574 . type ) {
2019-10-01 15:33:39 +01:00
for ( uint32_t i = 0 ; i < sizeof ( Pcf8574 . pin ) ; i + + ) {
Pcf8574 . pin [ i ] = 99 ;
}
2020-10-30 11:29:48 +00:00
TasmotaGlobal . devices_present = TasmotaGlobal . devices_present - Pcf8574 . max_connected_ports ; // reset no of devices to avoid duplicate ports on duplicate init.
2019-10-01 15:33:39 +01:00
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
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( " PCF: Device %d config 0x%02x " ) , idx + 1 , Settings . pcf8574_config [ idx ] ) ;
2019-10-01 15:33:39 +01:00
for ( uint32_t i = 0 ; i < 8 ; i + + ) {
uint8_t _result = Settings . pcf8574_config [ idx ] > > i & 1 ;
2020-11-06 16:09:13 +00:00
//AddLog_P(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);
2019-10-01 15:33:39 +01:00
if ( _result > 0 ) {
2020-10-30 11:29:48 +00:00
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 + + ;
2019-10-01 15:33:39 +01:00
Pcf8574 . max_connected_ports + + ;
}
}
}
2020-11-06 16:09:13 +00:00
AddLog_P ( LOG_LEVEL_INFO , PSTR ( " PCF: Total devices %d, PCF8574 output ports %d " ) , Pcf8574 . max_devices , Pcf8574 . max_connected_ports ) ;
2019-10-01 15:33:39 +01:00
}
}
/*********************************************************************************************\
* Presentation
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# ifdef USE_WEBSERVER
# define WEB_HANDLE_PCF8574 "pcf"
const char HTTP_BTN_MENU_PCF8574 [ ] PROGMEM =
" <p><form action=' " WEB_HANDLE_PCF8574 " ' method='get'><button> " D_CONFIGURE_PCF8574 " </button></form></p> " ;
const char HTTP_FORM_I2C_PCF8574_1 [ ] PROGMEM =
" <fieldset><legend><b> " D_PCF8574_PARAMETERS " </b></legend> "
" <form method='get' action=' " WEB_HANDLE_PCF8574 " '> "
2020-04-05 13:11:49 +01:00
" <p><label><input id='b1' name='b1' type='checkbox'%s><b> " D_INVERT_PORTS " </b></label></p><hr/> " ;
2019-10-01 15:33:39 +01:00
const char HTTP_FORM_I2C_PCF8574_2 [ ] PROGMEM =
" <tr><td><b> " D_DEVICE " %d " D_PORT " %d</b></td><td style='width:100px'><select id='i2cs%d' name='i2cs%d'> "
" <option%s value='0'> " D_DEVICE_INPUT " </option> "
" <option%s value='1'> " D_DEVICE_OUTPUT " </option> "
" </select></td></tr> " ;
void HandlePcf8574 ( void )
{
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
AddLog_P ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_HTTP D_CONFIGURE_PCF8574 ) ) ;
2020-04-15 08:58:38 +01:00
if ( Webserver - > hasArg ( " save " ) ) {
2019-10-01 15:33:39 +01:00
Pcf8574SaveSettings ( ) ;
WebRestart ( 1 ) ;
return ;
}
WSContentStart_P ( D_CONFIGURE_PCF8574 ) ;
WSContentSendStyle ( ) ;
2021-01-13 07:27:40 +00:00
WSContentSend_P ( HTTP_FORM_I2C_PCF8574_1 , ( Settings . flag3 . pcf8574_ports_inverted ) ? PSTR ( " checked " ) : " " ) ; // SetOption81 - Invert all ports on PCF8574 devices
2019-10-01 15:33:39 +01:00
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 ,
2021-01-12 18:31:15 +00:00
( ( helper & Settings . pcf8574_config [ idx ] ) > > idx2 = = 0 ) ? PSTR ( " selected " ) : " " ,
( ( helper & Settings . pcf8574_config [ idx ] ) > > idx2 = = 1 ) ? PSTR ( " selected " ) : " "
2019-10-01 15:33:39 +01:00
) ;
}
}
WSContentSend_P ( PSTR ( " </table> " ) ) ;
WSContentSend_P ( HTTP_FORM_END ) ;
WSContentSpaceButton ( BUTTON_CONFIGURATION ) ;
WSContentStop ( ) ;
}
2019-11-20 19:53:12 +00:00
void Pcf8574SaveSettings ( void )
2019-10-01 15:33:39 +01:00
{
char stemp [ 7 ] ;
char tmp [ 100 ] ;
2020-04-15 08:58:38 +01:00
//AddLog_P(LOG_LEVEL_DEBUG, PSTR("PCF: Start working on Save arguements: inverted:%d")), Webserver->hasArg("b1");
2019-10-01 15:33:39 +01:00
2020-04-15 08:58:38 +01:00
Settings . flag3 . pcf8574_ports_inverted = Webserver - > hasArg ( " b1 " ) ; // SetOption81 - Invert all ports on PCF8574 devices
2019-10-01 15:33:39 +01:00
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 + + ;
}
2020-10-30 11:29:48 +00:00
if ( count < = TasmotaGlobal . devices_present ) {
TasmotaGlobal . devices_present = TasmotaGlobal . devices_present - count ;
2019-10-01 15:33:39 +01:00
}
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 ;
2020-10-30 11:29:48 +00:00
TasmotaGlobal . devices_present + + ;
2019-10-01 15:33:39 +01:00
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());
2020-11-06 16:09:13 +00:00
//AddLog_P(LOG_LEVEL_INFO, PSTR("PCF: I2C Board: %d, Config: %2x")), idx, Settings.pcf8574_config[idx];
2019-10-01 15:33:39 +01:00
}
}
# endif // USE_WEBSERVER
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv28 ( uint8_t function )
{
2019-11-11 16:32:44 +00:00
if ( ! I2cEnabled ( XI2C_02 ) ) { return false ; }
2019-11-03 16:54:39 +00:00
2019-10-01 15:33:39 +01:00
bool result = false ;
2019-11-11 16:32:44 +00:00
if ( FUNC_PRE_INIT = = function ) {
Pcf8574Init ( ) ;
}
else if ( Pcf8574 . type ) {
switch ( function ) {
case FUNC_SET_POWER :
Pcf8574SwitchRelay ( ) ;
break ;
2019-10-01 15:33:39 +01:00
# ifdef USE_WEBSERVER
2019-11-11 16:32:44 +00:00
case FUNC_WEB_ADD_BUTTON :
WSContentSend_P ( HTTP_BTN_MENU_PCF8574 ) ;
break ;
case FUNC_WEB_ADD_HANDLER :
2020-10-20 17:56:18 +01:00
WebServer_on ( PSTR ( " / " WEB_HANDLE_PCF8574 ) , HandlePcf8574 ) ;
2019-11-11 16:32:44 +00:00
break ;
2019-10-01 15:33:39 +01:00
# endif // USE_WEBSERVER
2019-11-11 16:32:44 +00:00
}
2019-10-01 15:33:39 +01:00
}
return result ;
}
# endif // USE_PCF8574
# endif // USE_I2C