TapHome

TapHome to Modbus RTU / ASCII / TCP

TapHome as a Modbus slave for a BMS, SCADA or PLC over Modbus TCP, RTU or ASCII: pick the values and actions of each device, hand the register map to the master and read it there.

Last updated:

TapHome can act as a Modbus slave (server), so a building management system (BMS), SCADA or PLC reads and controls TapHome devices over Modbus TCP, or over Modbus RTU or ASCII on RS485. In Expose Devices you pick the values and actions of each device, TapHome gives each one its own register, and the result is a register map you hand to whoever sets up the master. A second Core can read the devices this way too (connecting multiple Cores).

How It Works

  1. On the Core, add a Modbus interface and pick what the master may see and do: values (temperature, blinds level, switch state …) and actions (blind commands, toggle …).
  2. The register map is built as you pick: every value gets a register range at the next free address, with a validity register right after it.
  3. On the master, the integrator enters the same registers as points or tags. Reading returns the current value; writing changes the device as a manual change, like in the app.

The Core keeps running its own modules and smart rules; Modbus only shares the values and actions you picked.

Example: Blinds and Thermostat for a BMS

The BMS of a building reads the room temperature, sets the thermostat set point and can stop the blinds. On the Core, in Expose Devices, select Add new interface → Modbus TCP, then add the blinds and the thermostat. This is the complete configuration on the TapHome side — click a register or Add device to see the next step:

Modbus TCP
Information
1
IP Address
Parameters
2
Devices
Blinds
3
Blinds level
Slats angle
4
Stop
Thermostat
Temperature
5
Set point
6
Add device
Set point
Register type
Register index
Data type
Data type
Back
Blinds
Values
Commands
OK
  1. IP address of the Core — the BMS connects to it. Give the Core a fixed address, for example a DHCP reservation in the router.
  2. Slave ID — the BMS must send the same Unit ID; a request with another one gets exception 0x0A.
  3. A value takes two registers: the value itself and the validity register after it. ×100 is the multiplier: blinds level 0.55 reads as 55.
  4. An action has one register the BMS writes to. Stop is a coil: writing ON stops the blinds.
  5. Register settings — register type, index, data type, multiplier, byte order and the validity register. The defaults usually fit; change the index when the BMS expects a fixed layout.
  6. Add device — check the values and actions of the device; OK adds them at the next free addresses. Writable values get a holding register (H) or coil (C), read-only values an input register (A) or discrete input (D).

On the BMS or SCADA Side

The master needs the IP address and port of the Core (or the RS485 line settings), the Slave ID and the point list. For the example above:

PointTapHome register1-based notationFunction codeValue
Blinds levelH 04000103 read, 06 write0–100
Blinds level validityH 140002030 = valid
Slats angleH 24000303 read, 06 write0–100
Slats angle validityH 340004030 = valid
Stop blindsC 40000505 writeON = stop
TemperatureA 53000604÷ 10 = °C
Temperature validityA 630007040 = valid
Set pointH 74000803 read, 06 write÷ 10 = °C
Set point validityH 840009030 = valid

All registers are Int16 (a coil is one bit). H = holding register, A = input register, C = coil.

TapHome numbers registers from 0, as they are sent in the request. Masters that use the 1-based notation (40001 for holding register 0) are one higher — an offset of one is the most common cause of reading the wrong value.

Reading and writing the example with pymodbus 3.16:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("192.168.1.50", port=502)
client.connect()

# Temperature: input register 5 (value × 10) and 6 (validity, 0 = valid)
rr = client.read_input_registers(5, count=2, device_id=1)
value, validity = client.convert_from_registers(rr.registers, client.DATATYPE.INT16)
if validity == 0:
    print(f"Temperature: {value / 10} °C")

# Set point 22.5 °C: holding register 7, written × 10
client.write_register(7, 225, device_id=1)

# Stop the blinds: write ON to coil 4
client.write_coil(4, True, device_id=1)
client.close()

