PT100 Temperature Monitoring with Modbus and InfluxDB v3
This recipe demonstrates how to monitor high-precision PT100 sensors using the ME31-XDXX0400 4-channel acquisition module. The pipeline supports both Modbus RTU and Modbus TCP transports, implements Exponential Moving Average (EMA) filtering for noise reduction, and exports telemetry to InfluxDB v3.

What this recipe does
- Dual-Transport Support: Provides a toggle-switch configuration for RS485 (Modbus RTU) or Ethernet (Modbus TCP) communication.
- 1Hz Polling: Requests integer temperature data from Channel 1 every 1000ms.
- EMA Filtering: Applies an Exponential Moving Average (ALPHA = 0.2) to smooth out fluctuations in the PT100 readings.
- InfluxDB v3 Integration: Batch-exports filtered temperature data every 10 seconds using the InfluxDB Line Protocol.
- Startup Inventory: Automatically queries the device firmware version upon script initialization.
- Fault Detection: Logs a warning if the sensor reports a disconnected state (-99.9 °C).
End-to-end data flow
Device and wire protocol
The ME31-XDXX0400 is a 4-channel PT100 acquisition module that functions as a Modbus Slave (RTU) or Modbus Server (TCP).
- Physical Layer: RS485 (9600 8N1) and RJ45 Ethernet (10/100M).
- Scaling: Temperature values are stored as 16-bit signed integers where
Physical Value = Register * 0.1. - Error Handling: A specific value
0xFC19(-999) indicates a disconnected sensor.
The table below maps the primary Modbus registers used in this recipe for temperature and system identification.
| Address | Register Name | Access | Scaling | Unit | Description |
|---|---|---|---|---|---|
0x0190 | CH1_TEMP_INT | Input (04) | 0.1 | °C | Channel 1 Integer Temperature |
0x07DC | FW_VERSION | Holding (03) | 1 | - | Firmware Version Number |
0x2328 | CH1_OFFSET | Holding (03) | 0.1 | °C | Calibration Offset for Channel 1 |
CycBox configuration
The engine is configured with two concurrent connections. The Lua script selects the active path based on a variable flag.

