tMDS-16 - Temperature meter with Modbus interface

tMDS-16 is a temperature measurement module designed for Modbus RTU-based systems. It features two independent 1-Wire buses, each capable of connecting up to 8 DS18B20 temperature sensors, allowing for a total of 16 sensors.

Temperature data is made available via Modbus registers, which include both the current sensor readings and corresponding offset values used for calibration. This ensures accurate and consistent measurements across all connected sensors.

The tMDS-16 is primarily designed to function as an extension module for LK4 or LK3.5+ devices (also compatible with LK3.9), enabling scalable and distributed temperature monitoring.
A ready-to-use configuration file for these devices is available, allowing for quick and hassle-free integration.

By default, the device uses Modbus Slave ID 1, which can be changed via the USB serial console.

Basic features #

Technical specifications #

Power supply5-12 V DC (connector)
5 V (USB)
Sensor supportUp to 2 x 8 DS18B20 sensors
Interfaces1-Wire: 2 buses via RJ12 and 3-position terminal block,
Modbus RTU (RS485),
Serial (over USB; 115200 baud)
Dimensions74 x 40 x 30 mm (without DIN handle)
Weight44 g

Integration with LK #

The tMDS-16 integrates seamlessly with LK4 and LK3.5+ devices. It can be powered directly from the LK controller using its 5V and GND outputs. Then, connect it—either alone or together with other Modbus devices—to the LK’s Modbus A+ and B− terminals to establish communication.

Once connected, configure a custom Modbus module on the LK using the ready-to-use configuration preset available in the Downloads section. You can easily modify this preset to read only the parameters you need, such as selected temperature sensors or calibration offsets.

Firmware update #

To update the firmware, the tMDS-16 must be placed into bootloader mode. In this mode, the device appears as a mass storage device named RPI-RP2 when connected to a PC. Simply copy the new firmware file to that storage. After the upload, the device will automatically restart and resume normal operation with the new firmware.

Enabling bootloader mode #

You can enable bootloader mode in one of two ways:

💡 Firmware files are available in the Downloads section.

Basic configuration with function button #

The function button on the tMDS-16 offers a simple and intuitive way to perform essential setup actions – no USB connection required. This makes it ideal for quick configuration in the field.

The button is located next to the 1-Wire bus connectors, with a status LED positioned between them. The LED provides feedback indicating which function is about to be executed, depending on how long the button is held.

How it works #

When holding the button, the status LED will blink to signal the selected action:

Press Duration LED Feedback Action Triggered
< 2 seconds 1 short blink shortly after press update_sensors – Detect and assign connected sensors
2–5 seconds 2 blinks after 2 seconds set_offsets – Auto-calibrate all sensors (offsets saved automatically)
5–10 seconds 3 blinks after 5 seconds reset_sensors – Clear all sensor assignments and offsets
> 10 seconds No action Cancelled (button held too long)

To activate a function, release the button right after the desired blink pattern.
If held too long (>10s), no action will be taken.

ℹ️ For more advanced configuration and diagnostics, use the USB console as described in the next section.

Advanced configuration #

To modify the settings, you will need to connect the tMDS-16 to a computer via USB. The device can be powered through USB, so no additional power connections are necessary.

Required software #

To establish a connection with the pulse counter, you'll need an application that can handle serial communication. Popular choices include:

Example configuration process #

  1. List Available Ports:
    Run pyserial-ports to list the available COM ports before connecting the device. The output might look like:

    COM1
    COM3
    COM15
    COM16
    COM17
    

  2. Connect the Device
    Plug in the tMDS-16 and run the command again. The new COM port (e.g., COM11) will represent the connected device.

  3. Connect to tMDS-16
    Use the following connection parameters:

    • Baud rate: 115200
    • Byte size: 8 bits
    • Parity: None
    • Stop bits: 1

    Example command:

    pyserial-miniterm COM11 115200

  4. Send Commands
    Once connected, you can issue commands to the device. Each command must be followed by the Enter key. A useful command is ? or help, which provides a list of all available commands (described below).

Command list #

Command Description
address? | a? Get the Modbus address (Slave ID).
address=X | a=X Set the Modbus address. Parameters: X - address to set, number <1, 255>.
offsetX? | oX? Get the value of offset for DSX. Parameters: X - DS number <1, 16>.
offsetX=Y | oX=Y Set the offset value. Parameters: X - DS number <1, 16>; Y - value of offset, number <-32_768, 32_767>.
update_sensors | us Update list of sensors - detect and assign sensor/s.
set_offsets | so Automatically calculate offsets for all active sensors.
reset_sensors Reset sensors IDs and their offsets.
read_sensors | rs Read the sensors readings.
save_config | sc Save configuration to Flash memory. It should be called after changing the Modbus address and offsets.
read_registers | rr Read Modbus register values.
read_definitions | rd Read Modbus register definitions (name, address, type).
bootloader Reboot the device into bootloader mode to update the firmware. When connected to a computer, it will appear as a storage device named RPI-RP2, to which you should copy the firmware file with the extension uf2. Then the device will reboot into normal operation mode.
restart Restart the device.
verbose=X | v=X Turn on or off verbose mode, which displays more messages.
help | ? Display this help message with a list of available commands.

Common use cases #

Below are practical examples of how to configure and use the tMDS-16 via the console. These scenarios cover the most common integration and calibration tasks.

🔧 1. Changing the Modbus Slave address

When using multiple Modbus devices on the same bus, assign a unique address to each.

address=5

Then save the new setting:

save_config
🌡️ 2. Calibrating sensor readings manually

If you want to fine-tune readings from individual sensors, use per-sensor offsets.

Example: Set an offset of −0.5°C for sensor 3:

offset3=-0.5

