mirror of https://github.com/arendst/Tasmota.git
In progess: Developping modbus write functionality
This commit is contained in:
parent
68527f3a12
commit
9881183726
|
@ -15,6 +15,8 @@
|
|||
|
||||
You should have received a copy of the GNU General Public License
|
||||
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"
|
||||
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<ModbusBridgeFunctionCode>(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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue