In progess: Developping modbus write functionality

This commit is contained in:
JeroenSt 2022-08-17 19:55:41 +02:00
parent 68527f3a12
commit 9881183726
3 changed files with 129 additions and 28 deletions

View File

@ -15,6 +15,8 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
Documentation about modbus protocol: https://www.modbustools.com/modbus.html
*/ */
#include "TasmotaModbus.h" #include "TasmotaModbus.h"
@ -53,24 +55,82 @@ int TasmotaModbus::Begin(long speed, uint32_t config)
return result; 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 mb_address = device_address; // Save address for receipt check
frame[0] = mb_address; // 0xFE default device address or dedicated like 0x01 frame[framepointer++] = mb_address; // 0xFE default device address or dedicated like 0x01
frame[1] = function_code; frame[framepointer++] = function_code;
frame[2] = (uint8_t)(start_address >> 8); // MSB frame[framepointer++] = (uint8_t)(start_address >> 8); // MSB
frame[3] = (uint8_t)(start_address); // LSB frame[framepointer++] = (uint8_t)(start_address); // LSB
frame[4] = (uint8_t)(register_count >> 8); // MSB if ((function_code < 5) || (function_code == 15) || (function_code == 16))
frame[5] = (uint8_t)(register_count); // LSB {
uint16_t crc = CalculateCRC(frame, 6); frame[framepointer++] = (uint8_t)(register_count >> 8); // MSB
frame[6] = (uint8_t)(crc); frame[framepointer++] = (uint8_t)(register_count); // LSB
frame[7] = (uint8_t)(crc >> 8); }
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(); flush();
write(frame, sizeof(frame)); write(frame, framepointer);
free(frame);
return 0;
} }
bool TasmotaModbus::ReceiveReady() bool TasmotaModbus::ReceiveReady()

View File

