Serial communication with Uponor Smatrix Base X-245

521 views Asked by At

I'm trying to communicate with a Uponor Smatrix Base X-245 using a use a Raspberry Pi 3B+ over the serial port (UART), without much luck.

Here is a sample output from the UART (/dev/ttyS0) using pyserial:

import serial
ser = serial.Serial("/dev/ttyS0", baudrate=19200)
while True:
    received_data = ser.read()
    sleep(0.025)
    data_left = ser.inWaiting()
    received_data += ser.read(data_left)
    print (received_data.hex()) 

Data without any thermostats (8) connected:

00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
dd  e7  c9  6b  01  0a  2e  fe
dd  e7  af  83  01  56  89  ff
dd  e7  f5  33  01  bf  36  fe
dd  e7  f7  6d  01  ce  f6  fe
dd  e7  f7  2b  01  aa  36  fe
dd  e7  f7  75  01  da  f6  fe
dd  e7  f7  49  01  fe  76  ff
dd  e7  f5  45  01  99  f6  fe

Data with all thermostats (8) connected:

00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
00  00  00  00  00  00  00  00  00  00  00  00  00  00  c0
dd  e7  c9  6b  01  0a  2e  fe  77  5e  6e  e3  ed  db  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  3d  7b  99  ff  75  53  fe
dd  e7  c9  6b  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  3d  87  ff  6f  95  ff  ff  8d  2f  ff  
dd  e7  af  83  01  56  89  ff  77  de  6d  e4  ed  cb  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  85  7b  99  ff  0b  07  ff
dd  e7  af  83  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  85  87  ff  6f  95  ff  ff  25  3b  fe  
dd  e7  f5  33  01  bf  36  fe  77  5e  df  d3  ed  eb  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  85  7b  99  ff  71  0f  ff
dd  e7  f5  33  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  85  87  ff  6f  95  ff  ff  d3  fb  ff  
dd  e7  f7  6d  01  ce  f6  fe  f7  fe  fc  fe                                                                              
dd  e7  f7  6d  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  79  87  ff  6f  95  ff  ff  15  d3  fe  
dd  e7  f7  2b  01  aa  36  fe                                                                                              
dd  e7  f7  2b  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  67  87  ff  6f  95  ff  ff  43  91  ff  
dd  e7  f7  75  01  da  f6  fe  77  de  5f  c7  ed  97  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  07  7b  99  ff  cf  8f  fe
dd  e7  f7  75  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  07  87  ff  6f  95  ff  ff  77  9b  ff  
dd  e7  f7  49  01  fe  76  ff  77  de  5f  e2  ed  7b  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  79  7b  99  ff  95  ff  ff
dd  e7  f7  49  a5  01  01  85  ff  7f  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  79  87  ff  6f  95  ff  ff  c1  3   ff  
dd  e7  f5  45  01  99  f6  fe  77  5e  5f  d4  ed  bb  83  ff  ff  81  e7  fb  87  ff  6f  89  fb  3d  7b  99  ff  bb  d1  ff
dd  e7  f5  45  a5  01  01  85  ff  ff  e7  ff  b7  91  fd  cb  8f  f9  93  89  fb  3d  87  ff  6f  95  ff  ff  bf  0d  ff  

Included here the same data, but color coded to recognize patterns.

