Python plugin: Modbus RTU / ASCII / TCP/IP Topic is solved

Python and python framework

Moderator: leecollings

T1NBK
Posts: 8
Joined: Wednesday 13 December 2017 21:08
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by T1NBK »

jatitsku wrote: Tuesday 26 April 2022 18:12
Alain wrote: Monday 25 April 2022 10:52 ...
My problem seem to be different. I tried restart, but got lots of errors. So what I did I set different polling rates for different parameters, prime numbers from 11 to 43 seconds. I suspect PLC (Siemens LOGO!) doesn't handle well several modbus request at same time. Mostly works. I only get errors when multiple parameters are read about the same time.

EDIT:
I ended up putting some random sleep in plugin, like this:

Code: Select all

@@ -173,6 +173,8 @@
import Domoticz
import sys
import pymodbus
import time
import random

from pymodbus.client.sync import ModbusSerialClient # RTU
from pymodbus.client.sync import ModbusTcpClient    # RTU over TCP
@@ -324,6 +326,7 @@ def onDisconnect(self, Connection):
        Domoticz.Log("onDisconnect called")

    def onHeartbeat(self):
	time.sleep(random.random()*5)
        Domoticz.Log("onHeartbeat called")

I have the same problem!

I'm using quite a bit of modbus TCP registers for reading out sensors and status bits of my home PLCs (Eaton Easy-E4).
I found that after a reboot Domoticz reports problems with the data returned by the PLC. After manually disabling all hardware modules in Domoticz and re-enabling the again one by one the problem disapears. After restart or reboot the problem returns.

After inspecting the log-files I found that Domoticz initiates ALL modbusTCP hardware devices at nearly the same time. This results in a flood of requests to my PLCs on every heartbeat.

Is this problem something that would basically be wrong in the core of Domoticz or is this something that has to be fixed by the plugin?
It would be better if the plugins with the same target address would not initiate at the same moment, but I have no idea how to solve this.

It would be nice if this could be fixed.
For now, I'll try the fix supplied by jatitsku if I ever find out where to put it :S

edit: found it finally..
This fix does work a little, but the spread is not big enough when handeling 10+ sensors. I'll try *10 but I'm afraid it eventually will result in other forms of timeouts.
Already @*20 and still not perfect.
Also adding random 20 (seconds?) to a read on a status bit I want to check everey 10 seconds, for smooth GUI response, is not really a solution...

edit:
Would it be difficult to add a fixed offset to the hardware settings? Instead of random it would use te same offset every time allowing the user te spread the polls more equally. And allow the user to set it to 0 for fast inputs and allow longer offset for slow inputs.

edit:
I changed the sleeptime to the remaining value after deviding the register value by 10:

Code: Select all

time.sleep(int(self.Domoticz_Setting_Register_Number) % 10)
Not sure i did this correctly but testing it now..

edit:
I have 16 reading so changed it to:

Code: Select all

time.sleep(int(self.Domoticz_Setting_Register_Number) % 20)
Still some errors but it's a lot less than before
Last edited by T1NBK on Sunday 13 November 2022 23:40, edited 5 times in total.
Micha123
Posts: 62
Joined: Monday 28 August 2017 16:44
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Micha123 »

i have an problem it is not working

2022-11-13 10:42:20.650 Error: Epever Batt Volt: (ModbusREAD) failed to load 'plugin.py', Python Path used was '/home/pi/domoticz/plugins/modbus-read/:/usr/lib/python39.zip:/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload:/usr/local/lib/python3.9/dist-packages:/usr/lib/python3/dist-packages:/usr/lib/python3.9/dist-packages'.
2022-11-13 10:42:20.659 Error: Epever Batt Volt: Traceback (most recent call last):
2022-11-13 10:42:20.660 Error: Epever Batt Volt: File "/home/pi/domoticz/plugins/modbus-read/plugin.py", line 177, in <module>
2022-11-13 10:42:20.660 Error: Epever Batt Volt: from pymodbus.client.sync import ModbusSerialClient # RTU
2022-11-13 10:42:20.660 Error: Epever Batt Volt: ModuleNotFoundError: No module named 'pymodbus.client.sync'

packages are installed with

sudo pip3 install -U pymodbus pymodbusTCP but still the same problem with raspbian
T1NBK
Posts: 8
Joined: Wednesday 13 December 2017 21:08
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus Universal

Post by T1NBK »

Domoticx wrote: Sunday 07 January 2018 23:10 Ps. There will be 2 universal plugins, one to READ and one to WRITE :D
modbus domoticz universal write 01.png
modbus domoticz universal write 02.png
I never managed to get the WRITE function to work. I got some INT error. But now I see in this picture that the payload ON and payload OFF are (HEX). Why is that no longer mentioned in the current version? Did you switch to INT? Or did the "(HEX)" get lost somewhere...
T1NBK
Posts: 8
Joined: Wednesday 13 December 2017 21:08
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by T1NBK »

Micha123 wrote: Sunday 13 November 2022 10:48 i have an problem it is not working

2022-11-13 10:42:20.650 Error: Epever Batt Volt: (ModbusREAD) failed to load 'plugin.py', Python Path used was '/home/pi/domoticz/plugins/modbus-read/:/usr/lib/python39.zip:/usr/lib/python3.9:/usr/lib/python3.9/lib-dynload:/usr/local/lib/python3.9/dist-packages:/usr/lib/python3/dist-packages:/usr/lib/python3.9/dist-packages'.
2022-11-13 10:42:20.659 Error: Epever Batt Volt: Traceback (most recent call last):
2022-11-13 10:42:20.660 Error: Epever Batt Volt: File "/home/pi/domoticz/plugins/modbus-read/plugin.py", line 177, in <module>
2022-11-13 10:42:20.660 Error: Epever Batt Volt: from pymodbus.client.sync import ModbusSerialClient # RTU
2022-11-13 10:42:20.660 Error: Epever Batt Volt: ModuleNotFoundError: No module named 'pymodbus.client.sync'

packages are installed with

sudo pip3 install -U pymodbus pymodbusTCP but still the same problem with raspbian
Can you try it on a new SD card? Just a clean install of rasbian, domoticz, the pip3 modules and the modbusTCP python plugins. That might help to figure out if it's in your old install or if it's something with current versions or something
fantilator
Posts: 28
Joined: Saturday 12 November 2016 20:52
Target OS: NAS (Synology & others)
Domoticz version: 3.5877
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by fantilator »

Hi all,

I'm having a similar problem on a synology system.

Code: Select all

