Modbus causes (semi) brick and writes multiple registers

Hello OH Community

Currently I am trying to properly implement my (Sugar Valley) Oxilife pool control system into OH. The registers seem to be correct which can be found here Modbus Register Description.

Right after the start I noticed a problem with writing. This means I cannot properly write a 0 into the respective registers. Just nothing happens. Over at HASS people mentioned that in their case they have to use [0] to properly write the value. Therefore I guess something is wrong with here, too.

Furthermore it looks like the Modbus Binding writes into multiple registers in one use case for whatever reason, causing a brick to my device. But only if I try to write a 3 into the respective register.

Maybe it is the writeValueType?

I prepared some logs for you guys. Unfortunately I only used debug mode, thought I enabled trace but maybe this is enough?

  • 01-04: show the usage of writing with python in debug mode
  • 05-08: show the usage of openhab writing with python using exec binding
  • 09-12: directly using openhab modbus binding to write to the registers
    • 09 shows when the brick occurs. It should write 3 to register 1041 but instead writes whatever values into 1041, 1042, 1043
    • 11 should write 0 into 1041 but instead does nothing

If you need more logs / info, please tell me and I will try to provide it.

Thanks in advance for your help, I really appreciate it!
Sefer

Info
openhab

openhab> bundle:list -s | grep modbus
208 │ Active │  80 │ 2.5.8                   │ org.openhab.binding.modbus
209 │ Active │  80 │ 2.5.8                   │ org.openhab.binding.modbus.sunspec
219 │ Active │  80 │ 2.5.8                   │ org.openhab.io.transport.modbus

Hardware

Raspberry Pi 4 with 4GB
Waveshare RS485 Can Hat, A/B Connected

These are my things for 09-12:

Bridge modbus:serial:oxilifepool [port="/dev/ttyS0", baud=19200, dataBits=8, id=1, parity="none", stopBits="1.0", encoding="rtu" ] {
   Bridge poller register1041 [ start=1041, length=1, refresh=30000, type="holding", maxTries=10 ] {
      Thing data FiltrationMode [ readStart="1041", readValueType="int16", writeStart="1041", writeValueType="int16", writeType="holding", writeMaxTries=10 ]
   }
   Bridge poller register1043 [ start=1043, length=2, refresh=30000, type="holding", maxTries=10] {
      Thing data FiltrationManualState [readStart="1043", readValueType="int16", writeStart="1043", writeValueType="int16", writeType="holding", writeMaxTries=10 ]
   }
}

This is the python script for 05-08:

#!/usr/bin/env python

#Dieses Skript muss mit zwei Inputparametern angesprochen werden
#Parameter 1: Registernummer, Parameter 2: Zu schreibender Wert
# 1041: Pumpenmodus
# 1043: Pumpenstatus im manuellen Modus

import sys
import minimalmodbus
import csv

register=int(sys.argv[1])
value=int(sys.argv[2])

#Skript eventuell nicht ausfuehren
if value not in (0, 1, 3):
        sys.exit()
if register not in (1041, 1043):
        sys.exit()

instrument = minimalmodbus.Instrument('/dev/ttyS0',1)

while True:
    try:
        instrument.write_register(register,value,0)
        break
    except:
        pass

This is the python script for 01-04:

#!/usr/bin/env python
import minimalmodbus
import time

instrument = minimalmodbus.Instrument('/dev/ttyS0',1, debug = True)

pump_manual_state = instrument.read_register(1041,0)
print("Pump is in mode: " + str(pump_manual_state))
time.sleep(1.5)
instrument.write_register(1041,3,0)
time.sleep(1.5)
print("Pump mode changed")
pump_manual_state1 = instrument.read_register(1041,0)
print("New pump mode is: " + str(pump_manual_state1))

01-python_1_to_3.log (1.6 KB) 02-python_3_to_1.log (1.6 KB) 03-python_1_to_0.log (1.6 KB) 04-python_0_to_1.log (1.6 KB) 05-openhab_1_to_3_pythonexec.log (5.4 KB) 06-openhab_3_to_1_pythonexec.log (4.5 KB) 07-openhab_1_to_0_pythonexec.log (5.4 KB) 08-openhab_0_to_1_pythonexec.log (5.4 KB) 09-openhab_1_to_3_brick.log (6.6 KB) 10-openhab_3_to_1.log (6.6 KB) 11-openhab_1_to_0_wont_work.log (3.9 KB) 12-openhab_0_to_1.log (3.8 KB)

