I'm facing some issues with a Modbus RTU implementation. I have 2x Arduino MKR Zeros with RS485 hats/expansions as my 2 slave devices (using the ArduinoModbus library). I am trying to poll the devices from my PC (Windows) using python and the pymodbus library, running at 9600 baud.
I can succesfully transfer data. The initial sanity test was a simple analogRead() on one of the Arduinos (sensor 1), writing to it's internal holding register and then having the pymodbus master poll/request that register.
I've now connected a second Arduino (sensor 2) which has an I2C connection to a flow sensor. That arduino is running reads of the sensor over I2C and updating 5x holding registers with the data. The master (PC) is polling both Arduinos (sensors 1 and 2) one after the other. It is always successful in acquiring sensor 1's data (only 1 register) but intermittently fails getting sensor 2's data (5 registers). The python console looks like this:
Sensor 2: 0,25000,0,0, 0
Sensor 2: 0,25000,0,0, 0
Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response
Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response
Sensor 2: 0,25000,0,0, 0
Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response
Sensor 2: 0,25000,0,0, 0
Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response
A deeper look at the logs reveals the issue is not all the Bytes are making it accross, see below:
11/24/2022 03:52:59 PM Running transaction 4
11/24/2022 03:52:59 PM SEND: 0x1 0x3 0x0 0x0 0x0 0x5 0x85 0xc9
11/24/2022 03:52:59 PM Changing state to IDLE - Last Frame End - 1669265577.447308, Current Time stamp - 1669265579.457942
11/24/2022 03:52:59 PM New Transaction state "SENDING"
11/24/2022 03:52:59 PM Changing transaction state from "SENDING" to "WAITING FOR REPLY"
11/24/2022 03:52:59 PM {msg_start} received, Expected 15 bytes Received 11 bytes !!!!
11/24/2022 03:52:59 PM Changing transaction state from "WAITING FOR REPLY" to "PROCESSING REPLY"
11/24/2022 03:52:59 PM RECV: 0x1 0x3 0xa 0x0 0x0 0x61 0xa8 0x0 0x0 0x0 0x0
11/24/2022 03:52:59 PM Frame - [b'\x01\x03\n\x00\x00a\xa8\x00\x00\x00\x00'] not ready
11/24/2022 03:52:59 PM Getting transaction 1
11/24/2022 03:52:59 PM Changing transaction state from "PROCESSING REPLY" to "TRANSACTION_COMPLETE"
11/24/2022 03:52:59 PM Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response
11/24/2022 03:53:01 PM Current transaction state - TRANSACTION_COMPLETE
I've now removed asking sensor 1 for data at all and have my python script only requesting from sensor 2 (the problem sensor) but the problem still remains, my python script is as follows:
import serial
import time
import logging
from pymodbus.client import ModbusSerialClient
from pymodbus.transaction import ModbusRtuFramer
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler('main.log', 'w', 'utf-8')
handler.setFormatter(logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p'))
logger.addHandler(handler)
client = ModbusSerialClient("COM12", ModbusRtuFramer, baudrate=9600, timeout=10, reset_socket=False)
client.connect()
while(1):
c2 = client.read_holding_registers(0,5,1)
if c2.isError():
logger.error(msg=c2)
print(c2)
else:
print(f"Sensor 2: {c2.getRegister(0)},{c2.getRegister(1)},{c2.getRegister(2)},{c2.getRegister(3)}, {c2.getRegister(4)}")
time.sleep(2)
I'm not quite sure what's misfiring... I can stream the sensor 2 data perfectly well using a GUI like QModMaster which I know uses libmodbus under the hood. Should I be instead working with the python library pylibmodbus? It requires a compiled libmodbus locally which is a bit of a headache for Windows...
Am I missing a setting for pymodbus that could help?
I have tried varying the pymodbus timeout which did not work. I have tried decreasing the sample rate for the flow sensor to reduce the frequecy and ensure the Arduino is free/available for Modbus requests. This did not work, in fact decreasing it made the problem worse.
I have tried adding basic delays to the python code (time.sleep(2)
) to slow down the Modbus requests but this had no impact on the errors.
Hope someone knows what's going on as I've spent ages trawling through online sources to find the answer to no avail. If more clarification is needed from my end I can provide :)
Thanks!
P.S. Arduino code below for reference
#include <Arduino.h>
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <Wire.h>
/**
* Modbus slave/server
*/
#define SAMPLE_RATE 50
const int ADDRESS = 0x08; // Sensor I2C Address
const float SCALE_FACTOR_FLOW = 500.0; // Scale Factor for flow rate measurement
const float SCALE_FACTOR_TEMP = 200.0; // Scale Factor for temperature measurement
int count = 0;
unsigned long startMillis;
unsigned long currentMillis;
enum error_types{
no_error,
write_mode_error,
read_error
};
enum error_types error;
// Protoypes
int stop_continuous_measurement();
int start_continous_measurement();
void setup() {
int ret;
// Start Serial and I2C
Serial.begin(9600); // initialize serial communication
Wire.begin(); // join i2c bus (address optional for master)
// Set up MODBUS
if (!ModbusRTUServer.begin(0x01,9600)) {
Serial.println("Could not begin ModbusRTU server...");
while(1);
}
// configure holding registers at address 0x00, 4 registers for data
ModbusRTUServer.configureHoldingRegisters(0x00, 5);
// start sensor
do {
// Soft reset the sensor
Wire.beginTransmission(0x00);
Wire.write(0x06);
ret = Wire.endTransmission();
if (ret != 0) {
Serial.println("Error while sending soft reset command, retrying...");
delay(500); // wait long enough for chip reset to complete
}
} while (ret != 0);
delay(50); // wait long enough for chip reset to complete
// To perform a measurement, first send 0x3608 to switch to continuous
if(start_continous_measurement() !=0) {
error = write_mode_error;
}
startMillis = millis();
}
void loop() {
ModbusRTUServer.poll();
uint16_t aux_value;
uint16_t sensor_flow_value;
uint16_t sensor_temp_value;
int16_t signed_flow_value;
int16_t signed_temp_value;
float scaled_flow_value;
float scaled_temp_value;
byte aux_crc;
byte sensor_flow_crc;
byte sensor_temp_crc;
// measurement mode (H20 calibration), then read 3x (2 bytes + 1 CRC byte) from the sensor.
// To perform a IPA based measurement, send 0x3615 instead.
// Check datasheet for available measurement commands.
error = no_error;
currentMillis = millis();
if(currentMillis - startMillis > SAMPLE_RATE){
Wire.requestFrom(ADDRESS, 9);
if (Wire.available() < 9) {
error = read_error;
}
else{
sensor_flow_value = Wire.read() << 8; // read the MSB from the sensor
sensor_flow_value |= Wire.read(); // read the LSB from the sensor
sensor_flow_crc = Wire.read();
sensor_temp_value = Wire.read() << 8; // read the MSB from the sensor
sensor_temp_value |= Wire.read(); // read the LSB from the sensor
sensor_temp_crc = Wire.read();
aux_value = Wire.read() << 8; // read the MSB from the sensor
aux_value |= Wire.read(); // read the LSB from the sensor
aux_crc = Wire.read();
signed_flow_value = (int16_t) sensor_flow_value;
scaled_flow_value = ((float) signed_flow_value) / SCALE_FACTOR_FLOW;
signed_temp_value = (int16_t) sensor_temp_value;
scaled_temp_value = ((float) signed_temp_value) / SCALE_FACTOR_TEMP;
// write to MODBUS registers
ModbusRTUServer.holdingRegisterWrite(0, (uint16_t) count);
ModbusRTUServer.holdingRegisterWrite(1, (uint16_t) scaled_temp_value*1000);
ModbusRTUServer.holdingRegisterWrite(2, (uint16_t) scaled_flow_value*1000);
ModbusRTUServer.holdingRegisterWrite(3,(uint16_t) aux_value);
ModbusRTUServer.holdingRegisterWrite(4, (uint16_t) error);
}
startMillis = currentMillis;
}
}
int start_continous_measurement() {
Wire.beginTransmission(ADDRESS);
Wire.write(0x36);
Wire.write(0x08);
return Wire.endTransmission();
}
int stop_continuous_measurement() {
// To stop the continuous measurement, first send 0x3FF9.
Wire.beginTransmission(ADDRESS);
Wire.write(0x3F);
Wire.write(0xF9);
return Wire.endTransmission();
}
The log shows that data is received only partially: Expected 15 bytes Received 11 bytes
. This may be caused by wrong inter-character timing, i.e. a silent time between characters is erroneously interpreted as end of message. By specifying client.strict = False
the Modbus specification inter-character timing is enforced.
Also see the similar story: Pymodbus : Wrong byte count in response, and the pymodbus issue.