2022-11-17 10:47:46.566 Error: Siemens Logo I1: (ModbusREAD) failed to load 'plugin.py', Python Path used was '/opt/domoticz/userdata/plugins/modbus-read/:/usr/lib/python37.zip:/usr/lib/python3.7:/usr/lib/python3.7/lib-dynload:/usr/local/lib/python3.7/dist-packages:/usr/lib/python3/dist-packages:/usr/lib/python3.7/dist-packages'.
2022-11-17 10:47:46.665 Error: Siemens Logo I1: Traceback (most recent call last):
2022-11-17 10:47:46.665 Error: Siemens Logo I1: File "/opt/domoticz/userdata/plugins/modbus-read/plugin.py", line 175, in <module>
2022-11-17 10:47:46.666 Error: Siemens Logo I1: import pymodbus
2022-11-17 10:47:46.666 Error: Siemens Logo I1: ModuleNotFoundError: No module named 'pymodbus'
2022-11-17 10:47:46.704 Error: Traceback (most recent call last):
2022-11-17 10:47:46.704 Error: File "Script #1", line 175, in <module>
2022-11-17 10:47:46.704 Error: ModuleNotFoundError: No module named 'pymodbus'
2022-11-17 10:47:46.718 Error: Traceback (most recent call last):
2022-11-17 10:47:46.718 Error: File "Script #1", line 175, in <module>
2022-11-17 10:47:46.718 Error: ModuleNotFoundError: No module named 'pymodbus'
2022-11-17 10:47:46.735 Error: Traceback (most recent call last):
2022-11-17 10:47:46.735 Error: File "Script #1", line 175, in <module>
2022-11-17 10:47:46.736 Error: ModuleNotFoundError: No module named 'pymodbus'
I'm currently running the 2022.2 stable version.
The about of domtics says Python V3.7.3

My synology tells me that Python 2 and V3.8.12 is installed.

Can someone help me here?
ErikAtSpijk
Posts: 16
Joined: Sunday 11 March 2018 22:03
Target OS: Raspberry Pi / ODroid
Domoticz version: 4.10717
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by ErikAtSpijk »

I ran into the same issue, it looks like a problem with the different versions. The plugin seems to depend on pymodbus, which seems to have been updated to version 3 to be compatible with python =>3.8

The site of pymodbus states: Note 3.0.0 is a major release with a number of incompatible changes."" I guess this could explain the error: ModuleNotFoundError: No module named 'pymodbus.client.sync'""

https://github.com/riptideio/pymodbus
Alain
Posts: 164
Joined: Sunday 26 April 2020 5:27
Target OS: Linux
Domoticz version: 2022.1
Location: Netherlands
Contact:

Re: Python plugin: Modbus Universal

Post by Alain »

T1NBK wrote: Wednesday 16 November 2022 23:29
Domoticx wrote: Sunday 07 January 2018 23:10 Ps. There will be 2 universal plugins, one to READ and one to WRITE :D
modbus domoticz universal write 01.png
modbus domoticz universal write 02.png
I never managed to get the WRITE function to work. I got some INT error. But now I see in this picture that the payload ON and payload OFF are (HEX). Why is that no longer mentioned in the current version? Did you switch to INT? Or did the "(HEX)" get lost somewhere...
I don't think user @Domoticx is active on this forum anymore.

I changed the WRITE plugin myself to accept values instead of just on and off. Not able to access the plugin right now but if you want, I can share the code I use now.
Hue | Zigbee2Mqtt | MQTT | P1 | Xiaomi | RFXCom | Modbus | Qlima | Solaredge
TP-Link | Plugwise | Thermosmart | Node-Red | Grafana | Master and 5 remote servers
Alain
Posts: 164
Joined: Sunday 26 April 2020 5:27
Target OS: Linux
Domoticz version: 2022.1
Location: Netherlands
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Alain »

ErikAtSpijk wrote: Sunday 20 November 2022 10:56 I ran into the same issue, it looks like a problem with the different versions. The plugin seems to depend on pymodbus, which seems to have been updated to version 3 to be compatible with python =>3.8

The site of pymodbus states: Note 3.0.0 is a major release with a number of incompatible changes."" I guess this could explain the error: ModuleNotFoundError: No module named 'pymodbus.client.sync'""

https://github.com/riptideio/pymodbus
I couldn't get the plugins running without errors on a newly installed system running all the latest versions. The older system I had running on another system worked fine. I changed my new system over to an older Domoticz version 2021.1 and then the plugins run fine.
Hue | Zigbee2Mqtt | MQTT | P1 | Xiaomi | RFXCom | Modbus | Qlima | Solaredge
TP-Link | Plugwise | Thermosmart | Node-Red | Grafana | Master and 5 remote servers
fantilator
Posts: 28
Joined: Saturday 12 November 2016 20:52
Target OS: NAS (Synology & others)
Domoticz version: 3.5877
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by fantilator »

Anyone already found a way on how to upgrade domoticz to use the correct version of Python?

Returning back to 2021.1 version is not an option due to missing build in plugins.
I'm running Domoticz in a docker image on a synology system (DSM7.1).
Alain
Posts: 164
Joined: Sunday 26 April 2020 5:27
Target OS: Linux
Domoticz version: 2022.1
Location: Netherlands
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Alain »

fantilator wrote: Monday 28 November 2022 18:39 Anyone already found a way on how to upgrade domoticz to use the correct version of Python?

Returning back to 2021.1 version is not an option due to missing build in plugins.
I'm running Domoticz in a docker image on a synology system (DSM7.1).
It's not about upgrading Domoticz to run the correct version op Python, I think. There's something in the script or one of its dependants doesn't work well with all the latest versions. I don't think you can depend on user @Domoticx to revamp the plugin. He's not active in this topic anymore for some reason. Someone else will have to try.

I gave it a look over, but then just didn't want to spend the time on it. I have most plugins running on their own remote server, so it doesn't matter to me if it needs an older version of domoticz. Good thing about running all major plugins on their own remote server is the flexibility when things go wrong. You just reinstall the whole server and it doesn't affect the rest of your system.
Hue | Zigbee2Mqtt | MQTT | P1 | Xiaomi | RFXCom | Modbus | Qlima | Solaredge
TP-Link | Plugwise | Thermosmart | Node-Red | Grafana | Master and 5 remote servers
fantilator
Posts: 28
Joined: Saturday 12 November 2016 20:52
Target OS: NAS (Synology & others)
Domoticz version: 3.5877
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by fantilator »

I think I figured it out by installing pymodbus, pymodbusTCP and wheels via this command.
I also updated pip and all outdated packages.

Code: Select all

sudo docker exec -it domoticz-domoticz2022.2 pip install pyModbusTCP
User avatar
andrehj
Posts: 45
Joined: Sunday 06 January 2019 14:26
Target OS: -
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by andrehj »