It’s right there in your log, showing the Modbus command sent. Which was to write to a single 16-bit register.
If other registers contents are also affected by that operation it is something the target slave has done.
I think if people are also struggling with this device from other host/script environments, it might be a clue.

Can you work out if your device needs some particular sequence of instructions or such like?

But why does this only happen with OH? Writing 0 doesn’t work either as stated above. I think this is weird. It works fine using python with the minimalmodbus binding or mbpoll.

Unfortunately I only have the device register description linked above (which doesn’t have changed as it seems).

The only way I can think of to find out more would be to get the wifi module and probably sniff what the external service does (non encrypted http traffic, also a Modbus device). But the module is 400-600€ if I remember correctly. I was hoping it’s only an implementation issue.

Do you have any other idea?

Sure. Your python script uses Modbus FC16, write multiple registers instruction. (With a count of 1)

You’ve configured your openHAB Modbus write to use FC06, write single register.

It’s not uncommon for devices to only work with one or the other, which is why this is configurable in openHAB.
It would be a badly behaved device that accepts an instruction it can’t handle, but far from impossible.

Try changing your data Thing write multi/single parameter to match what your test script does.

Looking more closely at your binding log, the device’s response to the FC06 write [ is nonsense ]- EDIT no forget that, it indicates success

Well. Right. I studied the minimalmodbus api but somehow I missed that completely. This could explain something. But still weird I can go 0 -> 1, 1 -> 2 but not 1 -> 3 or 2 -> 3 or * to 0…

I will try it tomorrow (it’s raining and I can’t risk a brick now) with writeMultipleEvenWithSingleRegisterOrCoil=true to see whether this “fixes” the problem. Can you explain why I would need this instruction to write into a single register?

The response could be true. I mean it was a success more or less. It wrote 3 into register 1041, but also 39166 into 1042 and 289 into 1043. I don’t understand where this is coming from.

Nope. If that’s what the device requires, that’s what the device requires and I have no idea why designers might choose to do it that way. There’s no reason not to write a single with multiple.

As I said, it’s not that uncommon as a general restriction. Very few devices implement a full Modbus command set, they are not obliged to. And there is no way to find out unless the designer tells you.

What is more unusual here is accepting a Modbus instruction but apparently doing the wrong thing with it. However I begin to wonder if you are just misinterpreting your device …

I have no idea what goes on in inside your device.

I find reading the NeoPool doc referenced a bit difficult, with the language changes.

The register you are writing, 1041 (x401), appears to be something about polarization reversing time. It’s a parameter that gets set by an installing engineer, and at some time saved to EEPROM It’s not clear if reading the register gives the temporary “live” value or the stored EEPROM value

Registers .x402 and x403 are also closely related to this function. It doesn’t altogether surprise me that if you write one parameter, it might affect others. e.g. if you set reversing “on” time to zero that might reasonably set some “active” bit another register to off.and/or set "off’ time to some other value. All depends on your device.

One of your scripts mentions “pump”. Are you poking at the correct registers?

Related comment, beware the off-by-one hazard of identifying Modbus registers. Documentation reference registers using either register number (starts at one) or address (starts at zero). I mention that because I’m not sure why you would read/write x401 but not x400

I find it difficult to quote here (first time ever, where is the quote button), hope you don’t mind

Adress 1041 (dec) references to 0x0411 MBF_PAR_FILT_MODE, therefore 1042 to 0x0412 MBF_PAR_FILT_GPIO (which causes the brick, unassigning the pump in 0x010E MBF_RELAY_STATE and I have to enter the service menu via pin found in the internet… or write to that register probably) and 1043 to 0x0413 MBF_PAR_FILT_MANUAL_STATE (can be changed even if not in manual mode but the value is invalid).

Pretty sure the registers are correct. Confirmed reading them via mbpoll (using -0 First reference is 0 (PDU addressing) instead 1) and python minimalmodbus.

Using the same thing but with other addresses and length = 1 I can confirm OH is behaving properly regarding reads

(confirmed via manual check on the device)
0x0106 MBF_MEASURE_TEMPERATURE = 262 showing the correct temperature
0x0102 MBF_MEASURE_PH = 258 showing the correct PH value

EDIT: 1041 is correct, too tho. 0 = Manual, 1 = Automatic, 3 = Smart, all confirmed by manual checks. 1043 is correct, too.

Just select the text that you want to directly quote (as if you are going to copy and paste), and then a little image pops up saying “Quote”. Click that!

If you’re already writing a post, you can still select text from other posts, and the “Quote” pop-up will also work!

1 Like

I crossed up x401 and x411 in the docs

