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 } ;
2021-03-11 16:28:35 +00:00
# ifdef USE_PCF8574_MQTTINPUT
uint8_t last_input [ MAX_PCF8574 ] = { 0 } ;
# endif
2019-10-01 15:33:39 +01:00
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 ;
2021-03-11 16:28:35 +00:00
uint8_t Pcf8574Read ( uint8_t idx )
{
Wire . requestFrom ( Pcf8574 . address [ idx ] , ( uint8_t ) 1 ) ;
return Wire . read ( ) ;
}
uint8_t Pcf8574Write ( uint8_t idx )
{
Wire . beginTransmission ( Pcf8574 . address [ idx ] ) ;
Wire . write ( Pcf8574 . pin_mask [ idx ] ) ;
return Wire . endTransmission ( ) ;
}
2019-10-01 15:33:39 +01:00
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 ) ;
if ( Pcf8574 . max_devices > 0 & & Pcf8574 . pin [ i ] < 99 ) {
uint8_t board = Pcf8574 . pin [ i ] > > 3 ;
2021-03-11 16:28:35 +00:00
uint8_t pin = Pcf8574 . pin [ i ] & 0x7 ;
2019-10-01 15:33:39 +01:00
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
2021-03-11 16:28:35 +00:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: SwitchRelay %d=%d => PCF-%d.D%d=%d"), i, relay_state, board +1, pin, _val);
bitWrite ( Pcf8574 . pin_mask [ board ] , pin , _val ) ;
2019-10-01 15:33:39 +01:00
if ( oldpinmask ! = Pcf8574 . pin_mask [ board ] ) {
2021-03-11 16:28:35 +00:00
Pcf8574 . error = Pcf8574Write ( board ) ;
2019-10-01 15:33:39 +01:00
}
2021-03-11 16:28:35 +00:00
//else AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: SwitchRelay skipped"));
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 ) {
2021-01-23 15:26:23 +00:00
AddLog ( 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
2021-01-23 15:26:23 +00:00
// AddLog(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
2021-03-11 16:28:35 +00:00
uint8_t gpio = Pcf8574Read ( idx ) ;
Pcf8574 . pin_mask [ idx ] = gpio ;
# ifdef USE_PCF8574_MQTTINPUT
2021-06-11 17:14:12 +01:00
Pcf8574 . last_input [ idx ] = gpio & ~ Settings - > pcf8574_config [ idx ] ;
2021-03-11 16:28:35 +00:00
# endif // #ifdef USE_PCF8574_MQTTINPUT
2021-06-11 17:14:12 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: PCF-%d config=0x%02x, gpio=0x%02X"), idx +1, Settings->pcf8574_config[idx], gpio);
2021-03-11 16:28:35 +00:00
for ( uint32_t i = 0 ; i < 8 ; i + + , gpio > > = 1 ) {
2021-06-11 17:14:12 +01:00
uint8_t _result = Settings - > pcf8574_config [ idx ] > > i & 1 ;
//AddLog(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 ;
2021-06-11 17:14:12 +01:00
bitWrite ( TasmotaGlobal . rel_inverted , TasmotaGlobal . devices_present , Settings - > flag3 . pcf8574_ports_inverted ) ; // SetOption81 - Invert all ports on PCF8574 devices
if ( ! Settings - > flag . save_state & & ! Settings - > flag3 . no_power_feedback ) { // SetOption63 - Don't scan relay power state at restart - #5594 and #5663
2021-03-11 16:28:35 +00:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Set power from from chip state"));
2021-06-11 17:14:12 +01:00
uint8_t power_state = Settings - > flag3 . pcf8574_ports_inverted ? 1 & ~ gpio : 1 & gpio ;
2021-03-11 16:28:35 +00:00
bitWrite ( TasmotaGlobal . power , TasmotaGlobal . devices_present , power_state ) ;
2021-06-11 17:14:12 +01:00
bitWrite ( Settings - > power , TasmotaGlobal . devices_present , power_state ) ;
2021-03-11 16:28:35 +00:00
}
//else AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: DON'T set power from chip state"));
2020-10-30 11:29:48 +00:00
TasmotaGlobal . devices_present + + ;
2019-10-01 15:33:39 +01:00
Pcf8574 . max_connected_ports + + ;
}
}
}
2021-06-11 17:14:12 +01:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Settings->power=0x%08X, TasmotaGlobal.power=0x%08X"), Settings->power, TasmotaGlobal.power);
2021-01-23 15:26:23 +00:00
AddLog ( 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> " ;
2021-03-11 16:28:35 +00:00
const char HTTP_SNS_PCF8574_GPIO [ ] PROGMEM = " {s}PCF8574%c%d D%d{m}%d{e} " ; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
2019-10-01 15:33:39 +01:00
void HandlePcf8574 ( void )
{
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_HTTP D_CONFIGURE_PCF8574 ) ) ;
2019-10-01 15:33:39 +01:00
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-06-11 17:14:12 +01: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-06-11 17:14:12 +01: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 ( ) ;
}
2021-03-11 16:28:35 +00:00
# if defined(USE_PCF8574_SENSOR) || defined(USE_PCF8574_DISPLAYINPUT)
void Pcf8574Show ( bool json )
{
# ifdef USE_PCF8574_SENSOR
if ( json ) {
for ( int idx = 0 ; idx < Pcf8574 . max_devices ; idx + + )
{
uint8_t gpio = Pcf8574Read ( idx ) ;
2021-05-12 16:17:35 +01:00
ResponseAppend_P ( PSTR ( " , \" PCF8574%c%d \" :{ \" D0 \" :%i, \" D1 \" :%i, \" D2 \" :%i, \" D3 \" :%i, \" D4 \" :%i, \" D5 \" :%i, \" D6 \" :%i, \" D7 \" :%i} " ) ,
2021-06-14 13:27:35 +01:00
IndexSeparator ( ) , idx + 1 ,
( gpio > > 0 ) & 1 , ( gpio > > 1 ) & 1 , ( gpio > > 2 ) & 1 , ( gpio > > 3 ) & 1 , ( gpio > > 4 ) & 1 , ( gpio > > 5 ) & 1 , ( gpio > > 6 ) & 1 , ( gpio > > 7 ) & 1 ) ;
2021-03-11 16:28:35 +00:00
}
}
# endif // #ifdef USE_PCF8574_SENSOR
# if defined(USE_WEBSERVER) && defined(USE_PCF8574_DISPLAYINPUT)
if ( ! json ) {
for ( int idx = 0 ; idx < Pcf8574 . max_devices ; idx + + )
{
2021-06-11 17:14:12 +01:00
uint8_t input_mask = ~ Settings - > pcf8574_config [ idx ] ; //invert to 1 = input
2021-03-11 16:28:35 +00:00
uint8_t gpio = Pcf8574Read ( idx ) ;
for ( int pin = 0 ; pin < 8 ; + + pin , input_mask > > = 1 , gpio > > = 1 )
{
if ( input_mask & 1 )
WSContentSend_P ( HTTP_SNS_PCF8574_GPIO , IndexSeparator ( ) , idx + 1 , pin , gpio & 1 ) ;
}
}
}
# endif // defined(USE_WEBSERVER) && defined(USE_PCF8574_DISPLAYINPUT)
}
# endif // #if defined(USE_PCF8574_SENSOR) || defined(USE_PCF8574_DISPLAYINPUT)
# ifdef USE_PCF8574_MQTTINPUT
void Pcf8574CheckForInputChange ( void )
{
for ( int idx = 0 ; idx < Pcf8574 . max_devices ; idx + + )
{
2021-06-11 17:14:12 +01:00
uint8_t input_mask = ~ Settings - > pcf8574_config [ idx ] ; //invert to 1 = input
2021-03-11 16:28:35 +00:00
uint8_t input = Pcf8574Read ( idx ) & input_mask ;
uint8_t last_input = Pcf8574 . last_input [ idx ] ;
if ( input ! = last_input ) { // don't scan bits if no change (EVERY_50_MS !)
for ( uint8_t pin = 0 ; pin < 8 ; + + pin ) {
if ( bitRead ( input_mask , pin ) & & bitRead ( input , pin ) ! = bitRead ( last_input , pin ) ) {
ResponseTime_P ( PSTR ( " , \" PCF8574%c%d_INP \" :{ \" D%i \" :%i}} " ) , IndexSeparator ( ) , idx + 1 , pin , bitRead ( input , pin ) ) ;
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_STAT , PSTR ( " PCF8574_INP " ) ) ;
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . hass_tele_on_power ) { // SetOption59 - Send tele/%topic%/SENSOR in addition to stat/%topic%/RESULT
2021-03-11 16:28:35 +00:00
MqttPublishSensor ( ) ;
}
}
Pcf8574 . last_input [ idx ] = input ;
}
}
}
}
# endif //#ifdef USE_PCF8574_MQTTINPUT
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 ] ;
2021-01-23 15:26:23 +00:00
//AddLog(LOG_LEVEL_DEBUG, PSTR("PCF: Start working on Save arguements: inverted:%d")), Webserver->hasArg("b1");
2019-10-01 15:33:39 +01:00
2021-06-11 17:14:12 +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 ;
2021-06-11 17:14:12 +01:00
byte n = Settings - > pcf8574_config [ idx ] ;
2019-10-01 15:33:39 +01:00
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 ) {
2021-06-11 17:14:12 +01:00
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 {
2021-06-11 17:14:12 +01:00
Settings - > pcf8574_config [ idx ] = Settings - > pcf8574_config [ idx ] & ~ ( 1 < < i ) ;
2019-10-01 15:33:39 +01:00
}
}
2021-06-11 17:14:12 +01:00
//Settings->pcf8574_config[0] = (!strlen(webServer->arg("i2cs0").c_str())) ? 0 : atoi(webServer->arg("i2cs0").c_str());
//AddLog(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
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv28 ( uint32_t function )
2019-10-01 15:33:39 +01:00
{
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 ;
2021-03-11 16:28:35 +00:00
# ifdef USE_PCF8574_MQTTINPUT
case FUNC_EVERY_50_MSECOND :
Pcf8574CheckForInputChange ( ) ;
break ;
# endif // #ifdef USE_PCF8574_MQTTINPUT
# ifdef USE_PCF8574_SENSOR
case FUNC_JSON_APPEND :
Pcf8574Show ( 1 ) ;
break ;
# endif // #ifdef USE_PCF8574_SENSOR
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 ;
2021-03-11 16:28:35 +00:00
# ifdef USE_PCF8574_DISPLAYINPUT
case FUNC_WEB_SENSOR :
Pcf8574Show ( 0 ) ;
break ;
# endif // #ifdef USE_PCF8574_DISPLAYINPUT
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