Also the raw bytes that were captured:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 
dd e7 c9 k  01 \n .  fe w  ^  n  e3 ed f7 83 ff ff 81 e7 fb {  99 ff -  1f fe 
dd e7 c9 k  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb =  87 ff o  95 ff ff 8d /  ff 

dd e7 af 83 01 V  89 ff w  de m  e4 ed cb 83 ff ff 81 e7 fb {  99 ff e9 d1 ff 
dd e7 af 83 a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb 85 87 ff o  95 ff ff %  ;  fe 

dd e7 f5 3  01 bf 6  fe w  ^  df d3 ed e5 83 ff ff 81 e7 fb {  99 ff 93 a7 fe 
dd e7 f5 3  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb 85 87 ff o  95 ff ff d3 fb ff 

dd e7 f7 m  01 ce f6 fe ff ff ff ff `  ff ff 
dd e7 f7 m  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb y  87 ff o  95 ff ff 15 d3 fe 

dd e7 f7 +  01 aa 6  fe 
dd e7 f7 +  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb w  87 ff o  95 ff ff Q  11 ff 

dd e7 f7 u  01 da f6 fe w  de _  c7 ed ?  87 ff ff 81 e7 fb {  99 ff bd i  ff 
dd e7 f7 u  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb 07 87 ff o  95 ff ff w  9b ff 

dd e7 f7 I  01 fe v  ff w  de _  e2 ed u  83 ff ff 81 e7 fb {  99 ff cf a1 ff 
dd e7 f7 I  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb y  87 ff o  95 ff ff eb 9d ff 

dd e7 f5 E  01 99 f6 fe w  ^  _  d4 ed a7 83 ff ff 81 e7 fb {  99 ff 85 0f ff 
dd e7 f5 E  a5 01 01 85 ff ff e7 ff b7 91 fd cb 8f f9 93 89 fb =  87 ff o  95 ff ff bf \r ff

Base station

Uponor Smatrix Base X-245. Some sites claim it communicates over RS485. Do not confuse with the PRO version that supports Modbus.

Thermostat

The thermostats are Uponor Smatrix Base T-146 Bus, which have a Renesas RL78/L13 chipset which is used for serial communication (I think). From the datasheet:

[80-pin]
• CSI: 1 channel/UART (UART supporting LIN-bus): 1 channel/simplified I2C: 1 channel
• CSI: 1 channel/UART: 1 channel/simplified I2C: 1 channel
• UART: 2 channels

Setup

Raspberry Pi 3B+         X-245        T-146
GPIO 14 (UART TX)  <-->    A    <-->    A
GPIO 15 (UART RX)  <-->    B    <-->    B
                           +    <-->    +
                           -    <-->    -

Who recognizes a protocol or can help me decode it? I've tried:

  • RS485/Modbus: Can't get a response, probably there is no modbus master/slave
  • LIN-bus: Can't see the 0x55 byte anywhere
  • I2C: i2cdetect -y 1 doesn't return an address
2

There are 2 answers

12
Thomas Buyukkilic On

So it turns out that I was missing a TTL > RS485 converter, so I added that and now I can clearly read the data, and it turns out to be Modbus after all. This article helped me to setup a stable serial reader and this one helped me to get to the Modbus result. This is the output now:

110c28beff6abb
110c28be4002753e00003f0c0242b3003f71
110c28be2d7fff3d00000c002437019a3803b63b023c3c0048358000fd22
110c0566ffa0b2
110c05664002623e00003f0c0242b30082ca
110c05662d7fff3d00000c002437019a3803b63b024e3c004835800054c5
110c04c9ff8c82
110c04c94002c53e00003f0c0242b300a1fd
110c04c92d7fff3d00000c002437019a3803b63b02c43c00483580008356
110c04eaff95b2
110c04ea4002c43e00003f0c0242b3008a9b
110c04ea2d7fff3d00000c002437019a3803b63b02c43c0048358000d777
110c04c5ff8982
110c04c54002a03e00003f0c0242b300a39f
110c04c52d7fff3d00000c002437019a3803b63b027c3c00483580004432
110c04dbff8022
110c04db4002c73e00003f0c0242b30039e5
110c04db2d7fff3d00000c002437019a3803b63b02c33c00483580000ab1
110c055dffb382
110c055d4002843e00003f0c0242b3003df8
110c055d2d7fff3d00000c002437019a3803b63b027c3c0048358000edf8

Now that I have a readable result, I do have another problem: The serial bus keeps sending the event log for all thermostats i.e. function code 12 (0x0C) Get Comm Event Log. I have no idea how to get rid of this as all the responses start with \x11\x0c minimalmobus can't match a request to the right slave (\x11 will be slave number 17, while I request data from slave number 1). What to do here?

minimalmodbus config

import minimalmodbus
from gpiozero import OutputDevice
de = OutputDevice(23)
re = OutputDevice(24)
de.off()
re.off()
instrument = minimalmodbus.Instrument('/dev/serial0', 1, minimalmodbus.MODE_RTU)
instrument.serial.timeout = None
instrument.debug = True

read_register output

>>> instrument.read_register(40006,0,3,True)
MinimalModbus debug mode. Will write to instrument (expecting 7 bytes back): 01 03 9C 46 00 01 4B 8F (8 bytes)
MinimalModbus debug mode. Clearing serial buffers for port /dev/serial0
MinimalModbus debug mode. No sleep required before write. Time since previous read: 2965.81 ms, minimum silent period: 2.01 ms.
MinimalModbus debug mode. Response from instrument: 11 0C 1B CA FF BD B4 (7 bytes), roundtrip time: 7.0 ms. Timeout for reading: 0.0 ms.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/dist-packages/minimalmodbus.py", line 486, in read_register
    payloadformat=_Payloadformat.REGISTER,
  File "/usr/local/lib/python3.7/dist-packages/minimalmodbus.py", line 1245, in _generic_command
    payload_from_slave = self._perform_command(functioncode, payload_to_slave)
  File "/usr/local/lib/python3.7/dist-packages/minimalmodbus.py", line 1330, in _perform_command
    response, self.address, self.mode, functioncode
  File "/usr/local/lib/python3.7/dist-packages/minimalmodbus.py", line 1875, in _extract_payload
    responseaddress, slaveaddress, response
minimalmodbus.InvalidResponseError: Wrong return slave address: 17 instead of 1. The response is: '\x11\x0c\x1bÊÿ½´'

Do you guys know what to do? Is it possible that the Uponor Smatrix Base X-245 only has a subset of the Modbus functions implemented? If so, what would my best shot be to write something back?

I tried to capture a possible request to change the temperature by adjusting it on the thermostat, but all I can see is the log where I went from 21,5°C (707°K, \x02\xc4) to 25,0°C (770°K, \x03\x02)

110c04eaff95b2
110c04ea4002c43e00003f0c02 42b3008a9b
110c04ea2d7fff3d00000c002437019a3803b63b02c43c0048358000d777

110c04eaff95b2
110c04ea4002c43e00003f0c02 3c00483b030242b3004466
110c04ea2d7fff3d00000c002437019a3803b63b03023c0048358000b0b7

110c04eaff95b2
110c04ea4002c43e00003f0c023c00483b030242b3004466
110c04ea2d7fff3d00400c002437019a3803b63b03023c0048358000a578

110c04eaff95b2
110c04ea4002c43e00003f0c0242b3008a9b
110c04ea2d7fff3d00400c002437019a3803b63b03023c0048358000a578
0
Thomas Buyukkilic On

So like Marcos suggested, the problem with sending was me not realising that for RX the RE should have been low and for TX the DE should have been high. Now I can read/write at the same time using this small class:

import time
import serial
import serial.rs485
import RPi.GPIO as GPIO
from crc import crc16

TX_DE = 11
RX_RE = 13

baud = 19200
T1_5 = 16500000 / baud / 1000
T3_5 = 38500000 / baud / 1000

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(TX_DE, GPIO.OUT, initial=GPIO.HIGH)  # HIGH enables TX
GPIO.setup(RX_RE, GPIO.OUT, initial=GPIO.LOW)  # LOW enables RX

ser = serial.rs485.RS485(port="/dev/serial0", baudrate=baud, timeout=T3_5)
ser.rs485_mode = serial.rs485.RS485Settings(
    rts_level_for_tx=False,
    rts_level_for_rx=False,
    loopback=True,
    delay_before_tx=T3_5,
    delay_before_rx=T3_5,
)

def write(command, direct=False):
    command_bytes = bytes.fromhex(command)
    if not direct:
        crc = crc16(command_bytes.decode("latin-1"))
        ser.write(command_bytes + bytes.fromhex(crc[-2:]) + bytes.fromhex(crc[:2]))
    else:
        ser.write(command_bytes)


def read():
    while True:
        x = ser.read(4095)
        print(x.hex())

How ever now I can see the commands on the serial bus e.g. write(b'\x01\x04\x00\x01\x00\x0c\xA1\xCF'), but nothing happens (i.e. no Modbus response), what am I missing?