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.