Upgrading my OS from Buster to Bullseye ruined this plugin (which I only used for modbustcp to communicate with my SMA solar inverter).
I've also tried to reinstall pymodbus and pymodbustcp, but as soon as the plugin is used Domoticz becomes completely unresponsive. (Temporarily) removing the modbus read plugin from the folder fixed Domoticz, but now I need a new solution to read data from Modbustcp devices.
I've decided to abandon this plugin, since it has not been maintained for years.
I'm now looking for a command line utility which can read modbus TCP devices. So far I've found modpoll and mbpoll. Anyone have any experience with those? Or should I use another (better, easier to use) one?
User avatar
Domoberry
Posts: 116
Joined: Tuesday 30 May 2017 19:00
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Domoberry »

I fear I'm also looking for an alternative if indeed this code is no longer maintained (unfortunately!!)
Have done a number of updates on my Domoticz RPI system lately. Most recent was the move Domoticz 2022.1 -> 2023.1 (still on Buster).
Now, errors show up more of less randomly on Modbus which is used to read 8 values from an energy meter. Here is an example of an error message. Similar error logging seemingly randomly on the other Reads. Most of the times a Read works ok:

Code: Select all

2023-03-14 08:58:22.026 EVSE-power-A: Pushing 'onHeartbeatCallback' on to queue
[…]
2023-03-14 08:58:22.076 EVSE-power-A: Processing 'onHeartbeatCallback' message
2023-03-14 08:58:22.076 EVSE-power-A: Acquiring GIL for 'onHeartbeatCallback'
2023-03-14 08:58:22.076 EVSE-power-A: Calling message handler 'onHeartbeat' on 'module' type object.
2023-03-14 08:58:22.076 EVSE-power-A: onHeartbeat called
2023-03-14 08:58:22.077 EVSE-power-A: MODBUS DEBUG - INTERFACE: Port=/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0, BaudRate=9600, StopBits=1, ByteSize=8 Parity=E
2023-03-14 08:58:22.077 EVSE-power-A: MODBUS DEBUG - SETTINGS: Method=rtu, Device ID=13, Register=18, Function=4, Data type=float32, Pollrate=30
[…]
2023-03-14 08:58:24.085 EVSE-power-A: MODBUS DEBUG - RESPONSE: Modbus Error: [Input/Output] Modbus Error: [Invalid Message] No response received, expected at least 2 bytes (0 received)
[…]
2023-03-14 08:58:24.086 Error: EVSE-power-A: Modbus error decoding or received no data (RTU/ASCII/RTU over TCP)!, check your settings!
I'm using the 2021.7 versions for Modbus Read (and Write) and I have not upgraded pymodbus and pymodbusTCP in a while.
Andrehj's post above makes me hesitate to move to Bullseye...

Any thoughts or alternative ways to interface a (any) 3 phase energy meter (next to the P1) to Domoticz?
User avatar
Domoberry
Posts: 116
Joined: Tuesday 30 May 2017 19:00
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Domoberry »

After I got modbus working again on Domoticz 2023.1 on Buster, today upgraded to Bullseye.
As already mentioned by andrehj Bullseye caused the modbus plugin to fails.
In my case, after the Bullseye update, the Modus 'hardware' was gone from Domoticz Setup/Hardware nd there is also not an option to reinstall it (see attached)
Is there a (simple) way to bring this modbus back to life again as far as someone knows?
Tip I got: follow this information first: https://www.domoticz.com/wiki/Using_Python_plugins
Any other suggestions welcome.
Attachments
230319 cannot select HW anymore.JPG
230319 cannot select HW anymore.JPG (49.32 KiB) Viewed 4305 times
230319 Modbus Hardware gone.JPG
230319 Modbus Hardware gone.JPG (64.97 KiB) Viewed 4305 times
User avatar
Domoberry
Posts: 116
Joined: Tuesday 30 May 2017 19:00
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Domoberry »

Update: following steps in https://www.domoticz.com/wiki/Using_Python_plugins resulted in checking the 'about' screen for Domoticz, which reported 'Python version: none' now changed to "Python Version: 3.9.2 (default, Mar 12 2021, 04:06:34) [GCC 10.2.1 20210110]".
After a restart, some other Python script (Nefit EMS from "BBQKees") started to work again.
Modbus still failing with similar errors in the log for each modbus device:

Code: Select all

2023-03-20 09:59:54.320 Error: EVSE-power: Traceback (most recent call last):
2023-03-20 09:59:54.320 Error: EVSE-power: File "/home/pi/domoticz/plugins/modbus-read/plugin.py", line 175, in <module>
2023-03-20 09:59:54.320 Error: EVSE-power: import pymodbus
2023-03-20 09:59:54.320 Error: EVSE-power: ModuleNotFoundError: No module named 'pymodbus'
Moving to the next step!
User avatar
waltervl
Posts: 5153
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by waltervl »

Did you install the module with sudo? As this is a known solution for error
ModuleNotFoundError: No module named 'xxxxxx'

See also the wiki page Using Python Plugins.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
T1NBK
Posts: 8
Joined: Wednesday 13 December 2017 21:08
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by T1NBK »

Too bad to hear that upgrading Raspbian will break everything again...

I finally sort of managed to fix the problems I had with reading the value's from my modbus devices.
I added a wait period depending on the modbus address so the scripts don't try to access the devices at the same time. Not sure how well this works tho... it might actually be useless so I'm going to try my latest script without this delay. Just put a # in front of line 319. apparently it does something so leave it there!
I also added an errorcode to the error message to find out where thing go wrong. I found out that most of the times the 'data' is 'None'.
So I added a 1 second delayed retry on reading the value if it fails on reading or decoding. Normally you would put this in a function or something but I'm no good in coding python so i just copied the whole part of the script again.

Anybody know how to send Null, None or 'Invalid' to domoticz? The default is now '0' so domoticz will log 0 degrees in my temperature logs instead of just ignoring the return value.

My code now looks like this:

Code: Select all

# Modbus RTU / ASCII / TCP/IP - Universal READ Plugin for Domoticz
#
# Tested on domoticz 2020.2 (stable) with Python v3.7.3 and pymodbus v2.3.0
#
# Author: Sebastiaan Ebeltjes / DomoticX.nl
# RTU Serial HW: USB RS485-Serial Stick, like https://webshop.domoticx.nl/index.php?route=product/search&search=RS485%20RTU%20USB
#
# Dependancies:
# - pymodbus AND pymodbusTCP:
#   - Install for python3 with: sudo pip3 install -U pymodbus pymodbusTCP
#

