Skip to main content

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:

  1. Poll CH1 temperature every 5 seconds via Modbus RTU (serial) or Modbus TCP (Ethernet)
  2. Parse the signed 16-bit integer value and convert to degrees Celsius (divide by 10)
  3. Detect sensor-not-connected condition (value = -999)
  4. Write each reading to InfluxDB v3 asynchronously for real-time dashboards
  5. Batch-flush accumulated readings every 30 seconds for efficient storage
  6. On shutdown, synchronously flush any remaining data

ME31 Temperature Register Map​

Register AddressFunction CodeData TypeDescription
0x0190 (400)0x04int16 (signed)CH1 temperature integer value (0.1°C resolution)
0x0191 (401)0x04int16 (signed)CH2 temperature integer value
0x0192 (402)0x04int16 (signed)CH3 temperature integer value
0x0193 (403)0x04int16 (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​

ParameterConnection 0 (RTU)Connection 1 (TCP)
TransportSerial (RS485)TCP Client (Ethernet)
Codecmodbus_rtu_codecmodbus_tcp_codec
Port / Host/dev/ttyUSB0192.168.7.7:502
Baud Rate / Timeout96005000ms
Slave / Unit IDSet 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​

VariableDefaultDescription
INFLUXDB_URLhttp://localhost:8181InfluxDB v3 API endpoint
INFLUXDB_TOKEN(empty)Authentication token
INFLUXDB_DBtemperatureTarget 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) or modbus_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-type modbus_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_lines for periodic batch writes
  • The batch is flushed every 30 seconds via influxdb_batch_write_v3_async() in on_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 pcall to 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):

ModeLogical KeyProtocol-Type KeyDescription
RTUmodbus_rtu_2:30401modbus_rtu_2:input_0190CH1 temperature (int16)
TCPmodbus_tcp_2:30401modbus_tcp_2:input_0190CH1 temperature (int16)

InfluxDB Line Protocol Output​

Each data point is written in InfluxDB line protocol format:

temperature,sensor=me31,channel=ch1 value=25.3
ComponentValueDescription
MeasurementtemperatureMeasurement name
Tag: sensorme31Device identifier
Tag: channelch1Sensor channel
Field: valueTemperature in °C (1 decimal)The actual temperature value

View Full example script on GitHub.