Save the changes:

save_config
⚙️ 3. Auto-calibrating all sensors

To align readings across all connected DS18B20 sensors, use the following command:

set_offsets

This command calculates the average temperature across all active sensors and applies an offset to each one so that their readings match this average. It helps eliminate small factory variances and improves consistency when the sensors are later placed in different environments.

🛈 Before running this command, it is recommended to place all sensors in the same location with a stable temperature, wait a short while for them to equalize, and then execute the calibration.

This command automatically saves the calculated offsets to Flash memory.

🔍 4. Reading current sensor values

To display temperature readings directly in the console:

read_sensors
♻️ 5. Detecting connected sensors

After connecting new sensors or replacing old ones, update the device’s sensor list:

update_sensors

To reset all sensor IDs and their offsets:

reset_sensors
🧪 6. Inspecting Modbus registers

To check live register values exposed by the device:

read_registers

To list register names, types, and addresses:

read_definitions

Modbus registers #

Below is the list of Modbus registers available in the device.
The content corresponds to the output of the read_definitions command and includes register names, addresses, types, and divisors.

| Name                           | Addr | Type   | Func         | Divisor |
| ------------------------------ | ---- | ------ | ------------ | ------- |
| uptime                         | 3000 | uint32 | 0x04 (IREG)  |         |
| ds1                            | 3002 | int32  | 0x04 (IREG)  |      10 |
| ds2                            | 3004 | int32  | 0x04 (IREG)  |      10 |
| ds3                            | 3006 | int32  | 0x04 (IREG)  |      10 |
| ds4                            | 3008 | int32  | 0x04 (IREG)  |      10 |
| ds5                            | 3010 | int32  | 0x04 (IREG)  |      10 |
| ds6                            | 3012 | int32  | 0x04 (IREG)  |      10 |
| ds7                            | 3014 | int32  | 0x04 (IREG)  |      10 |
| ds8                            | 3016 | int32  | 0x04 (IREG)  |      10 |
| ds9                            | 3018 | int32  | 0x04 (IREG)  |      10 |
| ds10                           | 3020 | int32  | 0x04 (IREG)  |      10 |
| ds11                           | 3022 | int32  | 0x04 (IREG)  |      10 |
| ds12                           | 3024 | int32  | 0x04 (IREG)  |      10 |
| ds13                           | 3026 | int32  | 0x04 (IREG)  |      10 |
| ds14                           | 3028 | int32  | 0x04 (IREG)  |      10 |
| ds15                           | 3030 | int32  | 0x04 (IREG)  |      10 |
| ds16                           | 3032 | int32  | 0x04 (IREG)  |      10 |
| ds1_readErrors                 | 3034 | uint32 | 0x04 (IREG)  |         |
| ds2_readErrors                 | 3036 | uint32 | 0x04 (IREG)  |         |
| ds3_readErrors                 | 3038 | uint32 | 0x04 (IREG)  |         |
| ds4_readErrors                 | 3040 | uint32 | 0x04 (IREG)  |         |
| ds5_readErrors                 | 3042 | uint32 | 0x04 (IREG)  |         |
| ds6_readErrors                 | 3044 | uint32 | 0x04 (IREG)  |         |
| ds7_readErrors                 | 3046 | uint32 | 0x04 (IREG)  |         |
| ds8_readErrors                 | 3048 | uint32 | 0x04 (IREG)  |         |
| ds9_readErrors                 | 3050 | uint32 | 0x04 (IREG)  |         |
| ds10_readErrors                | 3052 | uint32 | 0x04 (IREG)  |         |
| ds11_readErrors                | 3054 | uint32 | 0x04 (IREG)  |         |
| ds12_readErrors                | 3056 | uint32 | 0x04 (IREG)  |         |
| ds13_readErrors                | 3058 | uint32 | 0x04 (IREG)  |         |
| ds14_readErrors                | 3060 | uint32 | 0x04 (IREG)  |         |
| ds15_readErrors                | 3062 | uint32 | 0x04 (IREG)  |         |
| ds16_readErrors                | 3064 | uint32 | 0x04 (IREG)  |         |
| version_major                  | 3100 | uint16 | 0x04 (IREG)  |         |
| version_minor                  | 3101 | uint16 | 0x04 (IREG)  |         |
| version_patch                  | 3102 | uint16 | 0x04 (IREG)  |         |
| address                        | 4000 | uint16 | 0x03 (HREG)  |         |
| offset1                        | 4001 | int32  | 0x03 (HREG)  |      10 |
| offset2                        | 4003 | int32  | 0x03 (HREG)  |      10 |
| offset3                        | 4005 | int32  | 0x03 (HREG)  |      10 |
| offset4                        | 4007 | int32  | 0x03 (HREG)  |      10 |
| offset5                        | 4009 | int32  | 0x03 (HREG)  |      10 |
| offset6                        | 4011 | int32  | 0x03 (HREG)  |      10 |
| offset7                        | 4013 | int32  | 0x03 (HREG)  |      10 |
| offset8                        | 4015 | int32  | 0x03 (HREG)  |      10 |
| offset9                        | 4017 | int32  | 0x03 (HREG)  |      10 |
| offset10                       | 4019 | int32  | 0x03 (HREG)  |      10 |
| offset11                       | 4021 | int32  | 0x03 (HREG)  |      10 |
| offset12                       | 4023 | int32  | 0x03 (HREG)  |      10 |
| offset13                       | 4025 | int32  | 0x03 (HREG)  |      10 |
| offset14                       | 4027 | int32  | 0x03 (HREG)  |      10 |
| offset15                       | 4029 | int32  | 0x03 (HREG)  |      10 |
| offset16                       | 4031 | int32  | 0x03 (HREG)  |      10 |