2020-06-15 18:22:56 +01:00
/*
xdrv_41_tcp_bridge . ino - TCP to serial bridge
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Theo Arends and Stephan Hadinger
2020-06-15 18:22:56 +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_TCP_BRIDGE
# define XDRV_41 41
# ifndef TCP_BRIDGE_CONNECTIONS
# define TCP_BRIDGE_CONNECTIONS 2 // number of maximum parallel connections
# endif
# ifndef TCP_BRIDGE_BUF_SIZE
# define TCP_BRIDGE_BUF_SIZE 255 // size of the buffer, above 132 required for efficient XMODEM
# endif
//const uint16_t tcp_port = 8880;
WiFiServer * server_tcp = nullptr ;
//WiFiClient client_tcp1, client_tcp2;
WiFiClient client_tcp [ TCP_BRIDGE_CONNECTIONS ] ;
uint8_t client_next = 0 ;
uint8_t * tcp_buf = nullptr ; // data transfer buffer
2021-08-03 10:51:11 +01:00
IPAddress ip_filter ;
2020-06-15 18:22:56 +01:00
# include <TasmotaSerial.h>
TasmotaSerial * TCPSerial = nullptr ;
const char kTCPCommands [ ] PROGMEM = " TCP " " | " // prefix
2022-02-16 10:32:58 +00:00
" Start " " | " " Baudrate " " | " " Config " " | " " Connect "
2020-06-15 18:22:56 +01:00
;
void ( * const TCPCommand [ ] ) ( void ) PROGMEM = {
2022-02-16 10:32:58 +00:00
& CmndTCPStart , & CmndTCPBaudrate , & CmndTCPConfig , & CmndTCPConnect
2020-06-15 18:22:56 +01:00
} ;
//
// Called at event loop, checks for incoming data from the CC2530
//
void TCPLoop ( void )
{
uint8_t c ;
bool busy ; // did we transfer some data?
int32_t buf_len ;
if ( ! TCPSerial ) return ;
// check for a new client connection
if ( ( server_tcp ) & & ( server_tcp - > hasClient ( ) ) ) {
2021-08-01 12:26:42 +01:00
WiFiClient new_client = server_tcp - > available ( ) ;
2021-08-03 10:51:11 +01:00
2021-08-01 12:26:42 +01:00
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Got connection from %s " ) , new_client . remoteIP ( ) . toString ( ) . c_str ( ) ) ;
// Check for IP filtering if it's enabled.
if ( ip_filter ) {
if ( ip_filter ! = new_client . remoteIP ( ) ) {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Rejected due to filtering " ) ) ;
new_client . stop ( ) ;
} else {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Allowed through filter " ) ) ;
}
}
2020-06-15 18:22:56 +01:00
// find an empty slot
uint32_t i ;
2021-02-28 11:50:02 +00:00
for ( i = 0 ; i < nitems ( client_tcp ) ; i + + ) {
2020-06-15 18:22:56 +01:00
WiFiClient & client = client_tcp [ i ] ;
if ( ! client ) {
2021-08-01 12:26:42 +01:00
client = new_client ;
2020-06-15 18:22:56 +01:00
break ;
}
}
2021-02-28 11:50:02 +00:00
if ( i > = nitems ( client_tcp ) ) {
i = client_next + + % nitems ( client_tcp ) ;
2020-06-15 18:22:56 +01:00
WiFiClient & client = client_tcp [ i ] ;
client . stop ( ) ;
2021-08-01 12:26:42 +01:00
client = new_client ;
2020-06-15 18:22:56 +01:00
}
}
do {
busy = false ; // exit loop if no data was transferred
// start reading the UART, this buffer can quickly overflow
buf_len = 0 ;
while ( ( buf_len < TCP_BRIDGE_BUF_SIZE ) & & ( TCPSerial - > available ( ) ) ) {
c = TCPSerial - > read ( ) ;
if ( c > = 0 ) {
tcp_buf [ buf_len + + ] = c ;
busy = true ;
}
}
if ( buf_len > 0 ) {
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_TCP " from MCU: %*_H " ) , buf_len , tcp_buf ) ;
2020-06-15 18:22:56 +01:00
2021-02-28 11:50:02 +00:00
for ( uint32_t i = 0 ; i < nitems ( client_tcp ) ; i + + ) {
2020-06-15 18:22:56 +01:00
WiFiClient & client = client_tcp [ i ] ;
if ( client ) { client . write ( tcp_buf , buf_len ) ; }
}
}
// handle data received from TCP
2021-02-28 11:50:02 +00:00
for ( uint32_t i = 0 ; i < nitems ( client_tcp ) ; i + + ) {
2020-06-15 18:22:56 +01:00
WiFiClient & client = client_tcp [ i ] ;
buf_len = 0 ;
while ( client & & ( buf_len < TCP_BRIDGE_BUF_SIZE ) & & ( client . available ( ) ) ) {
c = client . read ( ) ;
if ( c > = 0 ) {
tcp_buf [ buf_len + + ] = c ;
busy = true ;
}
}
if ( buf_len > 0 ) {
2021-06-05 10:47:09 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_TCP " to MCU/%d: %*_H " ) , i + 1 , buf_len , tcp_buf ) ;
2020-06-15 18:22:56 +01:00
TCPSerial - > write ( tcp_buf , buf_len ) ;
}
}
yield ( ) ; // avoid WDT if heavy traffic
} while ( busy ) ;
}
/********************************************************************************************/
2021-11-12 08:07:06 +00:00
2020-06-15 18:22:56 +01:00
void TCPInit ( void ) {
if ( PinUsed ( GPIO_TCP_RX ) & & PinUsed ( GPIO_TCP_TX ) ) {
2021-11-04 16:14:34 +00:00
if ( 0 = = ( 0x80 & Settings - > tcp_config ) ) // !0x80 means unitialized
Settings - > tcp_config = 0x80 | ParseSerialConfig ( " 8N1 " ) ; // default as 8N1 for backward compatibility
2020-06-15 18:22:56 +01:00
tcp_buf = ( uint8_t * ) malloc ( TCP_BRIDGE_BUF_SIZE ) ;
2021-01-23 15:26:23 +00:00
if ( ! tcp_buf ) { AddLog ( LOG_LEVEL_ERROR , PSTR ( D_LOG_TCP " could not allocate buffer " ) ) ; return ; }
2020-06-15 18:22:56 +01:00
2021-06-11 17:14:12 +01:00
if ( ! Settings - > tcp_baudrate ) { Settings - > tcp_baudrate = 115200 / 1200 ; }
2020-10-30 11:29:48 +00:00
TCPSerial = new TasmotaSerial ( Pin ( GPIO_TCP_RX ) , Pin ( GPIO_TCP_TX ) , TasmotaGlobal . seriallog_level ? 1 : 2 , 0 , TCP_BRIDGE_BUF_SIZE ) ; // set a receive buffer of 256 bytes
2021-11-12 08:07:06 +00:00
TCPSerial - > begin ( Settings - > tcp_baudrate * 1200 , ConvertSerialConfig ( 0x7F & Settings - > tcp_config ) ) ;
2020-06-15 18:22:56 +01:00
if ( TCPSerial - > hardwareSerial ( ) ) {
ClaimSerial ( ) ;
}
}
}
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//
2021-08-01 12:26:42 +01:00
// Command `TCPStart`
// Params: port,<IPv4 allow>
2020-06-15 18:22:56 +01:00
//
void CmndTCPStart ( void ) {
if ( ! TCPSerial ) { return ; }
2021-08-01 12:26:42 +01:00
2020-06-15 18:22:56 +01:00
int32_t tcp_port = XdrvMailbox . payload ;
2021-08-01 12:26:42 +01:00
if ( ArgC ( ) = = 2 ) {
char sub_string [ XdrvMailbox . data_len ] ;
ip_filter . fromString ( ArgV ( sub_string , 2 ) ) ;
} else {
// Disable whitelist if previously set
ip_filter = ( uint32_t ) 0 ;
}
2020-06-15 18:22:56 +01:00
if ( server_tcp ) {
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Stopping TCP server " ) ) ;
2020-06-15 18:22:56 +01:00
server_tcp - > stop ( ) ;
delete server_tcp ;
server_tcp = nullptr ;
2021-02-28 11:50:02 +00:00
for ( uint32_t i = 0 ; i < nitems ( client_tcp ) ; i + + ) {
2020-06-15 18:22:56 +01:00
WiFiClient & client = client_tcp [ i ] ;
client . stop ( ) ;
}
}
if ( tcp_port > 0 ) {
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Starting TCP server on port %d " ) , tcp_port ) ;
2021-08-01 12:26:42 +01:00
if ( ip_filter ) {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Filtering %s " ) , ip_filter . toString ( ) . c_str ( ) ) ;
}
2020-06-15 18:22:56 +01:00
server_tcp = new WiFiServer ( tcp_port ) ;
server_tcp - > begin ( ) ; // start TCP server
server_tcp - > setNoDelay ( true ) ;
}
2020-10-30 11:29:48 +00:00
ResponseCmndDone ( ) ;
2020-06-15 18:22:56 +01:00
}
void CmndTCPBaudrate ( void ) {
if ( ( XdrvMailbox . payload > = 1200 ) & & ( XdrvMailbox . payload < = 115200 ) ) {
XdrvMailbox . payload / = 1200 ; // Make it a valid baudrate
2021-06-11 17:14:12 +01:00
Settings - > tcp_baudrate = XdrvMailbox . payload ;
2021-11-12 08:07:06 +00:00
TCPSerial - > begin ( Settings - > tcp_baudrate * 1200 , ConvertSerialConfig ( 0x7F & Settings - > tcp_config ) ) ; // Reinitialize serial port with new baud rate
2020-06-15 18:22:56 +01:00
}
2021-06-11 17:14:12 +01:00
ResponseCmndNumber ( Settings - > tcp_baudrate * 1200 ) ;
2020-06-15 18:22:56 +01:00
}
2021-11-04 16:14:34 +00:00
void CmndTCPConfig ( void ) {
if ( XdrvMailbox . data_len > 0 ) {
uint8_t serial_config = ParseSerialConfig ( XdrvMailbox . data ) ;
if ( serial_config > = 0 ) {
Settings - > tcp_config = 0x80 | serial_config ; // default 0x00 should be 8N1
2021-11-12 08:07:06 +00:00
TCPSerial - > begin ( Settings - > tcp_baudrate * 1200 , ConvertSerialConfig ( 0x7F & Settings - > tcp_config ) ) ; // Reinitialize serial port with new config
2021-11-04 16:14:34 +00:00
}
}
ResponseCmndChar_P ( GetSerialConfig ( 0x7F & Settings - > tcp_config ) . c_str ( ) ) ;
}
2022-02-16 10:32:58 +00:00
//
// Command `Connect`
// Params: port,<IPv4>
//
void CmndTCPConnect ( void ) {
int32_t tcp_port = XdrvMailbox . payload ;
if ( ArgC ( ) = = 2 ) {
char sub_string [ XdrvMailbox . data_len ] ;
WiFiClient new_client ;
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Connecting to %s on port %d " ) , ArgV ( sub_string , 2 ) , tcp_port ) ;
if ( new_client . connect ( ArgV ( sub_string , 2 ) , tcp_port ) ) {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " connected! " ) ) ;
} else {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " error connecting! " ) ) ;
}
// find an empty slot
uint32_t i ;
for ( i = 0 ; i < nitems ( client_tcp ) ; i + + ) {
WiFiClient & client = client_tcp [ i ] ;
if ( ! client ) {
client = new_client ;
break ;
}
}
if ( i > = nitems ( client_tcp ) ) {
i = client_next + + % nitems ( client_tcp ) ;
WiFiClient & client = client_tcp [ i ] ;
client . stop ( ) ;
client = new_client ;
}
} else {
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " Usage: port,ip_address " ) ) ;
}
ResponseCmndDone ( ) ;
}
2020-06-15 18:22:56 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv41 ( uint8_t function )
{
bool result = false ;
switch ( function ) {
case FUNC_LOOP :
TCPLoop ( ) ;
break ;
case FUNC_PRE_INIT :
TCPInit ( ) ;
break ;
case FUNC_COMMAND :
result = DecodeCommand ( kTCPCommands , TCPCommand ) ;
break ;
}
return result ;
}
# endif // USE_TCP_BRIDGE