diff --git a/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.cpp b/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.cpp index bd9b7a06b..d6319beb0 100644 --- a/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.cpp +++ b/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.cpp @@ -15,6 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . + + Documentation about modbus protocol: https://www.modbustools.com/modbus.html */ #include "TasmotaModbus.h" @@ -53,24 +55,82 @@ int TasmotaModbus::Begin(long speed, uint32_t config) return result; } -void TasmotaModbus::Send(uint8_t device_address, uint8_t function_code, uint16_t start_address, uint16_t register_count) +uint8_t TasmotaModbus::Send(uint8_t device_address, uint8_t function_code, uint16_t start_address, uint16_t register_count, uint16_t *registers) { - uint8_t frame[8]; + uint8_t *frame; + uint8_t framepointer = 0; + + if (function_code < 5) + { + frame = (uint8_t *)malloc(8); // Addres(1), Function(1), Start Address(2), Registercount (2) or Data(2), CRC(2) + } + else + { + if (register_count > 40) return 14; // Prevent to many allocation of memory while writing data + frame = (uint8_t *)malloc(9 + (register_count * 2)); // Addres(1), Function(1), Start Address(2), Quantity of registers (2), Bytecount(1), Data(2..n), CRC(2) + } mb_address = device_address; // Save address for receipt check - frame[0] = mb_address; // 0xFE default device address or dedicated like 0x01 - frame[1] = function_code; - frame[2] = (uint8_t)(start_address >> 8); // MSB - frame[3] = (uint8_t)(start_address); // LSB - frame[4] = (uint8_t)(register_count >> 8); // MSB - frame[5] = (uint8_t)(register_count); // LSB - uint16_t crc = CalculateCRC(frame, 6); - frame[6] = (uint8_t)(crc); - frame[7] = (uint8_t)(crc >> 8); + frame[framepointer++] = mb_address; // 0xFE default device address or dedicated like 0x01 + frame[framepointer++] = function_code; + frame[framepointer++] = (uint8_t)(start_address >> 8); // MSB + frame[framepointer++] = (uint8_t)(start_address); // LSB + if ((function_code < 5) || (function_code == 15) || (function_code == 16)) + { + frame[framepointer++] = (uint8_t)(register_count >> 8); // MSB + frame[framepointer++] = (uint8_t)(register_count); // LSB + } + + if ((function_code == 5) || (function_code == 6)) + { + if (registers == NULL) + { + free(frame); + return 13; // Register data not specified + } + if (register_count != 1) + { + free(frame); + return 12; // Wrong register count + } + frame[framepointer++] = (uint8_t)(registers[0] >> 8); // MSB + frame[framepointer++] = (uint8_t)(registers[0]); // LSB + } + + if ((function_code == 15) || (function_code == 16)) + { + frame[framepointer++] = register_count * 2; + if (registers == NULL) + { + free(frame); + return 13; // Register data not specified + } + if (register_count == 0) + { + free(frame); + return 12; // Wrong register count + } + for (int registerpointer = 0; registerpointer < register_count; registerpointer++) + { + frame[framepointer++] = (uint8_t)(registers[registerpointer] >> 8); // MSB + frame[framepointer++] = (uint8_t)(registers[registerpointer]); // LSB + } + } + else + { + free(frame); + return 1; // Wrong function code + } + + uint16_t crc = CalculateCRC(frame, framepointer); + frame[framepointer++] = (uint8_t)(crc); + frame[framepointer++] = (uint8_t)(crc >> 8); flush(); - write(frame, sizeof(frame)); + write(frame, framepointer); + free(frame); + return 0; } bool TasmotaModbus::ReceiveReady() diff --git a/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.h b/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.h index 78fc3d50b..a4c5b9629 100644 --- a/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.h +++ b/lib/lib_basic/TasmotaModbus-3.4.0/src/TasmotaModbus.h @@ -34,8 +34,6 @@ class TasmotaModbus : public TasmotaSerial { uint16_t CalculateCRC(uint8_t *frame, uint8_t num); - void Send(uint8_t device_address, uint8_t function_code, uint16_t start_address, uint16_t register_count); - bool ReceiveReady(); /* Return codes: @@ -51,7 +49,11 @@ class TasmotaModbus : public TasmotaSerial { * 9 = Crc error * 10 = Gateway Path Unavailable * 11 = Gateway Target device failed to respond + * 12 = Wrong number of registers + * 13 = Register data not specified + * 14 = To many registers */ + uint8_t Send(uint8_t device_address, uint8_t function_code, uint16_t start_address, uint16_t register_count, uint16_t *registers = NULL); uint8_t ReceiveBuffer(uint8_t *buffer, uint8_t register_count); uint8_t Receive16BitRegister(uint16_t *value); uint8_t Receive32BitRegister(float *value); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino b/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino index b5127deb2..1b4f8027b 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_63_modbus_bridge.ino @@ -27,7 +27,10 @@ * bridge. * * Example Command: + * -- Read Input Register -- * ModbusSend {"deviceaddress": 1, "functioncode": 3, "startaddress": 1, "type":"uint16", "count":2} + * -- Write multiple coils -- + * ModbusSend {"deviceaddress": 1, "functioncode": 15, "startaddress": 1, "type":"uint16", "count":14, "data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14]} \*********************************************************************************************/ #define XDRV_63 63 @@ -49,6 +52,7 @@ #define D_JSON_MODBUS_TYPE "Type" // allready defined #define D_JSON_MODBUS_VALUES "Values" #define D_JSON_MODBUS_LENGTH "Length" +#define D_JSON_MODBUS_DATA "Data" #ifndef USE_MODBUS_BRIDGE_TCP const char kModbusBridgeCommands[] PROGMEM = "Modbus|" // Prefix @@ -104,12 +108,14 @@ enum class ModbusBridgeError enum class ModbusBridgeFunctionCode { mb_undefined = 0, - mb_readCoilstartregister = 1, - mb_readContactstartregister = 2, - mb_readHoldingstartregister = 3, - mb_readInputstartregister = 4, + mb_readCoilStatus = 1, + mb_readContactStatus = 2, + mb_readHoldingRegisters = 3, + mb_readInputRegisters = 4, mb_writeSingleCoil = 5, - mb_writeSinglestartregister = 6 + mb_writeSingleRegister = 6, + mb_writeMultipleCoils = 15, + mb_writeMultipleRegisters = 16 }; enum class ModbusBridgeType @@ -144,6 +150,7 @@ struct ModbusBridge uint8_t deviceAddress = 0; uint8_t count = 0; bool raw = false; + }; ModbusBridge modbusBridge; @@ -154,7 +161,7 @@ ModbusBridge modbusBridge; // bool ModbusBridgeBegin(void) { - if ((Settings->modbus_sbaudrate < 300 / 300) || (Settings->modbus_sbaudrate > 115200 / 300)) Settings->modbus_sbaudrate = (uint8_t)((uint32_t)MBR_BAUDRATE / 300); + if ((Settings->modbus_sbaudrate < 1) || (Settings->modbus_sbaudrate > (115200 / 300))) Settings->modbus_sbaudrate = (uint8_t)((uint32_t)MBR_BAUDRATE / 300); if (Settings->modbus_sconfig > TS_SERIAL_8O2) Settings->modbus_sconfig = TS_SERIAL_8N1; int result = tasmotaModbus->Begin(Settings->modbus_sbaudrate * 300, ConvertSerialConfig(Settings->modbus_sconfig)); // Reinitialize modbus port with new baud rate @@ -257,8 +264,10 @@ void ModbusBridgeHandle(void) errorcode = ModbusBridgeError::wrongdeviceaddress; else if ((uint8_t)modbusBridge.functionCode != (uint8_t)buffer[1]) errorcode = ModbusBridgeError::wrongfunctioncode; - else if ((uint8_t)modbusBridge.registerCount * 2 != (uint8_t)buffer[2]) + else if (((uint8_t)modbusBridge.functionCode < 5) && ((uint8_t)modbusBridge.registerCount * 2 != (uint8_t)buffer[2])) + { errorcode = ModbusBridgeError::wrongregistercount; + } else { if (modbusBridge.type == ModbusBridgeType::mb_raw) @@ -470,6 +479,10 @@ void ModbusTCPHandle(void) void CmndModbusBridgeSend(void) { + uint16_t *writeData = NULL; + uint8_t writeDataSize = 0; + ModbusBridgeError errorcode = ModbusBridgeError::noerror; + JsonParser parser(XdrvMailbox.data); JsonParserObject root = parser.getRootObject(); if (!root) @@ -478,16 +491,16 @@ void CmndModbusBridgeSend(void) 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); + const char *stype = root.getStr(PSTR(D_JSON_MODBUS_TYPE), "uint8"); modbusBridge.count = root.getUInt(PSTR(D_JSON_MODBUS_COUNT), 1); - ModbusBridgeError errorcode = ModbusBridgeError::noerror; if (modbusBridge.deviceAddress == 0) errorcode = ModbusBridgeError::wrongdeviceaddress; - else if (modbusBridge.startAddress == 0) - ; - else if (functionCode > 4) - errorcode = ModbusBridgeError::wrongfunctioncode; // Writing is not supported + 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 else { modbusBridge.functionCode = static_cast(functionCode); @@ -506,7 +519,7 @@ void CmndModbusBridgeSend(void) modbusBridge.type = ModbusBridgeType::mb_int32; modbusBridge.registerCount = 2 * modbusBridge.count; } - else if (strcmp(stype, "uint16") == 0) + else if ((strcmp(stype, "uint16") == 0) || (strcmp(stype, "") == 0)) // Default is uint16 { modbusBridge.type = ModbusBridgeType::mb_uint16; modbusBridge.registerCount = modbusBridge.count; @@ -537,13 +550,39 @@ void CmndModbusBridgeSend(void) if (modbusBridge.registerCount > MBR_MAX_REGISTERS) errorcode = ModbusBridgeError::wrongcount; + // If write data is specified in JSON copy it into writeData array + JsonParserArray jsonDataArray = root[PSTR(D_JSON_MODBUS_DATA)].getArray(); + if (jsonDataArray.isArray()) + { + writeDataSize = jsonDataArray.size(); + + if (modbusBridge.registerCount != writeDataSize) + { + errorcode = ModbusBridgeError::wrongcount; + } + else + { + writeData = (uint16_t *)malloc(writeDataSize); + for (uint8_t jsonDataArrayPointer = 0; jsonDataArrayPointer < writeDataSize; jsonDataArrayPointer++) + { + writeData[jsonDataArrayPointer] = jsonDataArray[jsonDataArrayPointer].getUInt(0); + } + } + } + + // Handle errorcode and exit function when an error has occured if (errorcode != ModbusBridgeError::noerror) { AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBR Send Error %d"), (uint8_t)errorcode); + free(writeData); return; } - tasmotaModbus->Send(modbusBridge.deviceAddress, (uint8_t)modbusBridge.functionCode, modbusBridge.startAddress, modbusBridge.registerCount); + // If writing a single coil or single register, the register count is always 1. We also prevent writing data out of range + if ((modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleCoil) || (modbusBridge.functionCode == ModbusBridgeFunctionCode::mb_writeSingleRegister)) modbusBridge.registerCount = 1; + + tasmotaModbus->Send(modbusBridge.deviceAddress, (uint8_t)modbusBridge.functionCode, modbusBridge.startAddress, modbusBridge.registerCount, writeData); + free(writeData); ResponseCmndDone(); }