"""
<plugin key="ModbusREAD" name="Modbus RTU / ASCII / TCP/IP - READ v2020.2F" author="S. Ebeltjes / DomoticX.nl" version="2020.2F" externallink="http://domoticx.nl" wikilink="https://github.com/DomoticX/domoticz-modbus">
    <description>
        <h3>Modbus RTU / ASCII / TCP/IP - READ</h3>
        With this plugin you can read from RS485 Modbus devices with methods RTU/ASCII/TCP<br/>
        <br/>
        <h4>RTU</h4>
        The serial binary communication protocol. It is the communication standard that<br/>
        became widely used and all series of PLC's and other device producers support it.<br/>
        It goes about the network protocol of the 1Master x nSlave type. The Slave devices can be 254 at the most.<br/>
        <h4>ASCII</h4>
        This protocol is similar to Modbus RTU, but the binary content is transformed to common ASCII characters.<br/>
        It is not used as frequently as Modbus RTU.<br/>
        <h4>RTU over TCP</h4> 
        Means a MODBUS RTU packet wrapped in a TCP packet. The message bytes are modified to add the 6 byte MBAP header and remove the two byte CRC.
        <h4>TCP/IP</h4>
        It is a network protocol - classic Ethernet TCP/IP with the 10/100 Mbit/s speed rate, a standard net HW Ethernet card is sufficient.<br/>
        The communication principle (1Master x nSlave) is the same as for Modbus RTU. used port is most likely: 502<br/>
        <br/>
        <h3>Set-up and Configuration:</h3>
        See wiki link above.<br/> 
    </description>
    <params>
        <param field="Mode1" label="Communication Mode" width="160px" required="true">
            <options>
                <option label="RTU" value="rtu:rtu" default="true"/>
                <option label="RTU (+DEBUG)" value="rtu:debug"/>
                <option label="RTU ASCII" value="ascii:ascii"/>
                <option label="RTU ASCII (+DEBUG)" value="ascii:debug"/>
                <option label="RTU over TCP" value="rtutcp:rtutcp"/>
                <option label="RTU over TCP (+DEBUG)" value="rtutcp:debug"/>
                <option label="TCP/IP" value="tcpip:tcpip"/>
                <option label="TCP/IP (+DEBUG)" value="tcpip:debug"/>
            </options>
        </param>
        <param field="SerialPort" label="RTU - Serial Port" width="120px"/>
        <param field="Mode3" label="RTU - Port settings" width="260px">
            <options>
                <option label="StopBits 1 / ByteSize 7 / Parity: None" value="S1B7PN"/>
                <option label="StopBits 1 / ByteSize 7 / Parity: Even" value="S1B7PE"/>
                <option label="StopBits 1 / ByteSize 7 / Parity: Odd" value="S1B7PO"/>
                <option label="StopBits 1 / ByteSize 8 / Parity: None" value="S1B8PN" default="true"/>
                <option label="StopBits 1 / ByteSize 8 / Parity: Even" value="S1B8PE"/>
                <option label="StopBits 1 / ByteSize 8 / Parity: Odd" value="S1B8PO"/>
                <option label="StopBits 2 / ByteSize 7 / Parity: None" value="S2B7PN"/>
                <option label="StopBits 2 / ByteSize 7 / Parity: Even" value="S2B7PE"/>
                <option label="StopBits 2 / ByteSize 7 / Parity: Odd" value="S2B7PO"/>
                <option label="StopBits 2 / ByteSize 8 / Parity: None" value="S2B8PN"/>
                <option label="StopBits 2 / ByteSize 8 / Parity: Even" value="S2B8PE"/>
                <option label="StopBits 2 / ByteSize 8 / Parity: Odd" value="S2B8PO"/>
            </options>
        </param>
        <param field="Mode2" label="RTU - Baudrate" width="70px">
            <options>
                <option label="1200" value="1200"/>
                <option label="2400" value="2400"/>
                <option label="4800" value="4800"/>
                <option label="9600" value="9600" default="true"/>
                <option label="14400" value="14400"/>
                <option label="19200" value="19200"/>
                <option label="38400" value="38400"/>
                <option label="57600" value="57600"/>
                <option label="115200" value="115200"/>
            </options>
        </param>
        <param field="Address" label="TCP/IP - IP:Port" width="140px" default="192.168.2.1:502"/>
        <param field="Password" label="Device ID:Pollingrate(sec)" width="50px" default="1:10" required="true"/>
        <param field="Username" label="Modbus Function" width="250px" required="true">
            <options>
                <option label="Read Coil (Function 1)" value="1"/>
                <option label="Read Discrete Input (Function 2)" value="2"/>
                <option label="Read Holding Registers (Function 3)" value="3" default="true"/>
                <option label="Read Input Registers (Function 4)" value="4"/>
            </options>
        </param>
        <param field="Port" label="Register number" width="50px" default="1" required="true"/>
        <param field="Mode6" label="Data type" width="180px" required="true">
            <options>
                <option label="No conversion (1 register)" value="noco"/>
                <option label="BOOL (TRUE/FALSE)" value="bool"/>
                <option label="INT 8-Bit LSB" value="int8LSB"/>
                <option label="INT 8-Bit MSB" value="int8MSB"/>
                <option label="INT 16-Bit" value="int16"/>
                <option label="INT 16-Bit Swapped" value="int16s"/>
                <option label="INT 32-Bit" value="int32"/>
                <option label="INT 32-Bit Swapped" value="int32s"/>
                <option label="INT 64-Bit" value="int64"/>
                <option label="INT 64-Bit Swapped" value="int64s"/>
                <option label="UINT 8-Bit LSB" value="uint8LSB"/>
                <option label="UINT 8-Bit MSB" value="uint8MSB"/>
                <option label="UINT 16-Bit" value="uint16" default="true"/>
                <option label="UINT 16-Bit Swapped" value="uint16s"/>
                <option label="UINT 32-Bit" value="uint32"/>
                <option label="UINT 32-Bit Swapped" value="uint32s"/>
                <option label="UINT 64-Bit" value="uint64"/>
                <option label="UINT 64-Bit Swapped" value="uint64s"/>
                <option label="FLOAT 32-Bit" value="float32"/>
                <option label="FLOAT 32-Bit Swapped" value="float32"/>
                <option label="FLOAT 64-Bit" value="float64"/>
                <option label="FLOAT 64-Bit Swapped" value="float64s"/>
                <option label="STRING 2-byte" value="string2"/>
                <option label="STRING 4-byte" value="string4"/>
                <option label="STRING 6-byte" value="string6"/>
                <option label="STRING 8-byte" value="string8"/>
            </options>
        </param>
        <param field="Mode5" label="Divide value" width="100px" required="true">
            <options>
                <option label="No" value="div0" default="true"/>
                <option label="Divide /10" value="div10"/>
                <option label="Divide /100" value="div100"/>
                <option label="Divide /1000" value="div1000"/>
                <option label="Divide /10000" value="div10000"/>
            </options>
        </param>
        <param field="Mode4" label="Sensor type" width="160px" required="true" value="Custom">
            <options>
                <option label="Air Quality" value="Air Quality"/>
                <option label="Alert" value="Alert"/>
                <option label="Barometer" value="Barometer"/>
                <option label="Counter Incremental" value="Counter Incremental"/>
                <option label="Current/Ampere" value="Current/Ampere"/>
                <option label="Current (Single)" value="Current (Single)"/>
                <option label="Custom" value="Custom" default="true"/>
                <option label="Distance" value="Distance"/>
                <option label="Gas" value="Gas"/>
                <option label="Humidity" value="Humidity"/>
                <option label="Illumination" value="Illumination"/>
                <option label="kWh" value="kWh"/>
                <option label="Leaf Wetness" value="Leaf Wetness"/>
                <option label="Percentage" value="Percentage"/>
                <option label="Pressure" value="Pressure"/>
                <option label="Rain" value="Rain"/>
                <option label="Selector Switch" value="Selector Switch"/>
                <option label="Soil Moisture" value="Soil Moisture"/>
                <option label="Solar Radiation" value="Solar Radiation"/>
                <option label="Sound Level" value="Sound Level"/>
                <option label="Switch" value="Switch"/>
                <option label="Temperature" value="Temperature"/>
                <option label="Temp+Hum" value="Temp+Hum"/>
                <option label="Temp+Hum+Baro" value="Temp+Hum+Baro"/>
                <option label="Text" value="Text"/>
                <option label="Usage" value="Usage"/>
                <option label="UV" value="UV"/>
                <option label="Visibility" value="Visibility"/>
                <option label="Voltage" value="Voltage"/>
                <option label="Waterflow" value="Waterflow"/>
                <option label="Wind" value="Wind"/>
                <option label="Wind+Temp+Chill" value="Wind+Temp+Chill"/>
            </options>
        </param>
    </params>
</plugin>
"""