@ -34,8 +34,6 @@ class TasmotaModbus : public TasmotaSerial {
uint16_t CalculateCRC(uint8_t *frame, uint8_t num); 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(); bool ReceiveReady();
/* Return codes: /* Return codes:
@ -51,7 +49,11 @@ class TasmotaModbus : public TasmotaSerial {
* 9 = Crc error * 9 = Crc error
* 10 = Gateway Path Unavailable * 10 = Gateway Path Unavailable
* 11 = Gateway Target device failed to respond * 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 ReceiveBuffer(uint8_t *buffer, uint8_t register_count);
uint8_t Receive16BitRegister(uint16_t *value); uint8_t Receive16BitRegister(uint16_t *value);
uint8_t Receive32BitRegister(float *value); uint8_t Receive32BitRegister(float *value);

View File

@ -27,7 +27,10 @@
* bridge. * bridge.
* *
* Example Command: * Example Command:
* -- Read Input Register --
* ModbusSend {"deviceaddress": 1, "functioncode": 3, "startaddress": 1, "type":"uint16", "count":2} * 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 #define XDRV_63 63
@ -49,6 +52,7 @@
#define D_JSON_MODBUS_TYPE "Type" // allready defined #define D_JSON_MODBUS_TYPE "Type" // allready defined
#define D_JSON_MODBUS_VALUES "Values" #define D_JSON_MODBUS_VALUES "Values"
#define D_JSON_MODBUS_LENGTH "Length" #define D_JSON_MODBUS_LENGTH "Length"
#define D_JSON_MODBUS_DATA "Data"
#ifndef USE_MODBUS_BRIDGE_TCP #ifndef USE_MODBUS_BRIDGE_TCP
const char kModbusBridgeCommands[] PROGMEM = "Modbus|" // Prefix const char kModbusBridgeCommands[] PROGMEM = "Modbus|" // Prefix
@ -104,12 +108,14 @@ enum class ModbusBridgeError
enum class ModbusBridgeFunctionCode enum class ModbusBridgeFunctionCode
{ {
mb_undefined = 0, mb_undefined = 0,
mb_readCoilstartregister = 1, mb_readCoilStatus = 1,
mb_readContactstartregister = 2, mb_readContactStatus = 2,
mb_readHoldingstartregister = 3, mb_readHoldingRegisters = 3,
mb_readInputstartregister = 4, mb_readInputRegisters = 4,
mb_writeSingleCoil = 5, mb_writeSingleCoil = 5,
mb_writeSinglestartregister = 6 mb_writeSingleRegister = 6,
mb_writeMultipleCoils = 15,
mb_writeMultipleRegisters = 16
}; };
enum class ModbusBridgeType enum class ModbusBridgeType
@ -144,6 +150,7 @@ struct ModbusBridge
uint8_t deviceAddress = 0; uint8_t deviceAddress = 0;
uint8_t count = 0; uint8_t count = 0;
bool raw = false; bool raw = false;
}; };
ModbusBridge modbusBridge; ModbusBridge modbusBridge;
@ -154,7 +161,7 @@ ModbusBridge modbusBridge;
// //
bool ModbusBridgeBegin(void) 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; 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 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; errorcode = ModbusBridgeError::wrongdeviceaddress;
else if ((uint8_t)modbusBridge.functionCode != (uint8_t)buffer[1]) else if ((uint8_t)modbusBridge.functionCode != (uint8_t)buffer[1])
errorcode = ModbusBridgeError::wrongfunctioncode; 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; errorcode = ModbusBridgeError::wrongregistercount;
}
else else
{ {
if (modbusBridge.type == ModbusBridgeType::mb_raw) if (modbusBridge.type == ModbusBridgeType::mb_raw)
@ -470,6 +479,10 @@ void ModbusTCPHandle(void)
void CmndModbusBridgeSend(void) void CmndModbusBridgeSend(void)
{ {
uint16_t *writeData = NULL;
uint8_t writeDataSize = 0;
ModbusBridgeError errorcode = ModbusBridgeError::noerror;
JsonParser parser(XdrvMailbox.data); JsonParser parser(XdrvMailbox.data);
JsonParserObject root = parser.getRootObject(); JsonParserObject root = parser.getRootObject();
if (!root) if (!root)
@ -478,16 +491,16 @@ void CmndModbusBridgeSend(void)
modbusBridge.deviceAddress = root.getUInt(PSTR(D_JSON_MODBUS_DEVICE_ADDRESS), 0); modbusBridge.deviceAddress = root.getUInt(PSTR(D_JSON_MODBUS_DEVICE_ADDRESS), 0);
uint8_t functionCode = root.getUInt(PSTR(D_JSON_MODBUS_FUNCTION_CODE), 0); uint8_t functionCode = root.getUInt(PSTR(D_JSON_MODBUS_FUNCTION_CODE), 0);
modbusBridge.startAddress = root.getULong(PSTR(D_JSON_MODBUS_START_ADDRESS), 0); modbusBridge.startAddress = root.getULong(PSTR(D_JSON_MODBUS_START_ADDRESS), 0);
const char *stype = root.getStr(PSTR(D_JSON_MODBUS_TYPE), "uint8"); const char *stype = root.getStr(PSTR(D_JSON_MODBUS_TYPE), "uint8");
modbusBridge.count = root.getUInt(PSTR(D_JSON_MODBUS_COUNT), 1); modbusBridge.count = root.getUInt(PSTR(D_JSON_MODBUS_COUNT), 1);
ModbusBridgeError errorcode = ModbusBridgeError::noerror;
if (modbusBridge.deviceAddress == 0) if (modbusBridge.deviceAddress == 0)
errorcode = ModbusBridgeError::wrongdeviceaddress; errorcode = ModbusBridgeError::wrongdeviceaddress;
else if (modbusBridge.startAddress == 0) else if ((functionCode > (uint8_t)ModbusBridgeFunctionCode::mb_writeSingleRegister) &&
; (functionCode != (uint8_t)ModbusBridgeFunctionCode::mb_writeMultipleCoils) &&
else if (functionCode > 4) (functionCode != (uint8_t)ModbusBridgeFunctionCode::mb_writeMultipleRegisters))
errorcode = ModbusBridgeError::wrongfunctioncode; // Writing is not supported errorcode = ModbusBridgeError::wrongfunctioncode; // Invalid function code
else else
{ {
modbusBridge.functionCode = static_cast<ModbusBridgeFunctionCode>(functionCode); modbusBridge.functionCode = static_cast<ModbusBridgeFunctionCode>(functionCode);
@ -506,7 +519,7 @@ void CmndModbusBridgeSend(void)
modbusBridge.type = ModbusBridgeType::mb_int32; modbusBridge.type = ModbusBridgeType::mb_int32;
modbusBridge.registerCount = 2 * modbusBridge.count; 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.type = ModbusBridgeType::mb_uint16;
modbusBridge.registerCount = modbusBridge.count; modbusBridge.registerCount = modbusBridge.count;
@ -537,13 +550,39 @@ void CmndModbusBridgeSend(void)
if (modbusBridge.registerCount > MBR_MAX_REGISTERS) if (modbusBridge.registerCount > MBR_MAX_REGISTERS)
errorcode = ModbusBridgeError::wrongcount; 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) if (errorcode != ModbusBridgeError::noerror)
{ {
AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBR Send Error %d"), (uint8_t)errorcode); AddLog(LOG_LEVEL_DEBUG, PSTR("MBS: MBR Send Error %d"), (uint8_t)errorcode);
free(writeData);
return; 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(); ResponseCmndDone();
} }