Active Connections Index
| connection_id | Transport | Codec | Target/Port |
|---|---|---|---|
| 0 | Serial Port | Modbus RTU | /dev/ttyUSB0 (9600 8N1) |
| 1 | TCP Client | Modbus TCP | 192.168.7.7:502 |
Configuration JSON
[
{
"app": {
"app_transport": "serial_port_transport",
"app_codec": "modbus_rtu_codec",
"app_transformer": "disable_transformer",
"app_encoding": "UTF-8"
},
"modbus_rtu_codec": {
"with_receive_timeout": 20
},
"serial_port_transport": {
"serial_port_transport_data_bits": 8,
"serial_port_transport_port": "/dev/ttyUSB0",
"serial_port_transport_parity": "none",
"serial_port_transport_baud_rate": 9600,
"serial_port_transport_stop_bits": "1",
"serial_port_transport_flow_control": "none"
}
},
{
"app": {
"app_transport": "tcp_client_transport",
"app_codec": "modbus_tcp_codec",
"app_transformer": "disable_transformer",
"app_encoding": "UTF-8"
},
"tcp_client_transport": {
"tcp_client_transport_timeout": 5000,
"tcp_client_transport_keepalive": true,
"tcp_client_transport_nodelay": true,
"tcp_client_transport_port": 502,
"tcp_client_transport_host": "192.168.7.7"
},
"modbus_tcp_codec": {
"unit_id": 2
}
}
]
Lua pipeline walkthrough
The following script manages the polling lifecycle and data processing. It uses the on_timer callback to maintain a steady sampling rate regardless of downstream latency.
-- ME31-XDXX0400 PT100 Temperature Monitor
-- Configured for Modbus RTU (serial) and Modbus TCP. A flag toggles between the two.
-- Polls CH1 temperature every 1 second, applies an exponential moving average (EMA) filter,
-- and saves the filtered data to InfluxDB every 10 seconds. Device info is read once at startup.
--
-- Device: ME31-XDXX0400 4-Channel PT100 Module
-- Protocol: Modbus RTU (Baud: 9600 8N1) or Modbus TCP
-- Modbus Address: Slave 1 (RTU), Unit 2 (TCP, per config)
--
-- Register Map:
-- 0x0190 CH1_TEMP_INT Input Reg Temperature integer (*0.1 °C). 0xFC19 (-999) = Disconnected
-- 0x07DC FW_VERSION Holding Firmware Version
local CONNECTION_MODE = "RTU" -- Change to "TCP" to use Modbus TCP
local CONN_ID = (CONNECTION_MODE == "TCP") and 1 or 0
local SLAVE_ADDR = 1
local TCP_UNIT_ID = 2
local POLL_MS = 1000
local INFLUX_INTERVAL_MS = 10000
local ALPHA = 0.2 -- Smoothing factor for EMA filter
local last_poll_ms = 0
local last_influx_ms = 0
local filtered_temp = nil
local INFLUX_URL = get_env("INFLUX_URL") or "http://localhost:8181"
local INFLUX_TOKEN = get_env("INFLUX_TOKEN") or "<REDACTED>"
local INFLUX_DB = "cycbox"
function on_start()
log("info", "Starting ME31-XDXX0400 monitoring. Mode: " .. CONNECTION_MODE)
-- Read Firmware Version at startup
if CONNECTION_MODE == "TCP" then
modbus_tcp_read_holding_registers(0x07DC, 1, 100, CONN_ID)
else
modbus_rtu_read_holding_registers(SLAVE_ADDR, 0x07DC, 1, 100, CONN_ID)
end
end
function on_timer(now_ms)
-- Poll CH1 Temperature
if now_ms - last_poll_ms >= POLL_MS then
if CONNECTION_MODE == "TCP" then
modbus_tcp_read_input_registers(0x0190, 1, 0, CONN_ID)
else
modbus_rtu_read_input_registers(SLAVE_ADDR, 0x0190, 1, 0, CONN_ID)
end
last_poll_ms = now_ms
end
-- Save to InfluxDB periodically
if now_ms - last_influx_ms >= INFLUX_INTERVAL_MS then
if filtered_temp ~= nil then
local line_data = string.format("me31_temp,channel=1 temperature=%.2f", filtered_temp)
influxdb_write_v3_async(INFLUX_URL, INFLUX_TOKEN, INFLUX_DB, line_data, "auto", true, false)
end
last_influx_ms = now_ms
end
end
function on_receive()
-- Only process messages from the active connection
if message.connection_id ~= CONN_ID then return false end
local modified = false
-- Check Firmware Version
local fw_key = (CONNECTION_MODE == "TCP") and string.format("modbus_tcp_%d:holding_07DC", TCP_UNIT_ID)
or string.format("modbus_rtu_%d:holding_07DC", SLAVE_ADDR)
local fw_ver = message:get_value(fw_key)
if fw_ver then
message:add_int_value("Firmware_Version", fw_ver)
modified = true
end
-- Check CH1 Temperature
local temp_key = (CONNECTION_MODE == "TCP") and string.format("modbus_tcp_%d:input_0190", TCP_UNIT_ID)
or string.format("modbus_rtu_%d:input_0190", SLAVE_ADDR)
local raw_temp = message:get_value(temp_key)
if raw_temp then
-- Convert uint16 to int16 (Two's complement)
if raw_temp > 32767 then
raw_temp = raw_temp - 65536
end
if raw_temp == -999 then
log("warn", "CH1 Sensor Disconnected")
else
local temp_c = raw_temp * 0.1
-- Apply EMA smoothing filter
if filtered_temp == nil then
filtered_temp = temp_c
else
filtered_temp = ALPHA * temp_c + (1 - ALPHA) * filtered_temp
end
message:add_float_value("CH1_Temperature_Filtered", filtered_temp)
message:add_float_value("CH1_Temperature_Raw", temp_c)
modified = true
end
end
return modified
end
Callback Logic
on_start: Dispatches a one-time request for register0x07DC(Firmware Version).on_timer: Orchestrates two distinct loops. The first triggers a Modbus read every 1s. The second pushes the accumulated filtered value to InfluxDB every 10s.on_receive: This is the parser. It handles two's complement conversion for negative temperatures, scales the integer by 0.1, and calculates the EMA using the formulaEMA_now = (ALPHA * Value) + ((1 - ALPHA) * EMA_prior).
Downstream service contracts
InfluxDB v3
Data is written via the InfluxDB v3 write_async API using Line Protocol.
- Measurement:
me31_temp - Tags:
channel=1 - Fields:
temperature(float) - Precision: Auto
Example Line Protocol Payload:
me31_temp,channel=1 temperature=24.52
Operational concerns
- Environment Variables: The script requires
INFLUX_URLandINFLUX_TOKENto be set in the CycBox environment. - Filtering Response: The
ALPHAvalue of0.2provides moderate smoothing. Increasing this value towards1.0reduces filtering (more responsive, more noise); decreasing it towards0.0increases filtering (smoother, slower response to changes). - Address Conflicts: Note that in the provided session, the Lua script was targeting Slave ID 1, while the manual input templates were configured for Slave ID 2. Ensure the
SLAVE_ADDR(RTU) andTCP_UNIT_ID(TCP) variables in the script match your physical device DIP switch settings.
Frequently asked questions
Why is my reading -99.9?
The ME31-XDXX0400 returns a value of -999 (0xFC19) in the integer register when a PT100 sensor is disconnected or the wiring is faulty. Check your 2-wire or 3-wire connections at the module terminals.
How do I toggle between Modbus RTU and TCP modes?
The Lua script includes a CONNECTION_MODE flag at the top. Set this to "RTU" for serial or "TCP" for Ethernet. The script automatically adjusts the CONN_ID and register keys.
What is the scaling factor for temperature registers?
Temperature registers in this device use a 0.1x scaling factor. The script automatically handles this by multiplying the raw register value by 0.1 to get the actual degrees Celsius.
Gotchas and recommendations
- 2-Wire PT100 Wiring: If using 2-wire sensors, you must short the
CHx-andCOM xterminals as per the manufacturer's reference manual, otherwise the module will report a disconnected sensor state. - TCP Timeout: The Modbus TCP codec is configured with a 5000ms timeout. If the network is congested, the
on_receivecallback may not fire consistently, which will delay the InfluxDB updates. - Secrets Management: Never hardcode the
INFLUX_TOKENin the script if you intend to share the configuration. Always use theget_envfunction to pull secrets from the engine's secure environment storage.