Airlab in HomeAssistant using modbus integration

Has anybody succesfully integrated the Airlab in HomeAssistant using the modbus integration? Modbus integration is based on pymodbus, so any help integrating Airlab using pymodbus is also welcome.

Below is the configuration I tried, but I get an error back from pymodbus. I know for sure the IP address is correct and T3000 can connect with the Airlab over IP.

modbus:
  - name: Airlab
    type: tcp
    host: 192.168.1.61
    port: 502
    timeout: 10
    sensors:
      - name: CO2 level
        address: 139
        unit_of_measurement: ppm
        data_type: uint16

The error I get:

Pymodbus: Airlab: Modbus Error: [Input/Output] Modbus Error: [Invalid Message] No response received, expected at least 8 bytes (0 received)

I would set up a PC in the middle and log the data to see what’s going on. Wireshark is good for Ethernet based debugging. MBPoll or similar is good for modbus RTU polling.

T3000 has a built in rudimentary MBPoll, access that from T3000 → tools → Modbus Poll.

Yes, but for a start it would help to know the very least like: does the Airlab speak modbus over tcp, udp or rtuovertcp? It’s details like this that help a lot diagnosing the problems.

Ok, so from a wireshark capture it turns out I need to do modbus over tcp (502).

My pymodbus test script:

#!/usr/bin/env python
from pymodbus.client import ModbusTcpClient
import logging

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

client = ModbusTcpClient("192.168.1.61")
client.connect()
client.read_holding_registers(1, 10)
client.close()

The output:

DEBUG:pymodbus.logging:Connection to Modbus server established. Socket ('192.168.1.111', 43052)
DEBUG:pymodbus.logging:Current transaction state - IDLE
DEBUG:pymodbus.logging:Running transaction 1
DEBUG:pymodbus.logging:SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0x0 0x3 0x0 0x1 0x0 0xa
DEBUG:pymodbus.logging:New Transaction state "SENDING"
DEBUG:pymodbus.logging:Changing transaction state from "SENDING" to "WAITING FOR REPLY"
DEBUG:pymodbus.logging:Transaction failed. (Modbus Error: [Invalid Message] No response received, expected at least 8 bytes (0 received)) 
DEBUG:pymodbus.logging:Processing: 
DEBUG:pymodbus.logging:Getting transaction 1
DEBUG:pymodbus.logging:Changing transaction state from "PROCESSING REPLY" to "TRANSACTION_COMPLETE"

There is a modbus TCP packet sent to the Airlab:

TCP payload (12 bytes)  00 01 00 00 00 06 00 03 00 01 00 0a
.000 0011 = Function Code: Read Holding Registers (3)
Reference Number: 1
Word Count: 10

But except for a FIN/ACK no repsonse is received from the Airlab.

When I look at a capture from T3000.exe, there seems to be a bare (non modbus, according to Wireshark) TCP packet:

Data (12 bytes): 01020304050601032cd00064

That does get a modbus response:

.000 0011 = Function Code: Read Holding Registers (3)
Byte Count: 200
Register 0 (UINT16): 17748
...

So, what’s wrong with the “genuine” modbus request (as recognised by wireshark) that pymodus sends?

Hmm… Transaction identifier 0102, Protocol identifier 0304 and length 0506 looks a bit suspicious to be very honest. Are you sure T3000 and the Airlab speak proper modbus?

I have asked Fandu to chime in on this.

The airlab is a mature product integrated into many environments over the years… since before the pandemic in fact. It uses plain old modbus RTU on the '485 port and regular Modbus TCP on the Ethernet and WiFi ports, this is well documented in the Modbus protocol.

Maurice

Yes, and the documention clearly states that protocol identifier should be 0 for modbus (Modbus - Wikipedia) which the T3000 packet clearly doesn’t honour (it sends 0304)? Is there anything else that needs to be diverted from the standard for the Airlab to answer modbus over TCP requests?

Before diving in with a Python script it would be best to get some packets moving using a generic Modbus tool like MBPoll or our built in T3000 → tools → register viewer. There you can see all the registers, the description and the current value. You can jump back and forth from the graphical T3000 UI, change parameters there, then jump back to the register viewer to see the changes. Once you have all that working you can graduate on up to the Python scripts.

Fandu can comment better than I on the ‘protocol identifier’, we wouldn’t change it just to be different.

You are reading a device with a modbus ID of 0, and 0 is not a valid ID, I believe you can read the data if you change to normal modbus ID from 1 - 254.
The top half of the image below is my test using Modbus ID 1.

@Fan_Du Yes, you’re right, I initially used the wrong unit. I corrected that in my code and now I’m stupified. I expiremented with mbpoll and that works (Yes!):