import Domoticz
import sys
import pymodbus
import time
import random

from pymodbus.client.sync import ModbusSerialClient # RTU
from pymodbus.client.sync import ModbusTcpClient    # RTU over TCP
from pymodbus.transaction import ModbusRtuFramer    # RTU over TCP
from pyModbusTCP.client import ModbusClient         # TCP/IP
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

# Declare internal variables
result = ""
value = 0
ignored = 0
data = []

class BasePlugin:
    enabled = False
    def __init__(self):
        return

    def onStart(self):
        Domoticz.Log("onStart called")
        try:
          Domoticz.Log("Modbus RTU/ASCII/TCP - Universal READ loaded!, using python v" + sys.version[:6] + " and pymodbus v" + pymodbus.__version__)
        except:
          Domoticz.Log("Modbus RTU/ASCII/TCP - Universal READ loaded!")

        # Dependancies notification
        try:
          if (float(Parameters["DomoticzVersion"][:6]) < float("2020.2")): Domoticz.Error("WARNING: Domoticz version is outdated/not supported, please update!")
          if (float(sys.version[:1]) < 3): Domoticz.Error("WARNING: Python3 should be used!")	
          if (float(pymodbus.__version__[:3]) < float("2.3")): Domoticz.Error("WARNING: Pymodbus version is outdated, please update!")	
        except:
          Domoticz.Error("WARNING: Dependancies could not be checked!")

        ########################################
        # READ-IN OPTIONS AND SETTINGS
        ########################################
        # Convert "option names" to variables for easy reading and debugging.
        # Note: Parameters["Port"] cannot accept other value then int! (e.g. 192.168.0.0 will result in 192)

        Domoticz_Setting_Communication_MODDEB = Parameters["Mode1"].split(":") # Split MODE and DEBUG setting MODE:DEBUG
        self.Domoticz_Setting_Communication_Mode = Domoticz_Setting_Communication_MODDEB[0]
        self.Domoticz_Setting_Serial_Port = Parameters["SerialPort"]
        self.Domoticz_Setting_Baudrate = Parameters["Mode2"]
        self.Domoticz_Setting_Port_Mode = Parameters["Mode3"]
        self.Domoticz_Setting_Modbus_Function = Parameters["Username"]
        self.Domoticz_Setting_Register_Number = Parameters["Port"]
        self.Domoticz_Setting_Data_Type = Parameters["Mode6"]
        self.Domoticz_Setting_Divide_Value = Parameters["Mode5"]
        self.Domoticz_Setting_Sensor_Type = Parameters["Mode4"]

        self.Domoticz_Setting_Device_IDPOL = Parameters["Password"].split(":") # Split ID and pollrate setting ID:POLL (heartbeat)
        self.Domoticz_Setting_Device_ID = 1 # Default
        if len(self.Domoticz_Setting_Device_IDPOL) > 0: self.Domoticz_Setting_Device_ID = self.Domoticz_Setting_Device_IDPOL[0]
        self.Domoticz_Setting_Device_Pollrate = 10 # Default
        if len(self.Domoticz_Setting_Device_IDPOL) > 1: self.Domoticz_Setting_Device_Pollrate = self.Domoticz_Setting_Device_IDPOL[1]

        self.Domoticz_Setting_TCP_IPPORT = Parameters["Address"].split(":") # Split address and port setting TCP:IP
        self.Domoticz_Setting_TCP_IP = 0 # Default
        if len(self.Domoticz_Setting_TCP_IPPORT) > 0: self.Domoticz_Setting_TCP_IP = self.Domoticz_Setting_TCP_IPPORT[0]
        self.Domoticz_Setting_TCP_PORT = 0 # Default
        if len(self.Domoticz_Setting_TCP_IPPORT) > 1: self.Domoticz_Setting_TCP_PORT = self.Domoticz_Setting_TCP_IPPORT[1]

        # Set debug yes/no
        if (Domoticz_Setting_Communication_MODDEB[1] == "debug"):
          Domoticz.Debugging(1) # Enable debugging
          DumpConfigToLog()
          Domoticz.Debug("***** NOTIFICATION: Debug enabled!")
        else:
          Domoticz.Debugging(0) # Disable debugging

        # Set device pollrate (heartbeat)
        Domoticz.Heartbeat(int(self.Domoticz_Setting_Device_Pollrate))
        Domoticz.Debug("***** NOTIFICATION: Pollrate (heartbeat): "+self.Domoticz_Setting_Device_Pollrate+" seconds.")

        # RTU - Serial port settings
        if (self.Domoticz_Setting_Port_Mode == "S1B7PN"): self.StopBits, self.ByteSize, self.Parity = 1, 7, "N"
        if (self.Domoticz_Setting_Port_Mode == "S1B7PE"): self.StopBits, self.ByteSize, self.Parity = 1, 7, "E"
        if (self.Domoticz_Setting_Port_Mode == "S1B7PO"): self.StopBits, self.ByteSize, self.Parity = 1, 7, "O"
        if (self.Domoticz_Setting_Port_Mode == "S1B8PN"): self.StopBits, self.ByteSize, self.Parity = 1, 8, "N"
        if (self.Domoticz_Setting_Port_Mode == "S1B8PE"): self.StopBits, self.ByteSize, self.Parity = 1, 8, "E"
        if (self.Domoticz_Setting_Port_Mode == "S1B8PO"): self.StopBits, self.ByteSize, self.Parity = 1, 8, "O"
        if (self.Domoticz_Setting_Port_Mode == "S2B7PN"): self.StopBits, self.ByteSize, self.Parity = 2, 7, "N"
        if (self.Domoticz_Setting_Port_Mode == "S2B7PE"): self.StopBits, self.ByteSize, self.Parity = 2, 7, "E"
        if (self.Domoticz_Setting_Port_Mode == "S2B7PO"): self.StopBits, self.ByteSize, self.Parity = 2, 7, "O"
        if (self.Domoticz_Setting_Port_Mode == "S2B8PN"): self.StopBits, self.ByteSize, self.Parity = 2, 8, "N"
        if (self.Domoticz_Setting_Port_Mode == "S2B8PE"): self.StopBits, self.ByteSize, self.Parity = 2, 8, "E"
        if (self.Domoticz_Setting_Port_Mode == "S2B8PO"): self.StopBits, self.ByteSize, self.Parity = 2, 8, "O"

        # Read n registers depending on data type
        # Added additional options for byte/word swapping
        self.Register_Count = 1 # Default
        if (self.Domoticz_Setting_Data_Type == "noco"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "bool"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "int8LSB"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "int8MSB"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "int16"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "int16s"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "int32"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "int32s"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "int64"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "int64s"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "uint8LSB"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "uint8MSB"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "uint16"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "uint16s"): self.Register_Count = 1
        if (self.Domoticz_Setting_Data_Type == "uint32"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "uint32s"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "uint64"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "uint64s"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "float32"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "float32s"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "float64"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "float64s"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "string2"): self.Register_Count = 2
        if (self.Domoticz_Setting_Data_Type == "string4"): self.Register_Count = 4
        if (self.Domoticz_Setting_Data_Type == "string6"): self.Register_Count = 6
        if (self.Domoticz_Setting_Data_Type == "string8"): self.Register_Count = 8
		
        # Due to the lack of more parameter posibility, the name will be the hardware name
        self.Domoticz_Setting_Sensor_Type = Parameters["Mode4"]
        if (len(Devices) == 0): Domoticz.Device(Name="Modbus-READ",  Unit=1, TypeName=self.Domoticz_Setting_Sensor_Type, Image=0, Used=1).Create() #Added sensor type

        return

    def onStop(self):
        Domoticz.Log("onStop called")

    def onConnect(self, Connection, Status, Description):
        Domoticz.Log("onConnect called")
        return

    def onMessage(self, Connection, Data, Status, Extra):
        Domoticz.Log("onMessage called")

    def onCommand(self, Unit, Command, Level, Hue):
        Domoticz.Log("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level))

    def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
        Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)

    def onDisconnect(self, Connection):
        Domoticz.Log("onDisconnect called")

    def onHeartbeat(self):
        # extra delay toegevoegt om flood richting PLC te voorkomen, geen idee of het nog nodig is...
        time.sleep(int(self.Domoticz_Setting_Register_Number) % 20)
        # einde aanpassing #
        Domoticz.Log("onHeartbeat called")

        ########################################
        # SET HARDWARE - pymodbus: RTU / ASCII
        ########################################
        if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii"):
          Domoticz.Debug("MODBUS DEBUG - INTERFACE: Port="+self.Domoticz_Setting_Serial_Port+", BaudRate="+self.Domoticz_Setting_Baudrate+", StopBits="+str(self.StopBits)+", ByteSize="+str(self.ByteSize)+" Parity="+self.Parity)
          Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method="+self.Domoticz_Setting_Communication_Mode+", Device ID="+self.Domoticz_Setting_Device_ID+", Register="+self.Domoticz_Setting_Register_Number+", Function="+self.Domoticz_Setting_Modbus_Function+", Data type="+self.Domoticz_Setting_Data_Type+", Pollrate="+self.Domoticz_Setting_Device_Pollrate)
          try:
            client = ModbusSerialClient(method=self.Domoticz_Setting_Communication_Mode, port=self.Domoticz_Setting_Serial_Port, stopbits=self.StopBits, bytesize=self.ByteSize, parity=self.Parity, baudrate=int(self.Domoticz_Setting_Baudrate), timeout=2, retries=2)
          except:
            Domoticz.Error("Error opening Serial interface on "+self.Domoticz_Setting_Serial_Port)
            Devices[1].Update(1, "0") # Set value to 0 (error)

        ########################################
        # SET HARDWARE - pymodbus: RTU over TCP
        ########################################
        if (self.Domoticz_Setting_Communication_Mode == "rtutcp"):
          Domoticz.Debug("MODBUS DEBUG - INTERFACE: IP="+self.Domoticz_Setting_TCP_IP+", Port="+self.Domoticz_Setting_TCP_PORT)
          Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method="+self.Domoticz_Setting_Communication_Mode+", Device ID="+self.Domoticz_Setting_Device_ID+", Register="+self.Domoticz_Setting_Register_Number+", Function="+self.Domoticz_Setting_Modbus_Function+", Data type="+self.Domoticz_Setting_Data_Type+", Pollrate="+self.Domoticz_Setting_Device_Pollrate)
          try:
            client = ModbusTcpClient(host=self.Domoticz_Setting_TCP_IP, port=int(self.Domoticz_Setting_TCP_PORT), framer=ModbusRtuFramer, auto_open=True, auto_close=True, timeout=2)
          except:
            Domoticz.Error("Error opening RTU over TCP interface on address: "+self.Domoticz_Setting_TCP_IPPORT)
            Devices[1].Update(1, "0") # Set value to 0 (error)

        ########################################
        # SET HARDWARE - pymodbusTCP: TCP/IP
        ########################################
        if (self.Domoticz_Setting_Communication_Mode == "tcpip"):
          Domoticz.Debug("MODBUS DEBUG - INTERFACE: IP="+self.Domoticz_Setting_TCP_IP+", Port="+self.Domoticz_Setting_TCP_PORT)
          Domoticz.Debug("MODBUS DEBUG - SETTINGS: Method="+self.Domoticz_Setting_Communication_Mode+", Device ID="+self.Domoticz_Setting_Device_ID+", Register="+self.Domoticz_Setting_Register_Number+", Function="+self.Domoticz_Setting_Modbus_Function+", Data type="+self.Domoticz_Setting_Data_Type+", Pollrate="+self.Domoticz_Setting_Device_Pollrate)
          try:
            client = ModbusClient(host=self.Domoticz_Setting_TCP_IP, port=int(self.Domoticz_Setting_TCP_PORT), unit_id=int(self.Domoticz_Setting_Device_ID), auto_open=True, auto_close=True, timeout=2)
          except:
            Domoticz.Error("Error opening TCP/IP interface on address: "+self.Domoticz_Setting_TCP_IPPORT)
            Devices[1].Update(1, "0") # Set value to 0 (error)

        ########################################
        # GET DATA - pymodbus: RTU / ASCII / RTU over TCP
        ########################################
        if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii" or self.Domoticz_Setting_Communication_Mode == "rtutcp"):
          try:
            # Function to execute
            if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils(int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID))
            if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs(int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID))
            if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID))
            if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count, unit=int(self.Domoticz_Setting_Device_ID))
            Domoticz.Debug("MODBUS DEBUG - RESPONSE: " + str(data))
          except:
            Domoticz.Error("Modbus error 0x01 communicating! (RTU/ASCII/RTU over TCP), check your settings!")
            Devices[1].Update(1, "0") # Set value to 0 (error)


        ########################################
        # GET DATA - pymodbusTCP: TCP/IP
        ########################################
        if (self.Domoticz_Setting_Communication_Mode == "tcpip"):
          try:
            # Function to execute
            if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
            if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
            if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
            if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
            Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data))
          except:
            Domoticz.Error("Modbus error 0x02 communicating! (TCP/IP), wait 1 sec and retry..." + str(data))
            Devices[1].Update(1, "0") # Set value to 0 (error)
            # fout bij ophalen data, wacht 1 seconde en probeer opnieuw
            time.sleep(1)
            try:
                # Function to execute
                if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data))
            except:
                Domoticz.Error("Modbus error 0x21 communicating! (TCP/IP), check your settings! " + str(data))
                Devices[1].Update(1, "0") # Set value to 0 (error)
            # einde aanpassing #

        ########################################
        # DECODE DATA TYPE
        ########################################
        # pymodbus (RTU/ASCII/RTU over TCP) will reponse in ARRAY, no matter what values read e.g. MODBUS DEBUG RESPONSE: [2] = data.registers
        # pymodbusTCP (TCP/IP) will give the value back e.g. MODBUS DEBUG RESPONSE: [61, 44] = data
        if (self.Domoticz_Setting_Communication_Mode == "rtu" or self.Domoticz_Setting_Communication_Mode == "ascii" or self.Domoticz_Setting_Communication_Mode == "rtutcp"):
          try:
            Domoticz.Debug("MODBUS DEBUG - VALUE before conversion: " + str(data.registers[0]))
            # Added option to swap bytes (little endian)
            if (self.Domoticz_Setting_Data_Type == "int16s" or self.Domoticz_Setting_Data_Type == "uint16s"):
              decoder = BinaryPayloadDecoder.fromRegisters(data.registers, byteorder=Endian.Little, wordorder=Endian.Big)
            # Added option to swap words (little endian)
            elif (self.Domoticz_Setting_Data_Type == "int32s" or self.Domoticz_Setting_Data_Type == "uint32s" or self.Domoticz_Setting_Data_Type == "int64s" or self.Domoticz_Setting_Data_Type == "uint64s" 
                  or self.Domoticz_Setting_Data_Type == "float32s" or self.Domoticz_Setting_Data_Type == "float64s"):
              decoder = BinaryPayloadDecoder.fromRegisters(data.registers, byteorder=Endian.Big, wordorder=Endian.Little)
            # Otherwise always big endian
            else:
              decoder = BinaryPayloadDecoder.fromRegisters(data.registers, byteorder=Endian.Big, wordorder=Endian.Big)
          except:
            Domoticz.Error("Modbus error 0x04 decoding or received no data (RTU/ASCII/RTU over TCP)!, check your settings!")
            Devices[1].Update(1, "0") # Set value to 0 (error)

        if (self.Domoticz_Setting_Communication_Mode == "tcpip"):
          try:
            Domoticz.Debug("MODBUS DEBUG - VALUE before conversion: " + str(data))
            #value = data[0]
            # Added option to swap bytes (little endian)
            if (self.Domoticz_Setting_Data_Type == "int16s" or self.Domoticz_Setting_Data_Type == "uint16s"):
              decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Little, wordorder=Endian.Big)
            # Added option to swap words (little endian)
            elif (self.Domoticz_Setting_Data_Type == "int32s" or self.Domoticz_Setting_Data_Type == "uint32s" or self.Domoticz_Setting_Data_Type == "int64s" or self.Domoticz_Setting_Data_Type == "uint64s" 
                  or self.Domoticz_Setting_Data_Type == "float32s" or self.Domoticz_Setting_Data_Type == "float64s"):
              decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Little)
            # Otherwise always big endian
            else:
              decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
          except:
            Domoticz.Error("Modbus error 0x05 decoding or received no data (TCP/IP)!, check your settings!, wait 1 sec and retry... " + str(data))
            Devices[1].Update(1, "0") # Set value to 0 (error)
            # fout bij ophalen data, wacht 1 seconde en probeer opnieuw
            time.sleep(1)
            try:
                # Function to execute
                if (self.Domoticz_Setting_Modbus_Function == "1"): data = client.read_coils(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "2"): data = client.read_discrete_inputs(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "3"): data = client.read_holding_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                if (self.Domoticz_Setting_Modbus_Function == "4"): data = client.read_input_registers(int(self.Domoticz_Setting_Register_Number), self.Register_Count)
                Domoticz.Debug("MODBUS DEBUG RESPONSE: " + str(data))
            except:
                Domoticz.Error("Modbus error 0x51 communicating! (TCP/IP), check your settings! " + str(data))
                Devices[1].Update(1, "0") # Set value to 0 (error)
            try:
                Domoticz.Debug("MODBUS DEBUG - VALUE before conversion: " + str(data))
                #value = data[0]
                # Added option to swap bytes (little endian)
                if (self.Domoticz_Setting_Data_Type == "int16s" or self.Domoticz_Setting_Data_Type == "uint16s"):
                    decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Little, wordorder=Endian.Big)
                    # Added option to swap words (little endian)
                elif (self.Domoticz_Setting_Data_Type == "int32s" or self.Domoticz_Setting_Data_Type == "uint32s" or self.Domoticz_Setting_Data_Type == "int64s" or self.Domoticz_Setting_Data_Type == "uint64s" or self.Domoticz_Setting_Data_Type == "float32s" or self.Domoticz_Setting_Data_Type == "float64s"):
                    decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Little)
                    # Otherwise always big endian
                else:
                    decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
            except:
                Domoticz.Error("Modbus error 0x52 decoding or received no data (TCP/IP)!, check your settings! " + str(data))
                Devices[1].Update(1, "0") # Set value to 0 (error)
            # einde aanpassing #

        ########################################
        # DECODE DATA VALUE
        ########################################
        try:
          if (self.Domoticz_Setting_Data_Type == "noco"): value = data.registers[0]
          if (self.Domoticz_Setting_Data_Type == "bool"): value = bool(data.registers[0])
          if (self.Domoticz_Setting_Data_Type == "int8LSB"):
            ignored = decoder.skip_bytes(1)
            value = decoder.decode_8bit_int()
          if (self.Domoticz_Setting_Data_Type == "int8MSB"): value = decoder.decode_8bit_int()
          if (self.Domoticz_Setting_Data_Type == "int16"): value = decoder.decode_16bit_int()
          if (self.Domoticz_Setting_Data_Type == "int16s"): value = decoder.decode_16bit_int()
          if (self.Domoticz_Setting_Data_Type == "int32"): value = decoder.decode_32bit_int()
          if (self.Domoticz_Setting_Data_Type == "int32s"): value = decoder.decode_32bit_int()
          if (self.Domoticz_Setting_Data_Type == "int64"): value = decoder.decode_64bit_int()
          if (self.Domoticz_Setting_Data_Type == "int64s"): value = decoder.decode_64bit_int()
          if (self.Domoticz_Setting_Data_Type == "uint8LSB"):
            ignored = decoder.skip_bytes(1)
            value = decoder.decode_8bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint8MSB"): value = decoder.decode_8bit_uint()   
          if (self.Domoticz_Setting_Data_Type == "uint16"): value = decoder.decode_16bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint16s"): value = decoder.decode_16bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint32"): value = decoder.decode_32bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint32s"): value = decoder.decode_32bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint64"): value = decoder.decode_64bit_uint()
          if (self.Domoticz_Setting_Data_Type == "uint64s"): value = decoder.decode_64bit_uint()
          if (self.Domoticz_Setting_Data_Type == "float32"): value = decoder.decode_32bit_float()
          if (self.Domoticz_Setting_Data_Type == "float32s"): value = decoder.decode_32bit_float()
          if (self.Domoticz_Setting_Data_Type == "float64"): value = decoder.decode_64bit_float()
          if (self.Domoticz_Setting_Data_Type == "float64s"): value = decoder.decode_64bit_float()
          if (self.Domoticz_Setting_Data_Type == "string2"): value = decoder.decode_string(2)
          if (self.Domoticz_Setting_Data_Type == "string4"): value = decoder.decode_string(4)
          if (self.Domoticz_Setting_Data_Type == "string6"): value = decoder.decode_string(6)
          if (self.Domoticz_Setting_Data_Type == "string8"): value = decoder.decode_string(8)			
          
		  # Divide the value (decimal)
          if (self.Domoticz_Setting_Divide_Value == "div0"): value = str(value)
          if (self.Domoticz_Setting_Divide_Value == "div10"): value = str(round(value / 10, 1))
          if (self.Domoticz_Setting_Divide_Value == "div100"): value = str(round(value / 100, 2))
          if (self.Domoticz_Setting_Divide_Value == "div1000"): value = str(round(value / 1000, 3))
          if (self.Domoticz_Setting_Divide_Value == "div10000"): value = str(round(value / 10000, 4))
          Domoticz.Debug("MODBUS DEBUG - VALUE after conversion: " + str(value))
          Devices[1].Update(1, value) # Update value
		  
        except:
          Domoticz.Error("Modbus error 0x06 decoding or received no data!, check your settings!" + str(data))
          Devices[1].Update(1, "0") # Set value to 0 (error)

