2022-07-19 09:37:03 +01:00
/*
2022-07-20 09:41:02 +01:00
xdrv_63_modbus_bridge . ino - modbus bridge support for Tasmota
2022-07-19 09:37:03 +01:00
2022-07-20 09:41:02 +01:00
Copyright ( C ) 2021 Theo Arends and Jeroenst
2022-07-19 09:37:03 +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/>.
*/
2022-07-25 08:56:35 +01:00
# if defined(USE_MODBUS_BRIDGE)
2022-07-19 09:37:03 +01:00
/*********************************************************************************************\
* Modbus Bridge using Modbus library ( TasmotaModbus )
2022-07-20 10:11:18 +01:00
*
2022-07-25 08:56:35 +01:00
* Can be used trough web / mqtt commands and also via direct TCP connection ( when defined )
2022-08-17 18:55:41 +01:00
*
2022-07-25 08:56:35 +01:00
* When USE_MODBUS_BRIDGE_TCP is also defined , this bridge can also be used as an ModbusTCP
* bridge .
*
2022-08-27 08:11:36 +01:00
* Example Commands :
* - - Read Coils - -
* ModbusSend { " deviceaddress " : 1 , " functioncode " : 1 , " startaddress " : 1 , " type " : " bit " , " count " : 2 }
*
2022-08-17 18:55:41 +01:00
* - - Read Input Register - -
2022-07-25 08:56:35 +01:00
* ModbusSend { " deviceaddress " : 1 , " functioncode " : 3 , " startaddress " : 1 , " type " : " uint16 " , " count " : 2 }
2022-08-27 08:11:36 +01:00
*
2022-08-17 18:55:41 +01:00
* - - Write multiple coils - -
2022-08-27 08:11:36 +01:00
* ModbusSend { " deviceaddress " : 1 , " functioncode " : 15 , " startaddress " : 1 , " type " : " bit " , " count " : 4 , " values " : [ 1 , 0 , 1 , 1 ] }
2022-11-06 09:23:05 +00:00
*
* Info for modbusBridgeTCPServer :
2022-08-27 08:11:36 +01:00
* https : //ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/
2022-11-06 09:23:05 +00:00
*
2022-08-27 08:11:36 +01:00
* Info for modbus serial communications :
* https : //ozeki.hu/p_5879-mobdbus-function-code-4-read-input-registers.html
* https : //www.modbustools.com/modbus.html
* https : //ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/
2022-07-19 09:37:03 +01:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-07-25 08:56:35 +01:00
# define XDRV_63 63
2022-07-19 09:37:03 +01:00
2022-07-25 08:56:35 +01:00
# define MBR_MAX_VALUE_LENGTH 30
2022-07-25 18:18:30 +01:00
# define MBR_BAUDRATE TM_MODBUS_BAUDRATE
2022-07-25 08:56:35 +01:00
# define MBR_MAX_REGISTERS 64
2023-03-20 20:26:38 +00:00
# define MBR_RECEIVE_BUFFER_SIZE (MBR_MAX_REGISTERS * 2) + 9 // Addres(1), Function(1), Length(1) or StartAddress(2), N/A or Number of addresses(2),Data(1..n), CRC(2)
2022-07-20 09:41:02 +01:00
# define D_CMND_MODBUS_SEND "Send"
# define D_CMND_MODBUS_SETBAUDRATE "Baudrate"
# define D_CMND_MODBUS_SETSERIALCONFIG "SerialConfig"
2024-09-09 09:26:20 +01:00
# define D_CMND_MODBUS_SETSERIALTIMEOUT "SerialTimeout"
2022-07-19 09:37:03 +01:00
# define D_JSON_MODBUS_RECEIVED "ModbusReceived"
# define D_JSON_MODBUS_DEVICE_ADDRESS "DeviceAddress"
# define D_JSON_MODBUS_FUNCTION_CODE "FunctionCode"
# define D_JSON_MODBUS_START_ADDRESS "StartAddress"
# define D_JSON_MODBUS_COUNT "Count"
# define D_JSON_MODBUS_ENDIAN "Endian"
# define D_JSON_MODBUS_TYPE "Type" // allready defined
# define D_JSON_MODBUS_VALUES "Values"
# define D_JSON_MODBUS_LENGTH "Length"
2022-07-25 08:56:35 +01:00
# ifndef USE_MODBUS_BRIDGE_TCP
2022-07-20 09:41:02 +01:00
const char kModbusBridgeCommands [ ] PROGMEM = " Modbus| " // Prefix
2024-09-09 09:26:20 +01:00
D_CMND_MODBUS_SEND " | " D_CMND_MODBUS_SETBAUDRATE " | " D_CMND_MODBUS_SETSERIALCONFIG " | " D_CMND_MODBUS_SETSERIALTIMEOUT ;
2022-07-19 09:37:03 +01:00
void ( * const ModbusBridgeCommand [ ] ) ( void ) PROGMEM = {
2024-09-09 09:26:20 +01:00
& CmndModbusBridgeSend , & CmndModbusBridgeSetBaudrate , & CmndModbusBridgeSetConfig , & CmndModbusBridgeSetTimeout } ;
2022-07-25 08:56:35 +01:00
# endif
# ifdef USE_MODBUS_BRIDGE_TCP
# define MODBUS_BRIDGE_TCP_CONNECTIONS 1 // number of maximum parallel connections, only 1 supported with modbus
# define MODBUS_BRIDGE_TCP_BUF_SIZE 255 // size of the buffer, above 132 required for efficient XMODEM
# define D_CMND_MODBUS_TCP_START "TCPStart"
# define D_CMND_MODBUS_TCP_CONNECT "TCPConnect"
2023-03-21 08:39:32 +00:00
# define D_CMND_MODBUS_TCP_MQTT "TCPMqtt"
2022-07-25 08:56:35 +01:00
const char kModbusBridgeCommands [ ] PROGMEM = " Modbus| " // Prefix
2024-09-09 09:26:20 +01:00
D_CMND_MODBUS_TCP_START " | " D_CMND_MODBUS_TCP_CONNECT " | " D_CMND_MODBUS_TCP_MQTT " | " D_CMND_MODBUS_SEND " | " D_CMND_MODBUS_SETBAUDRATE " | " D_CMND_MODBUS_SETSERIALCONFIG " | " D_CMND_MODBUS_SETSERIALTIMEOUT ;
2022-07-25 08:56:35 +01:00
void ( * const ModbusBridgeCommand [ ] ) ( void ) PROGMEM = {
2023-03-21 08:39:32 +00:00
& CmndModbusTCPStart , & CmndModbusTCPConnect , & CmndModbusTCPMqtt ,
2024-09-09 09:26:20 +01:00
& CmndModbusBridgeSend , & CmndModbusBridgeSetBaudrate , & CmndModbusBridgeSetConfig , & CmndModbusBridgeSetTimeout } ;
2022-07-25 08:56:35 +01:00
struct ModbusBridgeTCP
{
WiFiServer * server_tcp = nullptr ;
WiFiClient client_tcp [ MODBUS_BRIDGE_TCP_CONNECTIONS ] ;
uint8_t client_next = 0 ;
uint8_t * tcp_buf = nullptr ; // data transfer buffer
IPAddress ip_filter ;
Avoid sending more then one Modbus TCP response (#22223)
Prevent from sending more then one Modbus TCP response
for a single request.
This might happen in the following scenario where,
shortly after the first response has been sent (4),
the second one will be send (8) (note the same
'TransactionId') triggered by the response to the
'ModbusSend' request issued by Berry (5).
The log excerpt from such a situation:
(1) 21:13:20.510 MBS: MBRTCP to Modbus TransactionId:520, deviceAddress:4, functionCode:3, startAddress:8193, count:1, recvCount:1, recvBytes:2
(2) 21:13:20.523 MBS: Serial Send: 04 03 20 01 00 01 DE 5F
(3) 21:13:20.647 MBS: Serial Received: 04 03 02 0A 28 72 FA
(4) 21:13:20.652 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:11 bytes to client (error:0)
(5) 21:13:20.724 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 89, Pld -99, Data '{"deviceAddress":4, "functionCode":6, "startAddress":8192, "type":"uint16", "Values":[6]}'
(6) 21:13:20.743 MBS: Serial Send: 04 06 20 00 00 06 02 5D
(7) 21:13:21.009 MBS: Serial Received: 04 06 20 00 00 06 02 5D
(8) 21:13:21.014 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:12 bytes to client (error:0)
Use 'tcp_transaction_id' field to denote that we already
sent a response.
Signed-off-by: Damian Wrobel <dwrobel@ertelnet.rybnik.pl>
2024-10-02 20:34:48 +01:00
# define MODBUS_TCP_INVALID_TRANSACTION_ID UINT32_MAX
// Invalid ID value denotes, that the data 'tcp_buf' points to
// should not be used to send Modbus TCP response (very likely
// the response was already sent).
uint32_t tcp_transaction_id = MODBUS_TCP_INVALID_TRANSACTION_ID ;
2023-03-21 08:39:32 +00:00
bool output_mqtt = false ;
2022-07-25 08:56:35 +01:00
} ;
ModbusBridgeTCP modbusBridgeTCP ;
2023-11-20 11:35:06 +00:00
# endif // USE_MODBUS_BRIDGE_TCP
2022-07-19 09:37:03 +01:00
# include <TasmotaModbus.h>
2022-11-06 09:23:05 +00:00
TasmotaModbus * modbusBridgeModbus = nullptr ;
2022-07-19 09:37:03 +01:00
enum class ModbusBridgeError
{
noerror = 0 ,
nodataexpected = 1 ,
wrongdeviceaddress = 2 ,
wrongfunctioncode = 3 ,
wrongstartaddress = 4 ,
wrongtype = 5 ,
2022-08-17 18:55:41 +01:00
wrongdataCount = 6 ,
2022-08-17 18:55:41 +01:00
wrongcount = 7 ,
tomanydata = 8
2022-07-19 09:37:03 +01:00
} ;
enum class ModbusBridgeFunctionCode
{
mb_undefined = 0 ,
2022-08-17 18:55:41 +01:00
mb_readCoilStatus = 1 ,
2022-08-27 08:11:36 +01:00
mb_readInputStatus = 2 ,
2022-08-17 18:55:41 +01:00
mb_readHoldingRegisters = 3 ,
mb_readInputRegisters = 4 ,
2022-07-19 09:37:03 +01:00
mb_writeSingleCoil = 5 ,
2022-08-17 18:55:41 +01:00
mb_writeSingleRegister = 6 ,
mb_writeMultipleCoils = 15 ,
mb_writeMultipleRegisters = 16
2022-07-19 09:37:03 +01:00
} ;
enum class ModbusBridgeType
{
mb_undefined ,
2022-08-17 18:55:41 +01:00
mb_uint8 ,
2022-07-19 09:37:03 +01:00
mb_uint16 ,
mb_uint32 ,
2022-08-17 18:55:41 +01:00
mb_int8 ,
2022-07-19 09:37:03 +01:00
mb_int16 ,
mb_int32 ,
mb_float ,
mb_raw ,
2022-08-17 18:55:41 +01:00
mb_hex ,
2022-07-25 08:56:35 +01:00
mb_bit
2022-07-19 09:37:03 +01:00
} ;
enum class ModbusBridgeEndian
{
mb_undefined ,
mb_msb ,
mb_lsb
} ;
2024-09-09 09:26:20 +01:00
# ifndef MODBUS_SERIAL_TIMEOUT_MS
# define MODBUS_SERIAL_TIMEOUT_MS 1000
# endif
2022-07-19 09:37:03 +01:00
struct ModbusBridge
{
unsigned long polling_window = 0 ;
int in_byte_counter = 0 ;
ModbusBridgeFunctionCode functionCode = ModbusBridgeFunctionCode : : mb_undefined ;
ModbusBridgeType type = ModbusBridgeType : : mb_undefined ;
2023-05-14 13:18:45 +01:00
ModbusBridgeEndian endian = ModbusBridgeEndian : : mb_undefined ;
2022-07-19 09:37:03 +01:00
2023-04-29 13:46:38 +01:00
uint16_t dataCount = 0 ; // Number of bits or registers to read/write
uint16_t byteCount = 0 ; // Number of bytes to read/write
uint16_t startAddress = 0 ; // First address to read/write
uint8_t deviceAddress = 0 ; // Modbus address of device to read
uint8_t count = 0 ; // Number of values to read / write
2022-07-20 09:41:02 +01:00
bool raw = false ;
2023-04-29 13:46:38 +01:00
uint8_t * buffer = nullptr ; // Buffer for storing read / write data
2023-11-20 11:35:06 +00:00
bool enabled = false ;
2024-09-09 09:26:20 +01:00
// Buffer to store command data received from CmndModbusBridgeSend()
char * command_data = nullptr ;
private :
// Timeout in [ms]. How long we will wait for Modbus response.
uint32_t modbusSerialTimeout_ms = MODBUS_SERIAL_TIMEOUT_MS ;
// Holds the value of millis() after we set
// waitingForAnswerFromSerial flag to true.
uint32_t sendDataToSerial_ms ;
// If true, then do not sent another Modbus request until:
// millis() - sendDataToSerial_ms > modbusSerialTimeout_ms
bool waitingForAnswerFromSerial = false ;
public :
void setModbusSerialTimeout_ms ( const uint32_t new_timeout )
{
modbusSerialTimeout_ms = new_timeout ;
}
uint32_t getModbusSerialTimeout_ms ( ) const
{
return modbusSerialTimeout_ms ;
}
void setWaitingForAnswerFromSerial ( const bool new_value )
{
waitingForAnswerFromSerial = new_value ;
if ( waitingForAnswerFromSerial )
sendDataToSerial_ms = millis ( ) ;
}
bool isWaitingForAnswerFromSerial ( ) const
{
return waitingForAnswerFromSerial ;
}
bool isWaitingForAnswerFromSerialTimedOut ( ) const
{
const auto t1 = millis ( ) - sendDataToSerial_ms ;
return ( t1 > modbusSerialTimeout_ms ) ? true : false ;
}
2022-07-19 09:37:03 +01:00
} ;
ModbusBridge modbusBridge ;
2022-08-27 08:11:36 +01:00
/********************************************************************************************/
//
2022-11-06 09:23:05 +00:00
// Helper functions
2022-08-27 08:11:36 +01:00
//
2022-11-06 09:23:05 +00:00
uint16_t ModbusBridgeSwapEndian16 ( uint16_t num )
2022-08-27 08:11:36 +01:00
{
return ( num > > 8 ) | ( num < < 8 ) ;
}
2022-11-06 09:12:08 +00:00
void ModbusBridgeAllocError ( const char * s )
{
AddLog ( LOG_LEVEL_ERROR , PSTR ( " MBS: could not allocate %s buffer " ) , s ) ;
}
2022-08-27 08:11:36 +01:00
2022-07-19 09:37:03 +01:00
/********************************************************************************************/
2022-07-25 08:56:35 +01:00
//
// Applies serial configuration to modbus serial port
//
2023-11-20 11:35:06 +00:00
bool ModbusBridgeBegin ( void ) {
if ( ( Settings - > modbus_sbaudrate < 1 ) | | ( Settings - > modbus_sbaudrate > ( 115200 / 300 ) ) ) {
2022-08-17 18:55:41 +01:00
Settings - > modbus_sbaudrate = ( uint8_t ) ( ( uint32_t ) MBR_BAUDRATE / 300 ) ;
2023-11-20 11:35:06 +00:00
}
if ( Settings - > modbus_sconfig > TS_SERIAL_8O2 ) {
2022-08-17 18:55:41 +01:00
Settings - > modbus_sconfig = TS_SERIAL_8N1 ;
2023-11-20 11:35:06 +00:00
}
2022-07-25 18:18:30 +01:00
2022-11-06 09:23:05 +00:00
int result = modbusBridgeModbus - > Begin ( Settings - > modbus_sbaudrate * 300 , ConvertSerialConfig ( Settings - > modbus_sconfig ) ) ; // Reinitialize modbus port with new baud rate
2023-11-20 11:35:06 +00:00
if ( result ) {
if ( 2 = = result ) {
2022-07-25 08:56:35 +01:00
ClaimSerial ( ) ;
}
2023-12-28 16:25:01 +00:00
# ifdef ESP32
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: Serial UART%d " ) , modbusBridgeModbus - > getUart ( ) ) ;
# endif
2022-07-25 18:18:30 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR %s ser init at %d baud " ) , ( 2 = = result ? " HW " : " SW " ) , Settings - > modbus_sbaudrate * 300 ) ;
2023-03-20 20:26:38 +00:00
2023-11-20 11:35:06 +00:00
if ( nullptr = = modbusBridge . buffer ) {
modbusBridge . buffer = ( uint8_t * ) malloc ( MBR_RECEIVE_BUFFER_SIZE ) ;
}
if ( nullptr = = modbusBridge . buffer ) {
ModbusBridgeAllocError ( PSTR ( " BUFFER " ) ) ;
result = false ;
}
2023-10-16 07:26:26 +01:00
}
2022-07-25 08:56:35 +01:00
return result ;
2022-07-19 09:37:03 +01:00
}
2022-11-06 09:23:05 +00:00
void ModbusBridgeSetConfig ( uint32_t serial_config )
2022-07-19 09:37:03 +01:00
{
if ( serial_config > TS_SERIAL_8O2 )
{
serial_config = TS_SERIAL_8N1 ;
}
2022-07-25 18:18:30 +01:00
if ( serial_config ! = Settings - > modbus_sconfig )
2022-07-19 09:37:03 +01:00
{
2022-07-25 18:18:30 +01:00
Settings - > modbus_sconfig = serial_config ;
2022-07-25 08:56:35 +01:00
ModbusBridgeBegin ( ) ;
2022-07-19 09:37:03 +01:00
}
}
2022-11-06 09:23:05 +00:00
void ModbusBridgeSetBaudrate ( uint32_t baudrate )
2022-07-25 08:56:35 +01:00
{
2022-07-25 18:18:30 +01:00
if ( ( baudrate > = 300 ) & & ( baudrate < = 115200 ) )
2022-07-25 08:56:35 +01:00
{
2022-08-17 18:55:41 +01:00
if ( baudrate / 300 ! = Settings - > modbus_sbaudrate )
2022-07-25 18:18:30 +01:00
{
Settings - > modbus_sbaudrate = baudrate / 300 ;
ModbusBridgeBegin ( ) ;
}
2022-07-25 08:56:35 +01:00
}
}
2022-07-19 09:37:03 +01:00
2022-07-25 08:56:35 +01:00
/********************************************************************************************/
//
2023-04-29 13:46:38 +01:00
// Handles data received from tasmota modbus wrapper and send this to (TCP and/or) MQTT client
2022-07-25 08:56:35 +01:00
//
2022-07-19 09:37:03 +01:00
void ModbusBridgeHandle ( void )
{
2024-09-09 09:26:20 +01:00
uint32_t error = 0 ;
2022-11-06 09:23:05 +00:00
bool data_ready = modbusBridgeModbus - > ReceiveReady ( ) ;
2022-07-19 09:37:03 +01:00
if ( data_ready )
{
2022-08-27 08:11:36 +01:00
if ( modbusBridge . byteCount = = 0 ) modbusBridge . byteCount = modbusBridge . dataCount * 2 ;
2023-04-29 13:46:38 +01:00
if ( nullptr = = modbusBridge . buffer ) // If buffer is not initialized do not process received data
2022-11-06 09:12:08 +00:00
{
ModbusBridgeAllocError ( PSTR ( " read " ) ) ;
2023-03-20 20:26:38 +00:00
modbusBridge . dataCount = 0 ;
modbusBridge . byteCount = 0 ;
2022-11-06 09:12:08 +00:00
return ;
}
2023-03-20 20:26:38 +00:00
memset ( modbusBridge . buffer , 0 , MBR_RECEIVE_BUFFER_SIZE ) ;
2024-09-09 09:26:20 +01:00
error = modbusBridgeModbus - > ReceiveBuffer ( modbusBridge . buffer , 0 , MBR_RECEIVE_BUFFER_SIZE - 9 ) ;
modbusBridge . setWaitingForAnswerFromSerial ( false ) ;
}
else if ( modbusBridge . isWaitingForAnswerFromSerial ( )
& & modbusBridge . isWaitingForAnswerFromSerialTimedOut ( ) )
{
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR Recv timed out " ) ) ;
modbusBridge . setWaitingForAnswerFromSerial ( false ) ;
// MODBUS Application Protocol Specification V1.1b3,
// p.7 MODBUS Exception Responses
error = 11 ; // The targeted device failed to respond
}
2022-07-19 09:37:03 +01:00
2024-09-09 09:26:20 +01:00
if ( data_ready | | error )
{
2022-07-25 08:56:35 +01:00
# ifdef USE_MODBUS_BRIDGE_TCP
for ( uint32_t i = 0 ; i < nitems ( modbusBridgeTCP . client_tcp ) ; i + + )
{
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
Avoid sending more then one Modbus TCP response (#22223)
Prevent from sending more then one Modbus TCP response
for a single request.
This might happen in the following scenario where,
shortly after the first response has been sent (4),
the second one will be send (8) (note the same
'TransactionId') triggered by the response to the
'ModbusSend' request issued by Berry (5).
The log excerpt from such a situation:
(1) 21:13:20.510 MBS: MBRTCP to Modbus TransactionId:520, deviceAddress:4, functionCode:3, startAddress:8193, count:1, recvCount:1, recvBytes:2
(2) 21:13:20.523 MBS: Serial Send: 04 03 20 01 00 01 DE 5F
(3) 21:13:20.647 MBS: Serial Received: 04 03 02 0A 28 72 FA
(4) 21:13:20.652 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:11 bytes to client (error:0)
(5) 21:13:20.724 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 89, Pld -99, Data '{"deviceAddress":4, "functionCode":6, "startAddress":8192, "type":"uint16", "Values":[6]}'
(6) 21:13:20.743 MBS: Serial Send: 04 06 20 00 00 06 02 5D
(7) 21:13:21.009 MBS: Serial Received: 04 06 20 00 00 06 02 5D
(8) 21:13:21.014 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:12 bytes to client (error:0)
Use 'tcp_transaction_id' field to denote that we already
sent a response.
Signed-off-by: Damian Wrobel <dwrobel@ertelnet.rybnik.pl>
2024-10-02 20:34:48 +01:00
if ( client & & modbusBridgeTCP . tcp_transaction_id ! = MODBUS_TCP_INVALID_TRANSACTION_ID )
2022-07-25 08:56:35 +01:00
{
2022-11-06 11:32:02 +00:00
uint8_t header [ 9 ] ;
2022-08-27 08:11:36 +01:00
uint8_t nrOfBytes = 8 ;
header [ 0 ] = modbusBridgeTCP . tcp_transaction_id > > 8 ;
header [ 1 ] = modbusBridgeTCP . tcp_transaction_id ;
header [ 2 ] = 0 ;
header [ 3 ] = 0 ;
2023-03-20 20:26:38 +00:00
header [ 6 ] = modbusBridge . buffer [ 0 ] ; // Send slave address
header [ 7 ] = modbusBridge . buffer [ 1 ] ; // Send function code
2022-08-27 08:11:36 +01:00
if ( error )
{
header [ 4 ] = 0 ; // Message Length Hi-Byte
header [ 5 ] = 3 ; // Message Length Low-Byte
2023-03-20 20:26:38 +00:00
header [ 7 ] = modbusBridge . buffer [ 1 ] | 0x80 ; // Send function code
2022-08-27 08:11:36 +01:00
header [ 8 ] = error ;
nrOfBytes + = 1 ;
client . write ( header , 9 ) ;
}
2023-03-20 20:26:38 +00:00
else if ( modbusBridge . buffer [ 1 ] < = 4 )
2022-08-27 08:11:36 +01:00
{
2023-04-29 13:46:38 +01:00
uint8_t received_data_bytes = modbusBridgeModbus - > ReceiveCount ( ) - 5 ;
header [ 4 ] = received_data_bytes > > 8 ;
header [ 5 ] = received_data_bytes + 3 ;
header [ 8 ] = received_data_bytes ;
2022-08-27 08:11:36 +01:00
client . write ( header , 9 ) ;
nrOfBytes + = 1 ;
2023-04-29 13:46:38 +01:00
client . write ( modbusBridge . buffer + 3 , received_data_bytes ) ; // Don't send CRC
nrOfBytes + = received_data_bytes ;
2022-08-27 08:11:36 +01:00
}
else
{
header [ 4 ] = 0 ; // Message Length Hi-Byte
header [ 5 ] = 6 ; // Message Length Low-Byte
client . write ( header , 8 ) ;
2023-03-20 20:26:38 +00:00
client . write ( modbusBridge . buffer + 2 , 4 ) ; // Don't send CRC
2022-08-27 08:11:36 +01:00
nrOfBytes + = 4 ;
}
2022-07-25 08:56:35 +01:00
client . flush ( ) ;
2024-09-09 09:26:20 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBRTCP from Modbus TransactionId:%d, deviceAddress:%d, writing:%d bytes to client (error:%d) " ) , ( static_cast < uint16_t > ( header [ 0 ] ) < < 8 ) + header [ 1 ] , modbusBridge . buffer [ 0 ] , nrOfBytes , error ) ;
Avoid sending more then one Modbus TCP response (#22223)
Prevent from sending more then one Modbus TCP response
for a single request.
This might happen in the following scenario where,
shortly after the first response has been sent (4),
the second one will be send (8) (note the same
'TransactionId') triggered by the response to the
'ModbusSend' request issued by Berry (5).
The log excerpt from such a situation:
(1) 21:13:20.510 MBS: MBRTCP to Modbus TransactionId:520, deviceAddress:4, functionCode:3, startAddress:8193, count:1, recvCount:1, recvBytes:2
(2) 21:13:20.523 MBS: Serial Send: 04 03 20 01 00 01 DE 5F
(3) 21:13:20.647 MBS: Serial Received: 04 03 02 0A 28 72 FA
(4) 21:13:20.652 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:11 bytes to client (error:0)
(5) 21:13:20.724 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 89, Pld -99, Data '{"deviceAddress":4, "functionCode":6, "startAddress":8192, "type":"uint16", "Values":[6]}'
(6) 21:13:20.743 MBS: Serial Send: 04 06 20 00 00 06 02 5D
(7) 21:13:21.009 MBS: Serial Received: 04 06 20 00 00 06 02 5D
(8) 21:13:21.014 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:12 bytes to client (error:0)
Use 'tcp_transaction_id' field to denote that we already
sent a response.
Signed-off-by: Damian Wrobel <dwrobel@ertelnet.rybnik.pl>
2024-10-02 20:34:48 +01:00
modbusBridgeTCP . tcp_transaction_id = MODBUS_TCP_INVALID_TRANSACTION_ID ;
2022-07-25 08:56:35 +01:00
}
2022-07-19 09:37:03 +01:00
}
2022-07-25 08:56:35 +01:00
# endif
2023-03-20 20:26:38 +00:00
modbusBridge . byteCount = 0 ;
2022-08-27 08:11:36 +01:00
if ( error )
{
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR Driver receive error %d " ) , error ) ;
2023-03-20 20:26:38 +00:00
modbusBridge . dataCount = 0 ;
2022-08-27 08:11:36 +01:00
return ;
}
2022-07-25 08:56:35 +01:00
ModbusBridgeError errorcode = ModbusBridgeError : : noerror ;
2022-08-27 09:54:41 +01:00
2022-07-25 08:56:35 +01:00
if ( modbusBridge . deviceAddress = = 0 )
{
# ifdef USE_MODBUS_BRIDGE_TCP
// If tcp client connected don't log error and exit this function (do not process)
2023-03-21 08:39:32 +00:00
if ( nitems ( modbusBridgeTCP . client_tcp ) & & ! modbusBridgeTCP . output_mqtt )
2022-07-25 08:56:35 +01:00
{
return ;
}
# endif
2022-07-19 09:37:03 +01:00
errorcode = ModbusBridgeError : : nodataexpected ;
2022-07-25 08:56:35 +01:00
}
2023-03-20 20:26:38 +00:00
else if ( modbusBridge . deviceAddress ! = ( uint8_t ) modbusBridge . buffer [ 0 ] )
2022-07-19 09:37:03 +01:00
errorcode = ModbusBridgeError : : wrongdeviceaddress ;
2023-03-20 20:26:38 +00:00
else if ( ( uint8_t ) modbusBridge . functionCode ! = ( uint8_t ) modbusBridge . buffer [ 1 ] )
2022-07-19 09:37:03 +01:00
errorcode = ModbusBridgeError : : wrongfunctioncode ;
2022-08-17 18:55:41 +01:00
else if ( ( uint8_t ) modbusBridge . functionCode < 5 )
2022-08-17 18:55:41 +01:00
{
2023-04-29 13:46:38 +01:00
// Do not check buffer[2] but received bytes for correct length but use the nr of received bytes
uint8_t received_data_bytes = modbusBridgeModbus - > ReceiveCount ( ) - 5 ;
2022-08-17 18:55:41 +01:00
if ( ( uint8_t ) modbusBridge . functionCode < 3 )
{
2023-04-29 13:46:38 +01:00
// Check if returned number of bits matches the requested number of bits
if ( ( uint8_t ) ( ( ( modbusBridge . dataCount - 1 ) > > 3 ) + 1 ) > received_data_bytes )
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : wrongdataCount ;
}
else
{
2023-04-29 13:46:38 +01:00
if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int8 | | modbusBridge . type = = ModbusBridgeType : : mb_uint8 ) & & ( ( uint8_t ) modbusBridge . dataCount > received_data_bytes ) )
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : wrongdataCount ;
2023-04-29 13:46:38 +01:00
else if ( ( modbusBridge . type = = ModbusBridgeType : : mb_bit ) & & ( ( uint8_t ) modbusBridge . dataCount > received_data_bytes ) )
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : wrongdataCount ;
2023-04-29 13:46:38 +01:00
else if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int16 | | modbusBridge . type = = ModbusBridgeType : : mb_uint16 ) & & ( ( uint8_t ) modbusBridge . dataCount > received_data_bytes ) )
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : wrongdataCount ;
2023-04-29 13:46:38 +01:00
else if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int32 | | modbusBridge . type = = ModbusBridgeType : : mb_uint32 | | modbusBridge . type = = ModbusBridgeType : : mb_float ) & & ( ( uint8_t ) modbusBridge . dataCount > received_data_bytes ) )
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : wrongdataCount ;
}
2022-08-17 18:55:41 +01:00
}
2022-08-17 18:55:41 +01:00
if ( errorcode = = ModbusBridgeError : : noerror )
2022-07-19 09:37:03 +01:00
{
if ( modbusBridge . type = = ModbusBridgeType : : mb_raw )
{
2023-04-29 13:46:38 +01:00
// Ouput raw data as decimal bytes
2022-08-17 18:55:41 +01:00
Response_P ( PSTR ( " { \" " D_JSON_MODBUS_RECEIVED " \" :{ \" RAW \" :[ " ) ) ;
2022-11-06 09:23:05 +00:00
for ( uint8_t i = 0 ; i < modbusBridgeModbus - > ReceiveCount ( ) ; i + + )
2022-07-19 09:37:03 +01:00
{
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " %d " ) , modbusBridge . buffer [ i ] ) ;
2022-11-06 09:23:05 +00:00
if ( i < modbusBridgeModbus - > ReceiveCount ( ) - 1 )
2022-07-19 09:37:03 +01:00
ResponseAppend_P ( PSTR ( " , " ) ) ;
}
ResponseAppend_P ( PSTR ( " ]} " ) ) ;
ResponseJsonEnd ( ) ;
2024-02-07 14:57:09 +00:00
if ( Settings - > flag6 . mqtt_disable_modbus ) { // SetOption158 If it is activated, Tasmota will not publish ModbusReceived MQTT messages, but it will proccess event trigger rules
XdrvRulesProcess ( 0 ) ;
} else {
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_MODBUS_RECEIVED ) ) ;
}
2022-07-19 09:37:03 +01:00
}
2022-08-17 18:55:41 +01:00
else if ( modbusBridge . type = = ModbusBridgeType : : mb_hex )
{
2023-04-29 13:46:38 +01:00
// Output raw data as hexadecimal bytes
2022-08-17 18:55:41 +01:00
Response_P ( PSTR ( " { \" " D_JSON_MODBUS_RECEIVED " \" :{ \" HEX \" :[ " ) ) ;
2022-11-06 09:23:05 +00:00
for ( uint8_t i = 0 ; i < modbusBridgeModbus - > ReceiveCount ( ) ; i + + )
2022-08-17 18:55:41 +01:00
{
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " 0x%02X " ) , modbusBridge . buffer [ i ] ) ;
2022-11-06 09:23:05 +00:00
if ( i < modbusBridgeModbus - > ReceiveCount ( ) - 1 )
2022-08-17 18:55:41 +01:00
ResponseAppend_P ( PSTR ( " , " ) ) ;
}
ResponseAppend_P ( PSTR ( " ]} " ) ) ;
ResponseJsonEnd ( ) ;
2024-02-07 14:57:09 +00:00
if ( Settings - > flag6 . mqtt_disable_modbus ) { // SetOption158 If it is activated, Tasmota will not publish ModbusReceived MQTT messages, but it will proccess event trigger rules
XdrvRulesProcess ( 0 ) ;
} else {
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_MODBUS_RECEIVED ) ) ;
}
2022-08-17 18:55:41 +01:00
}
2023-04-29 13:46:38 +01:00
else if ( ( modbusBridge . buffer [ 1 ] > 0 ) & & ( modbusBridge . buffer [ 1 ] < 7 ) )
2022-07-19 09:37:03 +01:00
{
2023-04-29 13:46:38 +01:00
// Read and process Registers
2022-08-17 18:55:41 +01:00
uint8_t dataOffset = 3 ;
2022-07-19 09:37:03 +01:00
Response_P ( PSTR ( " { \" " D_JSON_MODBUS_RECEIVED " \" :{ " ) ) ;
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_DEVICE_ADDRESS " \" :%d, " ) , modbusBridge . buffer [ 0 ] ) ;
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_FUNCTION_CODE " \" :%d, " ) , modbusBridge . buffer [ 1 ] ) ;
if ( modbusBridge . buffer [ 1 ] < 5 )
2022-08-17 18:55:41 +01:00
{
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_START_ADDRESS " \" :%d, " ) , modbusBridge . startAddress ) ;
}
else
{
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_START_ADDRESS " \" :%d, " ) , ( modbusBridge . buffer [ 2 ] < < 8 ) + modbusBridge . buffer [ 3 ] ) ;
2022-08-17 18:55:41 +01:00
dataOffset = 4 ;
}
2022-11-06 09:23:05 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_LENGTH " \" :%d, " ) , modbusBridgeModbus - > ReceiveCount ( ) ) ;
2022-07-19 09:37:03 +01:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_COUNT " \" :%d, " ) , modbusBridge . count ) ;
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_VALUES " \" :[ " ) ) ;
2022-08-17 18:55:41 +01:00
uint8_t data_count = modbusBridge . count ;
if ( ( uint8_t ) modbusBridge . functionCode < 3 )
{
2023-04-29 13:46:38 +01:00
// Calculate number of values to return in bitmode
2022-08-17 18:55:41 +01:00
if ( modbusBridge . type = = ModbusBridgeType : : mb_int8 | | modbusBridge . type = = ModbusBridgeType : : mb_uint8 )
data_count = ( uint8_t ) ( ( ( modbusBridge . count - 1 ) > > 3 ) + 1 ) ;
else if ( modbusBridge . type = = ModbusBridgeType : : mb_int16 | | modbusBridge . type = = ModbusBridgeType : : mb_uint16 )
data_count = ( uint8_t ) ( ( ( modbusBridge . count - 1 ) > > 4 ) + 1 ) ;
else if ( modbusBridge . type = = ModbusBridgeType : : mb_int32 | | modbusBridge . type = = ModbusBridgeType : : mb_uint32 | | modbusBridge . type = = ModbusBridgeType : : mb_float )
data_count = ( uint8_t ) ( ( ( modbusBridge . count - 1 ) > > 5 ) + 1 ) ;
}
2023-05-14 13:18:45 +01:00
// Copy modbus data to requested variables type
2022-08-17 18:55:41 +01:00
for ( uint8_t count = 0 ; count < data_count ; count + + )
2022-07-19 09:37:03 +01:00
{
char svalue [ MBR_MAX_VALUE_LENGTH + 1 ] = " " ;
if ( modbusBridge . type = = ModbusBridgeType : : mb_float )
{
2022-08-27 08:11:36 +01:00
// Convert next 4 bytes to float
2022-07-19 09:37:03 +01:00
float value = 0 ;
2023-05-14 13:18:45 +01:00
if ( modbusBridge . endian = = ModbusBridgeEndian : : mb_lsb )
2022-08-17 18:55:41 +01:00
{
2023-05-14 13:18:45 +01:00
// In lsb mode swap bytes
2023-03-20 20:26:38 +00:00
if ( modbusBridge . buffer [ 2 ] - ( count * 4 ) )
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + ( count * 4 ) ] ; // Get float values
if ( ( modbusBridge . buffer [ 2 ] - ( count * 4 ) ) > > 1 )
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 4 ) ] ;
if ( ( modbusBridge . buffer [ 2 ] - ( count * 4 ) - 1 ) > > 1 )
( ( uint8_t * ) & value ) [ 2 ] = modbusBridge . buffer [ dataOffset + 2 + ( count * 4 ) ] ;
if ( ( modbusBridge . buffer [ 2 ] - ( count * 4 ) ) > > 2 )
( ( uint8_t * ) & value ) [ 3 ] = modbusBridge . buffer [ dataOffset + 3 + ( count * 4 ) ] ;
2022-08-17 18:55:41 +01:00
}
else
{
2023-03-20 20:26:38 +00:00
( ( uint8_t * ) & value ) [ 3 ] = modbusBridge . buffer [ dataOffset + ( count * 4 ) ] ; // Get float values
( ( uint8_t * ) & value ) [ 2 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 4 ) ] ;
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + 2 + ( count * 4 ) ] ;
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + 3 + ( count * 4 ) ] ;
2022-08-17 18:55:41 +01:00
}
2022-07-19 09:37:03 +01:00
ext_snprintf_P ( svalue , sizeof ( svalue ) , " %*_f " , 10 , & value ) ;
}
2022-08-17 18:55:41 +01:00
else if ( modbusBridge . type = = ModbusBridgeType : : mb_bit )
{
2022-08-27 08:11:36 +01:00
uint8_t bits_left = modbusBridge . count - ( ( count / 8 ) * 8 ) ;
uint8_t value = 0 ;
if ( bits_left < 8 )
{
uint8_t bits_skip = 8 - bits_left ;
2023-03-20 20:26:38 +00:00
value = ( uint8_t ) ( modbusBridge . buffer [ dataOffset + ( ( count + bits_skip ) > > 3 ) ] ) ;
2022-08-27 08:11:36 +01:00
}
else
{
2023-03-20 20:26:38 +00:00
value = ( uint8_t ) ( modbusBridge . buffer [ dataOffset + ( count > > 3 ) ] ) ;
2022-08-27 08:11:36 +01:00
}
2022-08-17 18:55:42 +01:00
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %d " , ( ( value > > ( count & 7 ) ) & 1 ) ) ;
2022-08-17 18:55:41 +01:00
}
2022-07-19 09:37:03 +01:00
else
{
if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int32 ) | |
( modbusBridge . type = = ModbusBridgeType : : mb_uint32 ) )
{
uint32_t value = 0 ;
2023-05-14 13:18:45 +01:00
if ( modbusBridge . endian = = ModbusBridgeEndian : : mb_lsb )
2022-08-17 18:55:41 +01:00
{
2023-05-14 13:18:45 +01:00
// In lsb mode swap bytes
2023-03-20 20:26:38 +00:00
if ( modbusBridge . buffer [ 2 ] - ( count * 4 ) )
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + ( count * 4 ) ] ; // Get uint values
if ( modbusBridge . buffer [ 2 ] - ( ( count * 4 ) - 1 ) )
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 4 ) ] ;
if ( modbusBridge . buffer [ 2 ] - ( ( count * 4 ) - 2 ) )
( ( uint8_t * ) & value ) [ 2 ] = modbusBridge . buffer [ dataOffset + 2 + ( count * 4 ) ] ;
if ( modbusBridge . buffer [ 2 ] - ( ( count * 4 ) - 3 ) )
( ( uint8_t * ) & value ) [ 3 ] = modbusBridge . buffer [ dataOffset + 3 + ( count * 4 ) ] ;
2022-08-17 18:55:41 +01:00
}
else
{
2023-03-20 20:26:38 +00:00
( ( uint8_t * ) & value ) [ 3 ] = modbusBridge . buffer [ dataOffset + ( count * 4 ) ] ; // Get uint values
( ( uint8_t * ) & value ) [ 2 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 4 ) ] ;
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + 2 + ( count * 4 ) ] ;
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + 3 + ( count * 4 ) ] ;
2022-08-17 18:55:41 +01:00
}
2022-07-19 09:37:03 +01:00
if ( modbusBridge . type = = ModbusBridgeType : : mb_int32 )
2023-04-26 21:27:53 +01:00
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %d " , ( int32_t ) value ) ;
2022-07-19 09:37:03 +01:00
else
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %u " , value ) ;
}
else if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int16 ) | |
( modbusBridge . type = = ModbusBridgeType : : mb_uint16 ) )
{
uint16_t value = 0 ;
2023-05-14 13:18:45 +01:00
if ( modbusBridge . endian = = ModbusBridgeEndian : : mb_lsb )
2022-08-17 18:55:41 +01:00
{
2023-05-14 13:18:45 +01:00
// In lsb mode swap bytes
2023-03-20 20:26:38 +00:00
if ( modbusBridge . buffer [ 2 ] - ( count * 2 ) )
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + ( count * 2 ) ] ;
if ( modbusBridge . buffer [ 2 ] - ( ( count * 2 ) - 1 ) )
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 2 ) ] ;
2022-08-17 18:55:41 +01:00
}
else
{
2023-03-20 20:26:38 +00:00
( ( uint8_t * ) & value ) [ 1 ] = modbusBridge . buffer [ dataOffset + ( count * 2 ) ] ;
( ( uint8_t * ) & value ) [ 0 ] = modbusBridge . buffer [ dataOffset + 1 + ( count * 2 ) ] ;
2022-08-17 18:55:41 +01:00
}
2022-07-19 09:37:03 +01:00
if ( modbusBridge . type = = ModbusBridgeType : : mb_int16 )
2023-04-26 21:27:53 +01:00
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %d " , ( int16_t ) value ) ;
2022-07-19 09:37:03 +01:00
else
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %u " , value ) ;
}
2022-08-17 18:55:41 +01:00
else if ( ( modbusBridge . type = = ModbusBridgeType : : mb_int8 ) | |
( modbusBridge . type = = ModbusBridgeType : : mb_uint8 ) )
{
2023-03-20 20:26:38 +00:00
uint8_t value = modbusBridge . buffer [ dataOffset + ( count * 1 ) ] ;
2022-08-17 18:55:41 +01:00
if ( modbusBridge . type = = ModbusBridgeType : : mb_int8 )
2023-04-26 21:27:53 +01:00
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %d " , ( int8_t ) value ) ;
2022-08-17 18:55:41 +01:00
else
snprintf ( svalue , MBR_MAX_VALUE_LENGTH , " %u " , value ) ;
}
2022-07-19 09:37:03 +01:00
}
ResponseAppend_P ( PSTR ( " %s " ) , svalue ) ;
2022-08-17 18:55:41 +01:00
if ( count < data_count - 1 )
2022-07-19 09:37:03 +01:00
ResponseAppend_P ( PSTR ( " , " ) ) ;
}
ResponseAppend_P ( PSTR ( " ]} " ) ) ;
ResponseJsonEnd ( ) ;
if ( errorcode = = ModbusBridgeError : : noerror )
2024-02-16 14:06:11 +00:00
if ( Settings - > flag6 . mqtt_disable_modbus ) { // SetOption158 If it is activated, Tasmota will not publish ModbusReceived MQTT messages, but it will proccess event trigger rules
XdrvRulesProcess ( 0 ) ;
} else {
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_MODBUS_RECEIVED ) ) ;
}
2022-07-19 09:37:03 +01:00
}
2023-03-20 20:26:38 +00:00
else if ( ( modbusBridge . buffer [ 1 ] = = 15 ) | | ( modbusBridge . buffer [ 1 ] = = 16 ) ) // Write Multiple Registers
2022-08-17 18:55:41 +01:00
{
Response_P ( PSTR ( " { \" " D_JSON_MODBUS_RECEIVED " \" :{ " ) ) ;
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_DEVICE_ADDRESS " \" :%d, " ) , modbusBridge . buffer [ 0 ] ) ;
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_FUNCTION_CODE " \" :%d, " ) , modbusBridge . buffer [ 1 ] ) ;
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_START_ADDRESS " \" :%d, " ) , ( modbusBridge . buffer [ 2 ] < < 8 ) + modbusBridge . buffer [ 3 ] ) ;
2022-11-06 09:23:05 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_LENGTH " \" :%d, " ) , modbusBridgeModbus - > ReceiveCount ( ) ) ;
2023-03-20 20:26:38 +00:00
ResponseAppend_P ( PSTR ( " \" " D_JSON_MODBUS_COUNT " \" :%d " ) , ( modbusBridge . buffer [ 4 ] < < 8 ) + modbusBridge . buffer [ 5 ] ) ;
2022-08-17 18:55:41 +01:00
ResponseAppend_P ( PSTR ( " } " ) ) ;
ResponseJsonEnd ( ) ;
if ( errorcode = = ModbusBridgeError : : noerror )
2024-02-07 14:57:09 +00:00
if ( Settings - > flag6 . mqtt_disable_modbus ) { // SetOption158 If it is activated, Tasmota will not publish ModbusReceived MQTT messages, but it will proccess event trigger rules
XdrvRulesProcess ( 0 ) ;
} else {
MqttPublishPrefixTopicRulesProcess_P ( RESULT_OR_TELE , PSTR ( D_JSON_MODBUS_RECEIVED ) ) ;
}
2022-08-17 18:55:41 +01:00
}
2022-07-25 08:56:35 +01:00
else
errorcode = ModbusBridgeError : : wrongfunctioncode ;
2022-07-19 09:37:03 +01:00
}
if ( errorcode ! = ModbusBridgeError : : noerror )
{
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR Recv Error %d " ) , ( uint8_t ) errorcode ) ;
}
modbusBridge . deviceAddress = 0 ;
}
}
/********************************************************************************************/
2022-07-25 08:56:35 +01:00
//
// Inits the tasmota modbus driver, sets serialport and if TCP enabled allocates a TCP buffer
//
2023-11-20 11:35:06 +00:00
void ModbusBridgeInit ( void ) {
if ( PinUsed ( GPIO_MBR_RX ) & & PinUsed ( GPIO_MBR_TX ) ) {
modbusBridgeModbus = new TasmotaModbus ( Pin ( GPIO_MBR_RX ) , Pin ( GPIO_MBR_TX ) , Pin ( GPIO_MBR_TX_ENA ) ) ;
if ( ModbusBridgeBegin ( ) ) {
modbusBridge . enabled = true ;
2022-07-25 08:56:35 +01:00
# ifdef USE_MODBUS_BRIDGE_TCP
2023-11-20 11:35:06 +00:00
// If TCP bridge is enabled allocate a TCP receive buffer
if ( nullptr = = modbusBridgeTCP . tcp_buf ) modbusBridgeTCP . tcp_buf = ( uint8_t * ) malloc ( MODBUS_BRIDGE_TCP_BUF_SIZE ) ;
if ( nullptr = = modbusBridgeTCP . tcp_buf ) {
ModbusBridgeAllocError ( PSTR ( " TCP " ) ) ;
return ;
}
2023-05-10 13:38:52 +01:00
# ifdef MODBUS_BRIDGE_TCP_DEFAULT_PORT
2023-11-20 11:35:06 +00:00
else {
AddLog ( LOG_LEVEL_INFO , PSTR ( " MBS: MBRTCP Starting server on port %d " ) , MODBUS_BRIDGE_TCP_DEFAULT_PORT ) ;
2023-05-10 13:38:52 +01:00
2023-11-20 11:35:06 +00:00
modbusBridgeTCP . server_tcp = new WiFiServer ( MODBUS_BRIDGE_TCP_DEFAULT_PORT ) ;
modbusBridgeTCP . server_tcp - > begin ( ) ; // start TCP server
modbusBridgeTCP . server_tcp - > setNoDelay ( true ) ;
}
# endif // MODBUS_BRIDGE_TCP_DEFAULT_PORT
# endif // USE_MODBUS_BRIDGE_TCP
2023-05-10 13:38:52 +01:00
}
2022-07-25 08:56:35 +01:00
}
}
# ifdef USE_MODBUS_BRIDGE_TCP
/********************************************************************************************/
//
// Handles data for TCP server and TCP client. Sends requests to Modbus Devices
//
void ModbusTCPHandle ( void )
{
uint8_t c ;
bool busy ; // did we transfer some data?
int32_t buf_len ;
// check for a new client connection
if ( ( modbusBridgeTCP . server_tcp ) & & ( modbusBridgeTCP . server_tcp - > hasClient ( ) ) )
{
WiFiClient new_client = modbusBridgeTCP . server_tcp - > available ( ) ;
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " MBS: MBRTCP Got connection from %s " ) , new_client . remoteIP ( ) . toString ( ) . c_str ( ) ) ;
// Check for IP filtering if it's enabled.
if ( modbusBridgeTCP . ip_filter )
{
if ( modbusBridgeTCP . ip_filter ! = new_client . remoteIP ( ) )
{
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " MBS: MBRTCP Rejected due to filtering " ) ) ;
new_client . stop ( ) ;
}
else
{
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " MBS: MBRTCP Allowed through filter " ) ) ;
}
}
// find an empty slot
uint32_t i ;
for ( i = 0 ; i < nitems ( modbusBridgeTCP . client_tcp ) ; i + + )
2022-07-19 09:37:03 +01:00
{
2022-07-25 08:56:35 +01:00
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
if ( ! client )
2022-07-19 09:37:03 +01:00
{
2022-07-25 08:56:35 +01:00
client = new_client ;
break ;
2022-07-19 09:37:03 +01:00
}
2022-07-25 08:56:35 +01:00
}
if ( i > = nitems ( modbusBridgeTCP . client_tcp ) )
{
i = modbusBridgeTCP . client_next + + % nitems ( modbusBridgeTCP . client_tcp ) ;
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
client . stop ( ) ;
client = new_client ;
2022-07-19 09:37:03 +01:00
}
Avoid sending more then one Modbus TCP response (#22223)
Prevent from sending more then one Modbus TCP response
for a single request.
This might happen in the following scenario where,
shortly after the first response has been sent (4),
the second one will be send (8) (note the same
'TransactionId') triggered by the response to the
'ModbusSend' request issued by Berry (5).
The log excerpt from such a situation:
(1) 21:13:20.510 MBS: MBRTCP to Modbus TransactionId:520, deviceAddress:4, functionCode:3, startAddress:8193, count:1, recvCount:1, recvBytes:2
(2) 21:13:20.523 MBS: Serial Send: 04 03 20 01 00 01 DE 5F
(3) 21:13:20.647 MBS: Serial Received: 04 03 02 0A 28 72 FA
(4) 21:13:20.652 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:11 bytes to client (error:0)
(5) 21:13:20.724 CMD: Grp 0, Cmd 'MODBUSSEND', Idx 1, Len 89, Pld -99, Data '{"deviceAddress":4, "functionCode":6, "startAddress":8192, "type":"uint16", "Values":[6]}'
(6) 21:13:20.743 MBS: Serial Send: 04 06 20 00 00 06 02 5D
(7) 21:13:21.009 MBS: Serial Received: 04 06 20 00 00 06 02 5D
(8) 21:13:21.014 MBS: MBRTCP from Modbus TransactionId:520, deviceAddress:4, writing:12 bytes to client (error:0)
Use 'tcp_transaction_id' field to denote that we already
sent a response.
Signed-off-by: Damian Wrobel <dwrobel@ertelnet.rybnik.pl>
2024-10-02 20:34:48 +01:00
modbusBridgeTCP . tcp_transaction_id = MODBUS_TCP_INVALID_TRANSACTION_ID ;
2022-07-19 09:37:03 +01:00
}
2022-07-25 08:56:35 +01:00
do
{
busy = false ; // exit loop if no data was transferred
// handle data received from TCP
for ( uint32_t i = 0 ; i < nitems ( modbusBridgeTCP . client_tcp ) ; i + + )
{
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
buf_len = 0 ;
while ( client & & ( buf_len < MODBUS_BRIDGE_TCP_BUF_SIZE ) & & ( client . available ( ) ) )
{
c = client . read ( ) ;
if ( c > = 0 )
{
modbusBridgeTCP . tcp_buf [ buf_len + + ] = c ;
busy = true ;
}
}
2022-08-27 08:11:36 +01:00
if ( buf_len > = 12 )
2022-07-25 08:56:35 +01:00
{
uint8_t mbdeviceaddress = ( uint8_t ) modbusBridgeTCP . tcp_buf [ 6 ] ;
uint8_t mbfunctioncode = ( uint8_t ) modbusBridgeTCP . tcp_buf [ 7 ] ;
uint16_t mbstartaddress = ( uint16_t ) ( ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 8 ] ) < < 8 ) | ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 9 ] ) ) ;
2023-10-16 07:26:26 +01:00
uint16_t * writeData = nullptr ;
2022-08-27 08:11:36 +01:00
uint16_t count = 0 ;
2022-08-27 08:11:36 +01:00
2022-07-25 08:56:35 +01:00
modbusBridgeTCP . tcp_transaction_id = ( uint16_t ) ( ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 0 ] ) < < 8 ) | ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 1 ] ) ) ;
2023-03-21 08:39:32 +00:00
if ( mbfunctioncode < = 2 ) // Multiple Coils, Inputs
2022-08-27 08:11:36 +01:00
{
2022-08-27 08:11:36 +01:00
count = ( uint16_t ) ( ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 10 ] ) < < 8 ) | ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 11 ] ) ) ;
2022-11-06 09:23:05 +00:00
modbusBridge . byteCount = ( ( count - 1 ) > > 3 ) + 1 ;
2023-03-21 08:39:32 +00:00
modbusBridge . dataCount = count ;
modbusBridge . type = ModbusBridgeType : : mb_bit ;
2022-08-27 08:11:36 +01:00
}
2023-03-21 08:39:32 +00:00
else if ( mbfunctioncode < = 4 ) // Multiple Holding or input registers
2022-08-27 08:11:36 +01:00
{
2022-08-27 08:11:36 +01:00
count = ( uint16_t ) ( ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 10 ] ) < < 8 ) | ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 11 ] ) ) ;
modbusBridge . byteCount = count * 2 ;
modbusBridge . dataCount = count ;
2023-03-21 08:39:32 +00:00
modbusBridge . type = ModbusBridgeType : : mb_uint16 ;
2022-08-27 08:11:36 +01:00
}
2023-03-21 08:39:32 +00:00
else // Write coil(s) or register(s)
2022-08-27 08:11:36 +01:00
{
2022-11-06 09:23:05 +00:00
// For functioncode 15 & 16 ignore bytecount, modbusBridgeModbus does calculate this
2022-08-27 08:11:36 +01:00
uint8_t dataStartByte = mbfunctioncode < = 6 ? 10 : 13 ;
2022-08-27 08:11:36 +01:00
uint16_t byteCount = ( buf_len - dataStartByte ) ;
2022-08-27 08:11:36 +01:00
modbusBridge . byteCount = 2 ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = 1 ;
2023-03-21 08:39:32 +00:00
modbusBridge . type = ModbusBridgeType : : mb_uint16 ;
2022-08-27 08:11:36 +01:00
2023-10-16 08:42:55 +01:00
writeData = ( uint16_t * ) malloc ( byteCount + 1 ) ;
2022-11-06 09:12:08 +00:00
if ( nullptr = = writeData )
{
ModbusBridgeAllocError ( PSTR ( " write " ) ) ;
return ;
}
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
if ( ( mbfunctioncode = = 15 ) | | ( mbfunctioncode = = 16 ) ) count = ( uint16_t ) ( ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 10 ] ) < < 8 ) | ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ 11 ] ) ) ;
else count = 1 ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
for ( uint16_t dataPointer = 0 ; dataPointer < byteCount ; dataPointer + + )
2022-08-27 08:11:36 +01:00
{
2022-08-27 08:11:36 +01:00
if ( dataPointer % 2 = = 0 )
{
writeData [ dataPointer / 2 ] = ( uint16_t ) ( ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ dataStartByte + dataPointer ] ) < < 8 ) ;
}
else
2022-11-06 09:23:05 +00:00
{
2022-08-27 08:11:36 +01:00
writeData [ dataPointer / 2 ] | = ( ( uint16_t ) modbusBridgeTCP . tcp_buf [ dataStartByte + dataPointer ] ) ;
}
2022-08-27 08:11:36 +01:00
}
}
2022-07-25 08:56:35 +01:00
2022-08-27 09:40:02 +01:00
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( " MBS: MBRTCP to Modbus TransactionId:%d, deviceAddress:%d, functionCode:%d, startAddress:%d, count:%d, recvCount:%d, recvBytes:%d " ) ,
modbusBridgeTCP . tcp_transaction_id , mbdeviceaddress , mbfunctioncode , mbstartaddress , count , modbusBridge . dataCount , modbusBridge . byteCount ) ;
2022-08-27 08:11:36 +01:00
2024-09-09 09:26:20 +01:00
if ( modbusBridgeModbus - > Send ( mbdeviceaddress , mbfunctioncode , mbstartaddress , count , writeData ) = = 0 )
{
modbusBridge . setWaitingForAnswerFromSerial ( true ) ;
}
2022-08-27 08:11:36 +01:00
2023-03-21 08:39:32 +00:00
if ( modbusBridgeTCP . output_mqtt )
{
modbusBridge . deviceAddress = mbdeviceaddress ;
modbusBridge . functionCode = ( ModbusBridgeFunctionCode ) mbfunctioncode ;
modbusBridge . startAddress = mbstartaddress ;
modbusBridge . count = count ;
}
2022-08-27 08:11:36 +01:00
free ( writeData ) ;
2022-07-25 08:56:35 +01:00
}
}
yield ( ) ; // avoid WDT if heavy traffic
} while ( busy ) ;
2022-07-19 09:37:03 +01:00
}
2022-07-25 08:56:35 +01:00
# endif
2022-07-19 09:37:03 +01:00
/*********************************************************************************************\
* Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CmndModbusBridgeSend ( void )
2024-09-09 09:26:20 +01:00
{
if ( ! modbusBridge . isWaitingForAnswerFromSerial ( ) )
{
if ( modbusBridge . command_data = = nullptr )
{
CmndModbusBridgeSend ( XdrvMailbox . data ) ;
return ;
}
else
{
// There is already a command pending
ResponseCmndFailed ( ) ;
return ;
}
}
else
{
if ( modbusBridge . command_data = = nullptr )
{
// Let's store the command data
modbusBridge . command_data = ( char * ) malloc ( XdrvMailbox . data_len ) ;
if ( modbusBridge . command_data = = nullptr )
{
// We couldn't store the command data.
ModbusBridgeAllocError ( PSTR ( " COMMAND " ) ) ;
ResponseCmndFailed ( ) ;
return ;
}
memcpy ( modbusBridge . command_data , XdrvMailbox . data , XdrvMailbox . data_len ) ;
}
else
{
// We're processing a command and yet another is waiting.
ResponseCmndFailed ( ) ;
return ;
}
}
}
void CmndModbusBridgeSend ( char * json_in )
2022-07-19 09:37:03 +01:00
{
2023-10-16 07:26:26 +01:00
uint16_t * writeData = nullptr ;
2022-08-17 18:55:41 +01:00
uint8_t writeDataSize = 0 ;
2022-08-27 08:11:36 +01:00
bool bitMode = false ;
2022-08-17 18:55:41 +01:00
ModbusBridgeError errorcode = ModbusBridgeError : : noerror ;
2024-09-09 09:26:20 +01:00
JsonParser parser ( json_in ) ;
2022-07-19 09:37:03 +01:00
JsonParserObject root = parser . getRootObject ( ) ;
if ( ! root )
return ;
modbusBridge . deviceAddress = root . getUInt ( PSTR ( D_JSON_MODBUS_DEVICE_ADDRESS ) , 0 ) ;
uint8_t functionCode = root . getUInt ( PSTR ( D_JSON_MODBUS_FUNCTION_CODE ) , 0 ) ;
modbusBridge . startAddress = root . getULong ( PSTR ( D_JSON_MODBUS_START_ADDRESS ) , 0 ) ;
2022-08-17 18:55:41 +01:00
2022-07-19 09:37:03 +01:00
const char * stype = root . getStr ( PSTR ( D_JSON_MODBUS_TYPE ) , " uint8 " ) ;
2023-04-29 13:46:38 +01:00
modbusBridge . count = root . getUInt ( PSTR ( D_JSON_MODBUS_COUNT ) , 1 ) ; // Number of values to read / write
2023-05-14 13:18:45 +01:00
const char * sendian = root . getStr ( PSTR ( D_JSON_MODBUS_ENDIAN ) , " undefined " ) ;
modbusBridge . endian = ModbusBridgeEndian : : mb_undefined ;
2023-03-20 20:26:38 +00:00
// If functioncode is 1, 2 or 15, the count is not the number of registers but the number
2023-04-29 13:46:38 +01:00
// of bits to read or write, so calculate the number data bytes to read/write.
2023-05-14 13:18:45 +01:00
if ( ( functionCode = = 1 ) | | ( functionCode = = 2 ) | | ( functionCode = = 15 ) )
{
bitMode = true ;
modbusBridge . endian = ModbusBridgeEndian : : mb_lsb ;
}
// Change endianess when specified
if ( strcmp ( sendian , " msb " ) = = 0 ) modbusBridge . endian = ModbusBridgeEndian : : mb_msb ;
if ( strcmp ( sendian , " lsb " ) = = 0 ) modbusBridge . endian = ModbusBridgeEndian : : mb_lsb ;
2022-07-19 09:37:03 +01:00
if ( modbusBridge . deviceAddress = = 0 )
errorcode = ModbusBridgeError : : wrongdeviceaddress ;
2022-08-17 18:55:41 +01:00
else if ( ( functionCode > ( uint8_t ) ModbusBridgeFunctionCode : : mb_writeSingleRegister ) & &
( functionCode ! = ( uint8_t ) ModbusBridgeFunctionCode : : mb_writeMultipleCoils ) & &
( functionCode ! = ( uint8_t ) ModbusBridgeFunctionCode : : mb_writeMultipleRegisters ) )
errorcode = ModbusBridgeError : : wrongfunctioncode ; // Invalid function code
2022-07-19 09:37:03 +01:00
else
{
modbusBridge . functionCode = static_cast < ModbusBridgeFunctionCode > ( functionCode ) ;
if ( modbusBridge . functionCode = = ModbusBridgeFunctionCode : : mb_undefined )
errorcode = ModbusBridgeError : : wrongfunctioncode ;
}
modbusBridge . type = ModbusBridgeType : : mb_undefined ;
2023-03-20 20:26:38 +00:00
if ( bitMode ) modbusBridge . byteCount = ( modbusBridge . count + 7 ) / 8 ;
2022-08-17 18:55:41 +01:00
if ( strcmp ( stype , " int8 " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_int8 ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : ( ( modbusBridge . count - 1 ) / 2 ) + 1 ;
2022-08-17 18:55:41 +01:00
}
else if ( strcmp ( stype , " int16 " ) = = 0 )
2022-07-19 09:37:03 +01:00
{
modbusBridge . type = ModbusBridgeType : : mb_int16 ;
2022-08-17 18:55:41 +01:00
modbusBridge . dataCount = modbusBridge . count ;
2022-07-19 09:37:03 +01:00
}
else if ( strcmp ( stype , " int32 " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_int32 ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : 2 * modbusBridge . count ;
2022-07-19 09:37:03 +01:00
}
2022-08-17 18:55:41 +01:00
else if ( ( strcmp ( stype , " uint8 " ) = = 0 ) )
{
modbusBridge . type = ModbusBridgeType : : mb_uint8 ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : ( ( modbusBridge . count - 1 ) / 2 ) + 1 ;
2022-08-17 18:55:41 +01:00
}
2022-08-17 18:55:41 +01:00
else if ( ( strcmp ( stype , " uint16 " ) = = 0 ) | | ( strcmp ( stype , " " ) = = 0 ) ) // Default is uint16
2022-07-19 09:37:03 +01:00
{
modbusBridge . type = ModbusBridgeType : : mb_uint16 ;
2022-08-17 18:55:41 +01:00
modbusBridge . dataCount = modbusBridge . count ;
2022-07-19 09:37:03 +01:00
}
else if ( strcmp ( stype , " uint32 " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_uint32 ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : 2 * modbusBridge . count ;
2022-07-19 09:37:03 +01:00
}
else if ( strcmp ( stype , " float " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_float ;
2023-10-08 12:14:53 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : 2 * modbusBridge . count ;
2022-07-19 09:37:03 +01:00
}
else if ( strcmp ( stype , " raw " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_raw ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : ( ( modbusBridge . count - 1 ) / 2 ) + 1 ;
2022-07-19 09:37:03 +01:00
}
2022-08-17 18:55:41 +01:00
else if ( strcmp ( stype , " hex " ) = = 0 )
{
modbusBridge . type = ModbusBridgeType : : mb_hex ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : ( ( modbusBridge . count - 1 ) / 2 ) + 1 ;
2022-08-17 18:55:41 +01:00
}
2022-07-25 08:56:35 +01:00
else if ( strcmp ( stype , " bit " ) = = 0 )
2022-07-19 09:37:03 +01:00
{
2022-07-25 08:56:35 +01:00
modbusBridge . type = ModbusBridgeType : : mb_bit ;
2022-08-27 08:11:36 +01:00
modbusBridge . dataCount = bitMode ? modbusBridge . count : ( ( modbusBridge . count - 1 ) / 16 ) + 1 ;
2022-07-19 09:37:03 +01:00
}
else
errorcode = ModbusBridgeError : : wrongtype ;
2022-08-27 08:11:36 +01:00
// Prevent buffer overflow due to usage of to many registers
2022-08-27 09:54:41 +01:00
if ( ( ! bitMode ) & & ( modbusBridge . dataCount > MBR_MAX_REGISTERS ) )
errorcode = ModbusBridgeError : : wrongcount ;
if ( ( bitMode ) & & ( modbusBridge . dataCount > MBR_MAX_REGISTERS * 8 ) )
2022-07-25 08:56:35 +01:00
errorcode = ModbusBridgeError : : wrongcount ;
2022-07-19 09:37:03 +01:00
2022-08-27 08:11:36 +01:00
// Get Json data for writing
2022-08-17 18:55:41 +01:00
JsonParserArray jsonDataArray = root [ PSTR ( D_JSON_MODBUS_VALUES ) ] . getArray ( ) ;
2022-08-27 08:11:36 +01:00
writeDataSize = jsonDataArray . size ( ) ;
// Check if number of supplied data items is valid
switch ( modbusBridge . functionCode )
{
case ModbusBridgeFunctionCode : : mb_writeMultipleCoils :
// In writeMultipleCoil mode the amount of given data bits is less or equal to the count
switch ( modbusBridge . type )
{
case ModbusBridgeType : : mb_bit :
if ( modbusBridge . count > writeDataSize ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
case ModbusBridgeType : : mb_uint8 :
case ModbusBridgeType : : mb_int8 :
case ModbusBridgeType : : mb_raw :
case ModbusBridgeType : : mb_hex :
if ( modbusBridge . count > writeDataSize * 8 ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
case ModbusBridgeType : : mb_uint16 :
case ModbusBridgeType : : mb_int16 :
if ( modbusBridge . count > writeDataSize * 16 ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
case ModbusBridgeType : : mb_uint32 :
case ModbusBridgeType : : mb_int32 :
if ( modbusBridge . count > writeDataSize * 32 ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
}
break ;
case ModbusBridgeFunctionCode : : mb_writeSingleRegister :
case ModbusBridgeFunctionCode : : mb_writeSingleCoil :
if ( modbusBridge . count ! = 1 ) errorcode = ModbusBridgeError : : wrongcount ;
2022-08-27 08:11:36 +01:00
break ;
2022-08-27 08:11:36 +01:00
case ModbusBridgeFunctionCode : : mb_writeMultipleRegisters :
if ( modbusBridge . count ! = writeDataSize ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
}
// If write data is specified in JSON copy it into writeData array
if ( ( errorcode = = ModbusBridgeError : : noerror ) & & ( jsonDataArray . isArray ( ) ) )
2022-08-17 18:55:41 +01:00
{
2022-08-17 18:55:41 +01:00
if ( modbusBridge . dataCount > 40 )
2022-08-17 18:55:41 +01:00
{
2022-08-17 18:55:41 +01:00
errorcode = ModbusBridgeError : : tomanydata ;
2022-08-17 18:55:41 +01:00
}
else
{
2023-10-16 07:26:26 +01:00
writeData = ( uint16_t * ) malloc ( modbusBridge . dataCount * 2 ) ;
if ( nullptr = = writeData )
2022-11-06 09:12:08 +00:00
{
ModbusBridgeAllocError ( PSTR ( " write " ) ) ;
return ;
}
2022-08-17 18:55:41 +01:00
2022-08-27 08:11:36 +01:00
for ( uint8_t jsonDataArrayPointer = 0 ; jsonDataArrayPointer < writeDataSize ; jsonDataArrayPointer + + )
2022-08-17 18:55:41 +01:00
{
2022-08-27 08:11:36 +01:00
if ( errorcode ! = ModbusBridgeError : : noerror ) break ;
switch ( modbusBridge . type )
2022-08-17 18:55:41 +01:00
{
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_bit :
{
// Initialize current data/register to 0
if ( jsonDataArrayPointer % 16 = = 0 )
2022-08-17 18:55:41 +01:00
{
2022-08-27 08:11:36 +01:00
writeData [ jsonDataArrayPointer / 15 ] = 0 ;
2022-08-17 18:55:41 +01:00
}
2022-08-27 08:11:36 +01:00
// Swap low and high bytes according to modbus specification
uint16_t bitValue = ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) = = 1 ) ? 1 : 0 ;
uint8_t bitPointer = ( jsonDataArrayPointer % 16 ) + 8 ;
if ( bitPointer > 15 ) bitPointer - = 16 ;
writeData [ jsonDataArrayPointer / 16 ] + = bitValue < < bitPointer ;
}
break ;
case ModbusBridgeType : : mb_int8 :
if ( jsonDataArrayPointer % 2 )
writeData [ jsonDataArrayPointer / 2 ] + = ( int8_t ) jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) ;
else
writeData [ jsonDataArrayPointer / 2 ] = ( int8_t ) jsonDataArray [ jsonDataArrayPointer / 2 ] . getInt ( 0 ) < < 8 ;
if ( modbusBridge . dataCount ! = writeDataSize / 2 ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_hex :
case ModbusBridgeType : : mb_raw :
case ModbusBridgeType : : mb_uint8 :
if ( jsonDataArrayPointer % 2 )
writeData [ jsonDataArrayPointer / 2 ] + = ( uint8_t ) jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) ;
else
writeData [ jsonDataArrayPointer / 2 ] = ( uint8_t ) jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) < < 8 ;
if ( modbusBridge . dataCount ! = writeDataSize / 2 ) errorcode = ModbusBridgeError : : wrongcount ;
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_int16 :
2023-05-14 13:18:45 +01:00
writeData [ jsonDataArrayPointer ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 )
: ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) ) ;
2022-08-27 08:11:36 +01:00
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_uint16 :
2023-05-14 13:18:45 +01:00
writeData [ jsonDataArrayPointer ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 )
: ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) ) ;
2022-08-27 08:11:36 +01:00
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_int32 :
2023-05-14 13:18:45 +01:00
writeData [ ( jsonDataArrayPointer * 2 ) ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) )
2022-08-27 08:11:36 +01:00
: ( int16_t ) ( jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) > > 16 ) ;
2023-05-14 13:18:45 +01:00
writeData [ ( jsonDataArrayPointer * 2 ) + 1 ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) > > 16 )
2022-08-27 08:11:36 +01:00
: ( uint16_t ) ( jsonDataArray [ jsonDataArrayPointer ] . getInt ( 0 ) ) ;
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_uint32 :
2023-05-14 13:18:45 +01:00
writeData [ ( jsonDataArrayPointer * 2 ) ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) )
2022-08-27 08:11:36 +01:00
: ( uint16_t ) ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) > > 16 ) ;
2023-05-14 13:18:45 +01:00
writeData [ ( jsonDataArrayPointer * 2 ) + 1 ] = modbusBridge . endian ! = ModbusBridgeEndian : : mb_lsb ? ModbusBridgeSwapEndian16 ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) > > 16 )
2022-08-27 08:11:36 +01:00
: ( uint16_t ) ( jsonDataArray [ jsonDataArrayPointer ] . getUInt ( 0 ) ) ;
break ;
2022-11-06 09:23:05 +00:00
2022-08-27 08:11:36 +01:00
case ModbusBridgeType : : mb_float :
// TODO
default :
errorcode = ModbusBridgeError : : wrongtype ;
break ;
2022-08-17 18:55:41 +01:00
}
2022-08-17 18:55:41 +01:00
}
}
2022-08-27 08:11:36 +01:00
// Adapt data according to modbus protocol
if ( modbusBridge . functionCode = = ModbusBridgeFunctionCode : : mb_writeSingleCoil )
{
writeData [ 0 ] = writeData [ 0 ] ? 0xFF00 : 0x0000 ; // High Byte
}
2022-08-17 18:55:41 +01:00
}
// Handle errorcode and exit function when an error has occured
2022-07-19 09:37:03 +01:00
if ( errorcode ! = ModbusBridgeError : : noerror )
{
2022-08-17 18:55:41 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR Send Error %u " ) , ( uint8_t ) errorcode ) ;
2022-08-17 18:55:41 +01:00
free ( writeData ) ;
2022-07-19 09:37:03 +01:00
return ;
}
2022-08-17 18:55:41 +01:00
// If writing a single coil or single register, the register count is always 1. We also prevent writing data out of range
2022-08-17 18:55:41 +01:00
if ( ( modbusBridge . functionCode = = ModbusBridgeFunctionCode : : mb_writeSingleCoil ) | | ( modbusBridge . functionCode = = ModbusBridgeFunctionCode : : mb_writeSingleRegister ) )
2022-08-17 18:55:41 +01:00
modbusBridge . dataCount = 1 ;
2022-08-17 18:55:41 +01:00
2022-11-06 09:23:05 +00:00
uint8_t error = modbusBridgeModbus - > Send ( modbusBridge . deviceAddress , ( uint8_t ) modbusBridge . functionCode , modbusBridge . startAddress , modbusBridge . dataCount , writeData ) ;
2022-08-27 08:11:36 +01:00
free ( writeData ) ;
2022-11-06 09:23:05 +00:00
2024-09-09 09:26:20 +01:00
if ( error = = 0 )
{
modbusBridge . setWaitingForAnswerFromSerial ( true ) ;
}
else
2022-08-27 08:11:36 +01:00
{
2022-08-27 08:11:36 +01:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " MBS: MBR Driver send error %u " ) , error ) ;
2022-08-27 08:11:36 +01:00
return ;
2022-11-06 09:23:05 +00:00
}
2022-07-19 09:37:03 +01:00
ResponseCmndDone ( ) ;
}
void CmndModbusBridgeSetBaudrate ( void )
{
2022-11-06 09:23:05 +00:00
ModbusBridgeSetBaudrate ( XdrvMailbox . payload ) ;
2022-07-25 18:18:30 +01:00
ResponseCmndNumber ( Settings - > modbus_sbaudrate * 300 ) ;
2022-07-19 09:37:03 +01:00
}
void CmndModbusBridgeSetConfig ( void )
{
// See TasmotaModusConfig for possible options
// ModbusConfig 0..23 where 3 equals 8N1
// ModbusConfig 8N1
if ( XdrvMailbox . data_len > 0 )
{
if ( XdrvMailbox . data_len < 3 )
{ // Use 0..23 as serial config option
if ( ( XdrvMailbox . payload > = TS_SERIAL_5N1 ) & & ( XdrvMailbox . payload < = TS_SERIAL_8O2 ) )
{
2022-11-06 09:23:05 +00:00
ModbusBridgeSetConfig ( XdrvMailbox . payload ) ;
2022-07-19 09:37:03 +01:00
}
}
else if ( ( XdrvMailbox . payload > = 5 ) & & ( XdrvMailbox . payload < = 8 ) )
{
int8_t serial_config = ParseSerialConfig ( XdrvMailbox . data ) ;
if ( serial_config > = 0 )
{
2022-11-06 09:23:05 +00:00
ModbusBridgeSetConfig ( serial_config ) ;
2022-07-19 09:37:03 +01:00
}
}
}
2022-07-25 18:18:30 +01:00
ResponseCmndChar ( GetSerialConfig ( Settings - > modbus_sconfig ) . c_str ( ) ) ;
2022-07-19 09:37:03 +01:00
}
2024-09-09 09:26:20 +01:00
void CmndModbusBridgeSetTimeout ( void )
{
if ( XdrvMailbox . data_len > 0 )
{
const int timeout_ms = XdrvMailbox . payload ;
if ( timeout_ms > 0 ) // Accepts values in the range of: 1..INT_MAX
{
modbusBridge . setModbusSerialTimeout_ms ( timeout_ms ) ;
}
}
ResponseCmndNumber ( modbusBridge . getModbusSerialTimeout_ms ( ) ) ;
}
2022-07-25 08:56:35 +01:00
# ifdef USE_MODBUS_BRIDGE_TCP
//
// Command `TCPStart`
// Params: port,<IPv4 allow>
//
void CmndModbusTCPStart ( void )
{
int32_t tcp_port = XdrvMailbox . payload ;
2023-11-20 11:35:06 +00:00
2022-07-25 08:56:35 +01:00
if ( ArgC ( ) = = 2 )
{
char sub_string [ XdrvMailbox . data_len ] ;
modbusBridgeTCP . ip_filter . fromString ( ArgV ( sub_string , 2 ) ) ;
}
else
{
// Disable whitelist if previously set
modbusBridgeTCP . ip_filter = ( uint32_t ) 0 ;
}
if ( modbusBridgeTCP . server_tcp )
{
AddLog ( LOG_LEVEL_INFO , PSTR ( " MBS: MBRTCP Stopping server " ) ) ;
modbusBridgeTCP . server_tcp - > stop ( ) ;
delete modbusBridgeTCP . server_tcp ;
modbusBridgeTCP . server_tcp = nullptr ;
for ( uint32_t i = 0 ; i < nitems ( modbusBridgeTCP . client_tcp ) ; i + + )
{
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
client . stop ( ) ;
}
}
if ( tcp_port > 0 )
{
AddLog ( LOG_LEVEL_INFO , PSTR ( " MBS: MBRTCP Starting server on port %d " ) , tcp_port ) ;
if ( modbusBridgeTCP . ip_filter )
{
AddLog ( LOG_LEVEL_INFO , PSTR ( " MBS: MBRTCP Filtering %s " ) , modbusBridgeTCP . ip_filter . toString ( ) . c_str ( ) ) ;
}
modbusBridgeTCP . server_tcp = new WiFiServer ( tcp_port ) ;
modbusBridgeTCP . server_tcp - > begin ( ) ; // start TCP server
modbusBridgeTCP . server_tcp - > setNoDelay ( true ) ;
}
ResponseCmndDone ( ) ;
}
//
// Command `Connect`
// Params: port,<IPv4>
//
void CmndModbusTCPConnect ( 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 " MBS: MBRTCP 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 " MBS: MBRTCP connected! " ) ) ;
}
else
{
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " MBS: MBRTCP error connecting! " ) ) ;
}
// find an empty slot
uint32_t i ;
for ( i = 0 ; i < nitems ( modbusBridgeTCP . client_tcp ) ; i + + )
{
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
if ( ! client )
{
client = new_client ;
break ;
}
}
if ( i > = nitems ( modbusBridgeTCP . client_tcp ) )
{
i = modbusBridgeTCP . client_next + + % nitems ( modbusBridgeTCP . client_tcp ) ;
WiFiClient & client = modbusBridgeTCP . client_tcp [ i ] ;
client . stop ( ) ;
client = new_client ;
}
}
else
{
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_TCP " MBS: MBR Usage: port,ip_address " ) ) ;
}
ResponseCmndDone ( ) ;
}
2023-03-21 08:39:32 +00:00
void CmndModbusTCPMqtt ( void )
{
modbusBridgeTCP . output_mqtt = XdrvMailbox . payload ;
ResponseCmndDone ( ) ;
}
2022-07-25 08:56:35 +01:00
# endif
2022-07-19 09:37:03 +01:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv63 ( uint32_t function )
2022-07-19 09:37:03 +01:00
{
bool result = false ;
2023-11-20 11:35:06 +00:00
if ( FUNC_PRE_INIT = = function ) {
2022-07-19 09:37:03 +01:00
ModbusBridgeInit ( ) ;
2023-11-20 11:35:06 +00:00
} else if ( modbusBridge . enabled ) {
switch ( function ) {
case FUNC_LOOP :
ModbusBridgeHandle ( ) ;
2024-09-09 09:26:20 +01:00
// Check whether we can send a stored command
if ( modbusBridge . command_data & & ! modbusBridge . isWaitingForAnswerFromSerial ( ) )
{
CmndModbusBridgeSend ( modbusBridge . command_data ) ;
free ( modbusBridge . command_data ) , modbusBridge . command_data = nullptr ;
}
2022-07-25 08:56:35 +01:00
# ifdef USE_MODBUS_BRIDGE_TCP
2024-09-09 09:26:20 +01:00
if ( ! modbusBridge . isWaitingForAnswerFromSerial ( ) )
{
ModbusTCPHandle ( ) ;
}
2022-07-25 08:56:35 +01:00
# endif
2023-11-20 11:35:06 +00:00
break ;
case FUNC_COMMAND :
result = DecodeCommand ( kModbusBridgeCommands , ModbusBridgeCommand ) ;
break ;
2023-12-27 21:03:56 +00:00
case FUNC_ACTIVE :
result = true ;
break ;
2022-07-19 09:37:03 +01:00
}
}
return result ;
}
# endif // USE_MODBUS_BRIDGE