Libraries for other languages: NModbus4 (C#), libmodbus (C), digitalpetri/modbus (Java), goburrow/modbus (Go). Simply Modbus explains the protocol.

The master should read the validity register together with the value (one request for both) and treat any code other than 0 as a fault — a lost device then shows as a fault in the BMS, not as a frozen value.

Interfaces and Communication Properties

InterfacePropertiesDefault
Modbus TCPTCP port, Slave IDport 502, the next free Slave ID on the port
Modbus RTU, Modbus ASCIIterminal BUS 1, BUS 2 or BUS 3, baud rate, data bits, parity, stop bits, Slave IDno terminal, 9600 baud, 8 data bits (ASCII 7), parity None, 1 stop bit, Slave ID 1
  • Several masters over TCP: several Modbus TCP interfaces can share one port if they have different Slave IDs — for example one for the BMS and one for a second Core, each with its own registers. Up to 10 connections at a time.
  • RS485: an RTU or ASCII interface starts only after you pick a terminal, and one terminal carries one interface.
  • Slave ID is 1 to 247. The interface answers only its own: Modbus TCP returns exception 0x0A to another Unit ID (also 0 or 255), RTU and ASCII do not answer frames for another address.

The list of interfaces shows conflicts, such as the same port and Slave ID twice or an interface without a terminal.

Register Settings

SettingValues
Register typeH holding register, A input register, C coil, D discrete input. The master can write only H and C.
Register index0 to 65535, the address in the Modbus request.
Data typeBool, Int16 and UInt16 take one register, Int32, UInt32 and Float two. C and D carry only Bool.
MultiplierRead: register = value × multiplier. Write: value = register ÷ multiplier. Temperatures default to ×10, levels and percentages to ×100 (0.55 → 55).
ThresholdInstead of the multiplier for Bool on a numeric value: true when the value is at least the threshold; writing true to a coil sets the value to the threshold, false to 0.
EndiannessBigEndian or LittleEndian; Byte swap also swaps the bytes inside each register of Int32, UInt32 and Float.

The interface detail marks registers whose ranges overlap. An overlapping value is not served — give each value its own range, including its validity register.

Validity Register and Fallback Value

Include validity register (on C and D Include validity bit) is on by default. The read-only register right after the value tells the master whether the value is valid; when it is not, the value’s registers return the Fallback value instead (for Float, NaN with Use NaN as fallback).

CodeMeaning
0valid
1the value does not fit the data type (for example NaN in an integer type)
2the device is unavailable or in error
3the register settings are invalid
5no value yet, for example right after the Core starts

On C and D, the validity bit is 1 when the value is not valid.

Actions

  • Action without parameters (Stop, Toggle …) has one register on H or C. Writing 1 or more, or coil ON, triggers it; writing 0 returns exception 0x03.
  • Action with parameters (adding to the blinds level, setting a range …) has one holding register per parameter, one after another. One write must cover all of them; a write of only some returns exception 0x03.
  • Action registers are write-only — reading them returns exception 0x02.

What the Master Gets

TapHome serves function codes 01, 02, 03, 04, 05, 06, 15 and 16. One request may read several values in a row; otherwise the master gets an exception:

ExceptionWhen
0x01an unsupported function code
0x02an address not assigned to any value, a range with a gap, a write to A or D, a read of an action register, a write to a validity register
0x03a value the device cannot take, or an invalid action write; a write with several values is all or nothing
0x04the device refused the write
0x0AModbus TCP: another Slave ID (Unit ID) than the interface’s

Modbus TCP closes a connection idle for 15 minutes.

Updating to 2026.1

When the control unit is updated to 2026.1, existing Modbus slave interfaces are converted automatically, keeping the register addresses and Slave ID. The Stop register of a KNX blind, of a Packet parser blind and Awning device, and of a virtual Awning device is not converted; a master that used it gets exception 0x02 at its address (release notes 2026.1).

Since 2026.1, Modbus TCP answers only its own Slave ID — if your master sends Unit ID 0 or 255, set it to the interface’s Slave ID.

Debugging Modbus RTU Connections

RS485 is a shared bus, so a PC with a USB to RS485 converter (for example FTDI USB-RS485-WE-1800-BT) and terminal software, such as Terminal for Windows, can listen to the communication while you integrate.