global _plugin
_plugin = BasePlugin()

def onStart():
    global _plugin
    _plugin.onStart()

def onStop():
    global _plugin
    _plugin.onStop()

def onConnect(Connection, Status, Description):
    global _plugin
    _plugin.onConnect(Connection, Status, Description)

def onMessage(Connection, Data, Status, Extra):
    global _plugin
    _plugin.onMessage(Connection, Data, Status, Extra)

def onCommand(Unit, Command, Level, Hue):
    global _plugin
    _plugin.onCommand(Unit, Command, Level, Hue)

def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
    global _plugin
    _plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)

def onDisconnect(Connection):
    global _plugin
    _plugin.onDisconnect(Connection)

def onHeartbeat():
    global _plugin
    _plugin.onHeartbeat()

    # Generic helper functions
def DumpConfigToLog():
    for x in Parameters:
        if Parameters[x] != "":
            Domoticz.Debug( "'" + x + "':'" + str(Parameters[x]) + "'")
    Domoticz.Debug("Device count: " + str(len(Devices)))
    for x in Devices:
        Domoticz.Debug("Device:           " + str(x) + " - " + str(Devices[x]))
        Domoticz.Debug("Device ID:       '" + str(Devices[x].ID) + "'")
        Domoticz.Debug("Device Name:     '" + Devices[x].Name + "'")
        Domoticz.Debug("Device nValue:    " + str(Devices[x].nValue))
        Domoticz.Debug("Device sValue:   '" + Devices[x].sValue + "'")
        Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
    return
