ME31 PT100 Temperature to InfluxDB
Overview
This example demonstrates how to read PT100 temperature values from an ME31-XDXX0400 module via Modbus RTU or Modbus TCP and write them to InfluxDB v3. It showcases CycBox's capabilities for:
- Dual-mode Modbus communication — switch between RTU (serial) and TCP (Ethernet) with a single flag
- Codec-parsed register values — use the built-in Modbus codec to automatically parse register responses
- Real-time and batch writing to InfluxDB v3 — async single writes for dashboards plus periodic batch flushes
- Signed integer handling — correctly interpret negative temperatures from unsigned 16-bit registers
This is a common pattern in industrial monitoring where temperature data from Modbus sensors needs to be stored in a time-series database for trending and alerting.
Scenario
An ME31-XDXX0400 PT100 temperature acquisition module is connected via RS485 serial port or Ethernet. The module supports up to 4 channels of PT100 temperature sensors. The requirements are:
- Poll CH1 temperature every 5 seconds via Modbus RTU (serial) or Modbus TCP (Ethernet)
- Parse the signed 16-bit integer value and convert to degrees Celsius (divide by 10)
- Detect sensor-not-connected condition (value = -999)
- Write each reading to InfluxDB v3 asynchronously for real-time dashboards
- Batch-flush accumulated readings every 30 seconds for efficient storage
- On shutdown, synchronously flush any remaining data
ME31 Temperature Register Map
| Register Address | Function Code | Data Type | Description |
|---|---|---|---|
| 0x0190 (400) | 0x04 | int16 (signed) | CH1 temperature integer value (0.1°C resolution) |
| 0x0191 (401) | 0x04 | int16 (signed) | CH2 temperature integer value |
| 0x0192 (402) | 0x04 | int16 (signed) | CH3 temperature integer value |
| 0x0193 (403) | 0x04 | int16 (signed) | CH4 temperature integer value |
Value interpretation:
- Divide by 10 to get °C (e.g.,
0x012C= 300 = 30.0°C) - Negative values use two's complement (e.g.,
0xFFCE= -50 = -5.0°C) 0xFC19(-999) indicates sensor not connected
Configuration
CycBox is configured with two connections:
- Connection 0: Serial port with Modbus RTU codec for polling via RS485
- Connection 1: TCP client with Modbus TCP codec for polling via Ethernet
{
"version": "1.0.0",
"name": "ME31 PT100 Temperature to InfluxDB",
"description": "Read PT100 temperature from ME31 module via Modbus RTU or TCP, publish to InfluxDB v3",
"configs": [
{
"app": {
"app_transport": "serial",
"app_codec": "modbus_rtu_codec",
"app_transformer": "disable",
"app_encoding": "UTF-8"
},
"serial": {
"serial_port": "/dev/ttyUSB0",
"serial_baud_rate": 9600,
"serial_data_bits": 8,
"serial_parity": "none",
"serial_stop_bits": "1",
"serial_flow_control": "none"
},
"modbus_rtu_codec": {
"with_receive_timeout": 20
}
},
{
"app": {
"app_transport": "tcp_client",
"app_codec": "modbus_tcp_codec",
"app_transformer": "disable",
"app_encoding": "UTF-8"
},
"tcp_client": {
"tcp_client_host": "192.168.7.7",
"tcp_client_port": 502,
"tcp_client_timeout": 5000,
"tcp_client_keepalive": true,
"tcp_client_nodelay": true
},
"modbus_tcp_codec": {
"unit_id": 2
}
}
]
}
Connection Details
| Parameter | Connection 0 (RTU) | Connection 1 (TCP) |
|---|---|---|
| Transport | Serial (RS485) | TCP Client (Ethernet) |
| Codec | modbus_rtu_codec | modbus_tcp_codec |
| Port / Host | /dev/ttyUSB0 | 192.168.7.7:502 |
| Baud Rate / Timeout | 9600 | 5000ms |
| Slave / Unit ID | Set in script (2) | 2 |
Lua Script Logic
The script uses environment variables for InfluxDB configuration and a USE_RTU flag to switch between RTU and TCP
modes.
Environment Variables
| Variable | Default | Description |
|---|---|---|
INFLUXDB_URL | http://localhost:8181 | InfluxDB v3 API endpoint |
INFLUXDB_TOKEN | (empty) | Authentication token |
INFLUXDB_DB | temperature | Target database name |
Script Breakdown
-- InfluxDB v3 configuration
local INFLUXDB_URL = get_env("INFLUXDB_URL") or "http://localhost:8181"
local INFLUXDB_TOKEN = get_env("INFLUXDB_TOKEN") or ""
local INFLUXDB_DB = get_env("INFLUXDB_DB") or "temperature"
-- ME31 Modbus settings
local SLAVE_ADDRESS = 2 -- Modbus slave address
local TEMP_REG_START = 400 -- 0x0190: CH1 temperature integer register
local USE_RTU = true -- true: RTU (connection 0), false: TCP (connection 1)
How It Works
1. Periodic Polling (on_timer)
function on_timer(timestamp_ms)
read_counter = read_counter + 100
if read_counter >= READ_INTERVAL then
read_counter = 0
if USE_RTU then
modbus_rtu_read_input_registers(SLAVE_ADDRESS, TEMP_REG_START, 1, 0, 0)
else
modbus_tcp_read_input_registers(TEMP_REG_START, 1, 0, 1)
end
end
end
on_timer()is called every 100ms- Sends a Modbus read request every 5 seconds
- Uses
modbus_rtu_read_input_registers()for RTU mode (connection 0) ormodbus_tcp_read_input_registers()for TCP mode (connection 1)
2. Response Parsing (on_receive)
function on_receive()
local register_num = 30001 + TEMP_REG_START
local raw_value
if USE_RTU then
raw_value = message:get_value(
string.format("modbus_rtu_%d:%d", SLAVE_ADDRESS, register_num))
else
raw_value = message:get_value(
string.format("modbus_tcp_%d:%d", SLAVE_ADDRESS, register_num))
end
if raw_value == nil then return false end
-- Convert unsigned 16-bit to signed
if raw_value >= 0x8000 then
raw_value = raw_value - 0x10000
end
-- -999 means sensor not connected
if raw_value == -999 then
log("warn", "CH1 sensor not connected (value = -999)")
return false
end
local temp = raw_value / 10.0
end
- Reads the codec-parsed register value using
message:get_value()with the logical key - The Modbus codec assigns two keys per register: logical
modbus_rtu_{slave}:{30001 + addr}and protocol-typemodbus_rtu_{slave}:input_{addr:04X} - Converts unsigned 16-bit to signed for correct negative temperature handling
- Filters out the sensor-not-connected sentinel value (-999)
- Divides by 10 to convert to degrees Celsius
3. InfluxDB Writing — Real-time and Batch
-- Add chart value for UI display
message:add_float_value("CH1_Temp", temp)
-- Async single write for real-time dashboard
influxdb_write_v3_async(INFLUXDB_URL, INFLUXDB_TOKEN, INFLUXDB_DB,
string.format("temperature,sensor=me31,channel=ch1 value=%.1f", temp),
"auto", true, true)
-- Buffer for batch flush
pending_lines[#pending_lines + 1] =
string.format("temperature,sensor=me31,channel=ch1 value=%.1f", temp)
- Each reading is written immediately via
influxdb_write_v3_async()for real-time visibility - Readings are also buffered in
pending_linesfor periodic batch writes - The batch is flushed every 30 seconds via
influxdb_batch_write_v3_async()inon_timer()
4. Graceful Shutdown (on_stop)
function on_stop()
if #pending_lines == 0 then return end
local ok, result = pcall(influxdb_batch_write_v3,
INFLUXDB_URL, INFLUXDB_TOKEN, INFLUXDB_DB,
pending_lines, "auto", true, false)
pending_lines = {}
end
- On shutdown, any remaining buffered data is flushed synchronously using
influxdb_batch_write_v3()(blocking) - Uses
pcallto handle errors gracefully without crashing
Modbus Codec Value IDs
The Modbus codecs automatically parse register values and assign two keys per register:
- Logical key:
modbus_{rtu|tcp}_{slave}:{30001 + register_address}(Modicon convention) - Protocol-type key:
modbus_{rtu|tcp}_{slave}:input_{addr:04X}(hex address)
For this example with slave address 2 and register 400 (0x0190):
| Mode | Logical Key | Protocol-Type Key | Description |
|---|---|---|---|
| RTU | modbus_rtu_2:30401 | modbus_rtu_2:input_0190 | CH1 temperature (int16) |
| TCP | modbus_tcp_2:30401 | modbus_tcp_2:input_0190 | CH1 temperature (int16) |
InfluxDB Line Protocol Output
Each data point is written in InfluxDB line protocol format:
temperature,sensor=me31,channel=ch1 value=25.3
| Component | Value | Description |
|---|---|---|
| Measurement | temperature | Measurement name |
| Tag: sensor | me31 | Device identifier |
| Tag: channel | ch1 | Sensor channel |
| Field: value | Temperature in °C (1 decimal) | The actual temperature value |
View Full example script on GitHub.