1

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 Answers2

1

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
  • hello Thomas. Maybe you should share your minimalmodbus code? It would help if you can add the complete output of your code. – Marcos G. Feb 09 '22 at 07:51
  • Thanks Marcos, I've updated it. I was in doubt that it was read-only functionality, but I know there this extra device you can connect to the X-245 (i.e. R-208) with which you can read/write as well. So it has to support it somehow. – Thomas Buyukkilic Feb 09 '22 at 08:10
  • Hello Thomas, thanks, that helps, now I understand what you mean. Another question: can you explain the TTL to RS485 conversion? How are you doing the direction control? and what about the gpio pin? what are you using it for? – Marcos G. Feb 10 '22 at 06:42
  • Hi Marco, I think the problem is indeed the direction control. I have no prior experience with serial connections and I was not aware that RX/TX should be enabled differently. I will do some tests and report back! – Thomas Buyukkilic Feb 12 '22 at 14:19
  • OK, good luck. There are many useful Q&As but I would start [here](https://stackoverflow.com/questions/56922031/modbus-error-invalid-message-incomplete-message-received-expected-at-least-2/56923891#56923891). – Marcos G. Feb 12 '22 at 14:54
  • Awesome Marcos, that's a really detailed explanation, thanks! The MAX485 kind of works when I enabled RX/TX at the same time, I can now start a read thread which displays the serial data sent/read and a write thread from which I can send commands. I try to send a standard Modbus command to read a register, I see it in the read thread, but there is no specific response. What might be the problem? – Thomas Buyukkilic Feb 12 '22 at 21:28
  • I don't think you can enable RX and TX at the same time. Well, you can, but you will only be able to send the query and not to get the response from the slave. Before the slave can send its response it needs to be able to take control of the bus, and to do that the master needs to release it. If you operate manually as you are doing, you need to release the bus manually on the master by disabling TX. I don't think you'll be able to do that fast enough to avoid missing parts of the response... You need to follow the advice on the link, either go for a hardware direction control or software. – Marcos G. Feb 12 '22 at 21:45
  • I now hooked up one that is supposed to have automatic flow control (XY-017), but it doesn't seem to work nicely. Tried it with both 3,3 and 5 volts. TX/RX to TX/RX of RPi and A,B,GND to the corresponding connectors. However all I see are zero pairs (4096 of them to be exact), but sometime there are other characters in it. I've set the baudrate to 19200 (default for the device I'm trying to read). Is my chip converter broken? – Thomas Buyukkilic Feb 14 '22 at 13:53
  • you seem to be dealing with a strange and poorly documented device. This is my first experience with these devices. According to [this document](https://www.uponor.com/getmedia/3cd8ee9b-bcc0-4179-bfa0-bab5326c4de9/Uponor-TI-Smatrix-EN-1116265-v3-202002?sitename=UponorInternational&disposition=attachment) there seems to be a Modbus RTU link working over RS-232. That is very strange indeed, but maybe you need a TTL-to-RS-232 converter instead of RS-485? – Marcos G. Feb 14 '22 at 15:34
  • So I reverted back to my old setup where I manually enable TI/DI. The thing is that when I write: 110c0566ffa0b2, I get the following reply: 110c0566ffa0b2110c05664002703e00003f0c0242b3002295 The command is the same one that I see when I disconnect the thermostats. – Thomas Buyukkilic Feb 14 '22 at 19:46
  • Hello, I'm not sure what you have is Modbus. I think you should try to sniff traffic on the serial port, maybe try something like [this](https://stackoverflow.com/questions/62311034/passive-monitoring-serial-port-in-windows-using-c/62328093#62328093). – Marcos G. Feb 15 '22 at 15:49
  • It's not Modbus, even though it uses the Modbus CRC-16. I have figured it out I think, I will post back results – Thomas Buyukkilic Feb 19 '22 at 17:58
0

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?