$ mbpoll 192.168.1.61 -0 -r 139 -1
mbpoll 1.0-0 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2019 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus TCP
Slave configuration...: address = [1]
                        start reference = 139, count = 1
Communication.........: 192.168.1.61, port 502, t/o 1.00 s, poll rate 1000 ms
Data type.............: 16-bit register, output (holding) register table

-- Polling slave 1...
[139]:  586

The corresponding Wireshark trace looks like this in green rectangle:


The red rectangle is the pymodbus request, which doesn’t get a reply from Airlab?

The modbus packet from mbpoll:
wireshark_02

And this is the modbus packet from pymodbus:
wireshark_03

I don’t see a difference, but nevertheless the Airlab doesn’t reply? What’s happening here?

You might need to check the arguments for pymodbus, especially timeout.

Timeout is set to 1s, just like mbpoll?

#!/usr/bin/env python
from pymodbus.client import ModbusTcpClient
import logging

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

client = ModbusTcpClient("192.168.1.61", timeout=1)
client.connect()
client.read_holding_registers(139, 1, slave=1)
client.close()

Here is an example in python for reading the modbus registers of the Airlab. I hope that can help you
image
TemcoModbusMonitor.py (4.7 KB)

Thank you, but the goal is to get Airlab values in HomeAssistant and the HA integration is based on pymodbus, so I’m looking for a solution in pymodbus… I’ll investigate further and report back when I found the problem.

We’re standing by to help '8098

Pfff… fixed it. It turns out the Airlab needs some rest between connect and read.

0.01s seems to be the mininum. As far as I can tell, this can’t be configured in pymodbus, so it’ll probably need a PR on the HA integration I guess…

#!/usr/bin/env python
from pymodbus.client import ModbusTcpClient
import time
import logging

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

client = ModbusTcpClient("192.168.1.61", timeout=1)
client.connect()
time.sleep(0.01)
client.read_holding_registers(139, 1, slave=1)
client.close()
DEBUG:pymodbus.logging:Connection to Modbus server established. Socket ('192.168.1.111', 56416)
DEBUG:pymodbus.logging:Current transaction state - IDLE
DEBUG:pymodbus.logging:Running transaction 1
DEBUG:pymodbus.logging:SEND: 0x0 0x1 0x0 0x0 0x0 0x6 0x1 0x3 0x0 0x8b 0x0 0x1
DEBUG:pymodbus.logging:New Transaction state "SENDING"
DEBUG:pymodbus.logging:Changing transaction state from "SENDING" to "WAITING FOR REPLY"
DEBUG:pymodbus.logging:Changing transaction state from "WAITING FOR REPLY" to "PROCESSING REPLY"
DEBUG:pymodbus.logging:RECV: 0x0 0x1 0x0 0x0 0x0 0x5 0x1 0x3 0x2 0x2 0x6e
DEBUG:pymodbus.logging:Processing: 0x0 0x1 0x0 0x0 0x0 0x5 0x1 0x3 0x2 0x2 0x6e
DEBUG:pymodbus.logging:Factory Response[ReadHoldingRegistersResponse': 3]
DEBUG:pymodbus.logging:Adding transaction 1
DEBUG:pymodbus.logging:Getting transaction 1
DEBUG:pymodbus.logging:Changing transaction state from "PROCESSING REPLY" to "TRANSACTION_COMPLETE"

HomeAssistant :hugs:

For future reference and others finding this topic:
It turned out the be the slave after all.
Learned a lot these past days.

modbus:
  - name: Airlab
    type: tcp
    host: 192.168.1.61
    port: 502
    timeout: 1
    sensors:
      - name: CO2 level
        slave: 1
        address: 139
        unit_of_measurement: ppm
        input_type: holding
        data_type: uint16
        scan_interval: 15

And it turns out it is timing critical, because on restart it fails to read the registers. So I do need to insert a small timeout after client.connect() after all:

Glad you figured it out. If we can add some features to make your integrating go better you can let us know. For RTU we have a delay parameter, if you need one for IP polling we can work on it. I am not clear what you mean with “It turned out the be the slave after all” but we’re listening and anxious to make HA integrating go smoothly.

It turned I was looking at two problems (and one remaining). I misconfigured HA by omitting slave:1, but now even with delay: 1 I still see failing responses in the HA logs, so apparently the integration doesn’t insert the delay where it’s needed to be fail-safe?

The Airlab generally replies pretty quickly, a few milliseconds under RS485/RTU which is why we needed to introduce the delay parameter for the RS485 communications. Wireshark could tell you more about the timing and possibly we need to introduce a delay with IP as well for HA.