If don’t think mbpoll allows you to choose writing with FC06 or FC16 to see if your box behaves differently.

This means little to me, but maybe “write 0” means write-single and “write [0]”, being an array with one element, means write-multiple in that context.

hey there!
Finally had time today to check this again. Only two outcomes, either it works or I have lost 1.5k ^^

Thank you very much again for the hint with minimalmodbus’s way to write things! Using writeMultipleEvenWithSingleRegisterOrCoil=true indeed fixes it and everythings works fine. No more overwriting of a single register (which is, to be honest, totally weird).

1 Like

Hi @Sefer

I just bought Sugar Valley Controller and planning to integrate it in OH via Modbus.
Would you be so kind to share your configuration so I don’t start from 0?

Thanks

I use node-red unfortunately I can only read the registers.

here is the code of my nodered.
unfortunately I can only read out I am still looking for the white register of the boost mode and the setpiont of ph and redox

[
    {
        "id": "618881f6ac67f894",
        "type": "tab",
        "label": "X valley",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "8540ac185c4f0b5c",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "InputRegister",
        "adr": "258",
        "quantity": "1",
        "rate": "10",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 220,
        "wires": [
            [
                "eb7ef75e92679c2f"
            ],
            []
        ]
    },
    {
        "id": "d45189fa80d3d138",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "pH Waarde Zwembad (258)",
        "info": "",
        "x": 300,
        "y": 180,
        "wires": []
    },
    {
        "id": "5793be3be6a7dd43",
        "type": "debug",
        "z": "618881f6ac67f894",
        "name": "debug 4",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 960,
        "y": 100,
        "wires": []
    },
    {
        "id": "cf588a1fb85903b3",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "InputRegister",
        "adr": "262",
        "quantity": "1",
        "rate": "500",
        "rateUnit": "ms",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 100,
        "wires": [
            [
                "57cc04d605365961"
            ],
            []
        ]
    },
    {
        "id": "57cc04d605365961",
        "type": "function",
        "z": "618881f6ac67f894",
        "name": "function 4",
        "func": "msg.payload = msg.payload / 10;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 600,
        "y": 100,
        "wires": [
            [
                "5793be3be6a7dd43"
            ]
        ]
    },
    {
        "id": "dca10515cbd93b57",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Water Temperatuur Zwembad (262)",
        "info": "",
        "x": 320,
        "y": 60,
        "wires": []
    },
    {
        "id": "eb7ef75e92679c2f",
        "type": "function",
        "z": "618881f6ac67f894",
        "name": "function 5",
        "func": "msg.payload = msg.payload / 100;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 600,
        "y": 220,
        "wires": [
            [
                "6bd9fc6ce913243a"
            ]
        ]
    },
    {
        "id": "8bb62b999eff31d9",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "InputRegister",
        "adr": "259",
        "quantity": "1",
        "rate": "10",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 340,
        "wires": [
            [
                "e924bfc049eebdef"
            ],
            []
        ]
    },
    {
        "id": "532dcaf323292c93",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Rodux Sensor (259)",
        "info": "",
        "x": 270,
        "y": 300,
        "wires": []
    },
    {
        "id": "201cd07d0c2ca092",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "InputRegister",
        "adr": "257",
        "quantity": "1",
        "rate": "10",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 460,
        "wires": [
            [
                "855b18d3038c2974"
            ],
            []
        ]
    },
    {
        "id": "5a2bfab46dca6801",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Hydrolysis (257)",
        "info": "",
        "x": 260,
        "y": 420,
        "wires": []
    },
    {
        "id": "855b18d3038c2974",
        "type": "function",
        "z": "618881f6ac67f894",
        "name": "function 6",
        "func": "msg.payload = msg.payload / 10;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 580,
        "y": 460,
        "wires": [
            [
                "1eaef61f7ae507dd"
            ]
        ]
    },
    {
        "id": "6bd9fc6ce913243a",
        "type": "openhab2-out2",
        "z": "618881f6ac67f894",
        "name": "",
        "controller": "6d29cbc8a3dbce62",
        "itemname": "Zwemspa_Sugar_Valley_PH",
        "topic": "ItemUpdate",
        "payload": "",
        "onlywhenchanged": false,
        "x": 1020,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "e924bfc049eebdef",
        "type": "openhab2-out2",
        "z": "618881f6ac67f894",
        "name": "",
        "controller": "6d29cbc8a3dbce62",
        "itemname": "Zwemspa_Sugar_Valley_Rodux",
        "topic": "ItemUpdate",
        "payload": "",
        "onlywhenchanged": false,
        "x": 1030,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "1eaef61f7ae507dd",
        "type": "openhab2-out2",
        "z": "618881f6ac67f894",
        "name": "",
        "controller": "6d29cbc8a3dbce62",
        "itemname": "Zwemspa_Sugar_Valley_Hydrolysis",
        "topic": "ItemUpdate",
        "payload": "",
        "onlywhenchanged": false,
        "x": 1040,
        "y": 460,
        "wires": [
            []
        ]
    },
    {
        "id": "8f5bcd6107c34e12",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Pool Target PH (1284)",
        "info": "",
        "x": 280,
        "y": 620,
        "wires": []
    },
    {
        "id": "7ef11bb47471170c",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Pool Target Redox (1288)",
        "info": "",
        "x": 290,
        "y": 760,
        "wires": []
    },
    {
        "id": "2e3d0e9dd3ebed73",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Pool Filtration (1057)",
        "info": "",
        "x": 270,
        "y": 860,
        "wires": []
    },
    {
        "id": "933ce1153517a269",
        "type": "comment",
        "z": "618881f6ac67f894",
        "name": "Pool Filtration mode (1041)",
        "info": "",
        "x": 290,
        "y": 980,
        "wires": []
    },
    {
        "id": "01e94f6f2bfd0cab",
        "type": "debug",
        "z": "618881f6ac67f894",
        "name": "debug 11",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 660,
        "wires": []
    },
    {
        "id": "a0e76710700e84fa",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "1284",
        "quantity": "1",
        "rate": "500",
        "rateUnit": "ms",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 660,
        "wires": [
            [
                "723055f85d83ac73"
            ],
            []
        ]
    },
    {
        "id": "723055f85d83ac73",
        "type": "function",
        "z": "618881f6ac67f894",
        "name": "function 35",
        "func": "msg.payload = msg.payload / 100;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 590,
        "y": 660,
        "wires": [
            [
                "01e94f6f2bfd0cab"
            ]
        ]
    },
    {
        "id": "960ae8868fffdaad",
        "type": "debug",
        "z": "618881f6ac67f894",
        "name": "debug 12",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 800,
        "wires": []
    },
    {
        "id": "e1f222950981c090",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "1288",
        "quantity": "1",
        "rate": "500",
        "rateUnit": "ms",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 800,
        "wires": [
            [
                "960ae8868fffdaad"
            ],
            []
        ]
    },
    {
        "id": "1424f8d71512dd56",
        "type": "debug",
        "z": "618881f6ac67f894",
        "name": "debug 13",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1000,
        "y": 900,
        "wires": []
    },
    {
        "id": "48c8116905002b2f",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "1057",
        "quantity": "1",
        "rate": "500",
        "rateUnit": "ms",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 250,
        "y": 900,
        "wires": [
            [
                "1424f8d71512dd56"
            ],
            []
        ]
    },
    {
        "id": "ab076dab24091524",
        "type": "debug",
        "z": "618881f6ac67f894",
        "name": "debug 14",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1000,
        "y": 1020,
        "wires": []
    },
    {
        "id": "e92fb0055f7b6121",
        "type": "modbus-read",
        "z": "618881f6ac67f894",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "1041",
        "quantity": "1",
        "rate": "500",
        "rateUnit": "ms",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "4a874db92373c071",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 270,
        "y": 1020,
        "wires": [
            [
                "ab076dab24091524"
            ],
            []
        ]
    },
    {
        "id": "4a874db92373c071",
        "type": "modbus-client",
        "name": "x valley sugar modbus RS485",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "failureLogEnabled": true,
        "tcpHost": "192.168.2.220",
        "tcpPort": "8899",
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB",
        "serialType": "RTU-BUFFERD",
        "serialBaudrate": "9600",
        "serialDatabits": "8",
        "serialStopbits": "1",
        "serialParity": "none",
        "serialConnectionDelay": "100",
        "serialAsciiResponseStartDelimiter": "0x3A",
        "unit_id": "1",
        "commandDelay": "1",
        "clientTimeout": "1000",
        "reconnectOnTimeout": true,
        "reconnectTimeout": "2000",
        "parallelUnitIdsAllowed": true,
        "showWarnings": true,
        "showLogs": true
    },
    {
        "id": "6d29cbc8a3dbce62",
        "type": "openhab2-controller2",
        "name": "Openhab 3",
        "protocol": "http",
        "host": "localhost",
        "port": "8080",
        "path": "",
        "username": "",
        "password": "",
        "ohversion": "v3",
        "token": ""
    }
]