User avatar
waltervl
Posts: 5153
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2024.7
Location: NL
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by waltervl »

You cannot send Null, None or 'Invalid' to domoticz.
Just dont send it.... Wait until the next valid value is received, not being Null or None.
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
User avatar
Domoberry
Posts: 116
Joined: Tuesday 30 May 2017 19:00
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by Domoberry »

Re: "Did you install the module with sudo? As this is a known solution for error". Thanks, this pointed my in the right direction!
Module pymodbus (which is required by this modbus plugin) for some reason was no longer present on my RPI after the Bullseye update. So installed the latest version pymodbus 3.2.1 (see https://github.com/pymodbus-dev/pymodbus). However, with this installed, Domoticz immediately fails after startup. After uninstalling pymodbus 3.2.1 again, Domoticz 2023.2 would run again on Bullseye. I'm not sure, but possibly the modbus plugin cannot function with pymodbus 3.2.1, causing Domoticz to fail when it starts the modbus plugin.

Would it make sense to deliberately install pymodbus 2.5.3 instead? This was the version I was using OK-isch on Domoticz 2023.1 while still on Buster.
T1NBK
Posts: 8
Joined: Wednesday 13 December 2017 21:08
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Python plugin: Modbus RTU / ASCII / TCP/IP

Post by T1NBK »

waltervl wrote: Monday 20 March 2023 13:48 You cannot send Null, None or 'Invalid' to domoticz.
Just dont send it.... Wait until the next valid value is received, not being Null or None.
Thanks for the answer.

However, now I'm even more confused.
Judging on the code Sebastiaan Ebeltjes does seem to know how this should work.

I find the next line often in the code:

Code: Select all

Devices[1].Update(1, "0") # Set value to 0 (error)
Why would the author make such a basic 'mistake'? What is the reason for this solution?
Seems to me like he uses it as an sort of exit() or break function. And in that case: what would be the appropriate exit or break function?
I find that python uses exit() or sys.exit() but does this work or does it exit the whole domoticz python script thing?
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest