Reputation: 645
I have a simple modbus device (Ebyte MA02-XACX0440) that I'm trying to learn how to work with. Using a third party GUI called serial port monitor (www.serial-port-monitor.org) I was able to more or less "stumble upon" the proper hexadecimal inputs needed to turn a discrete output on and then off. I don't understand these terms of unit, address, coils, registers, etc. and how it all relates when it comes to the hexadecimal aspect. To break down what I think I know so far (below is the hexadecimal string that will turn the first discrete output 'ON'):
0x20 0x05 0x00 0x00 0xFF 0x00 0x8A 0x8B
I know 0x20 is the device address in hex. The documentation of the Ebyte device stipulates that the default hardware address is '31' and that the first software address is '1'. If I understand that correctly, that means my first physical modbus device on the line has an address of '32', and if I were to put additional modbus devices on the line (either RS485 or TCP(?)) that the next device would be '33' and so on.
I know that the next byte 0x05 is 'write coil'.
I don't know what the next two bytes of '0x00' and '0x00' refer to.
The next two bytes are essentially on/off with '0xFF00' being 'ON' and 0x0000 being 'OFF'.
The final two bytes are simply the CRC checksum.
So up to this point I can get my DO (Discrete Output) to turn on, open up, and light up an LED as a simple proof of concept. Now when I take that approach over to using the pymodbus library (my ultimate goal), things don't seem to line up.
I am able to connect to my device using the pymodbus REPL using
pymodbus.console serial --baudrate 9600 --bytesize 8 --parity N --stopbits 1 --port /dev/cu.usbserial-AH01F4EN
I can confirm that the device is connected:
----------------------------------------------------------------------------
__________ _____ .___ __________ .__
\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | |
| ___< | |/ \ / \ / _ \ / __ | | _// __ \\____ \| |
| | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__
|____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/
\/ \/ \/ \/ \/ \/|__|
v1.3.0 - [pymodbus, version 2.5.3]
----------------------------------------------------------------------------
> client.connect
true
However when I attempt to run the same commands that I did earlier with serial monitor, I'm having difficulty mapping the hexadecimal values to the integers expected by the 'client.write_coil' command in pymodbus:
> client.write_coil address=32 value=255 unit=1
{
"original_function_code": "5 (0x5)",
"error": "[Input/Output] No Response received from the remote unit/Unable to decode response"
}
What is meant by address? Is this a coil/register address or a device address? For 'value' I put '255' as that would be all bits flipped to a '1' thinking that would be considered 'ON', and that a value of '0' would be off. I also tried '65280', which is what 0xFF00 would be in decimal. What is meant by a unit? Is that the previous value I determined to be '32' to target the specific device on the line? Or is it possible that the address would be '31' (which is the hardware address) and then the unit address is '1', making the overall 'address' 32?
EDIT: This is shown in the 'help' of the client.write_coil command, but still doesn't help me figure out how to put the right pieces in the right places. I don't know what a 'coil offset' is and how that relates to the discrete outputs. I don't see anything mapped to the hexadecimal string that would be considered a 'coil offset'. This is being provided for context to those that may know how to properly form the request.
I also sometimes get this error, and it's not consistent. If it was a permissions issue one would think you'd get it every single time? I don't think it makes sense to be either changing the permissions of the file indicated or to run the pymodbus server with root permissions.
Unhandled exception in event loop:
File "/usr/local/Cellar/[email protected]/3.9.7/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/input/vt100.py", line 170, in callback_wrapper
callback()
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/application/application.py", line 708, in read_from_input
self.key_processor.process_keys()
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/key_binding/key_processor.py", line 271, in process_keys
self._process_coroutine.send(key_press)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/key_binding/key_processor.py", line 186, in _process
self._call_handler(matches[-1], key_sequence=buffer[:])
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/key_binding/key_processor.py", line 321, in _call_handler
handler.call(event)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/key_binding/key_bindings.py", line 124, in call
result = self.handler(event)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/shortcuts/prompt.py", line 798, in _accept_input
self.default_buffer.validate_and_handle()
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/buffer.py", line 1877, in validate_and_handle
self.append_to_history()
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/buffer.py", line 1385, in append_to_history
self.history.append_string(self.text)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/history.py", line 74, in append_string
self.store_string(string)
File "/usr/local/lib/python3.9/site-packages/prompt_toolkit/history.py", line 295, in store_string
with open(self.filename, "ab") as f:
Exception [Errno 13] Permission denied: '../.pymodhis'
{ress ENTER to continue...
Upvotes: 0
Views: 6840
Reputation: 18280
The Modbus specs describe the protocol and are worth a look. I use this online Modbus parser when I want to quickly parse a command; it's output for the string of bytes you give (20 05 00 00 FF 00 8A 8B
) is:
Part of Data Package | Description | Value |
---|---|---|
20 | Slave address | 0x20 (32) |
05 | Function code | 0x05 (5) - Write Single Coil |
00 00 | Output address | Physical: 0x0000 (0) Logical: 0x0001 (1) |
FF 00 | Output value | On |
8A 8B | CRC | 0x8A8B (35467) |
The slave address indicates which device on the bus you wish to communicate with. This is set on the device (you cannot just add devices and expect things to work). The method used to set slave ID's differs from device to device (some have a utility to do this, some use switches or settings via their own user interface, and some have this hard coded). Your device (Ebyte MA02-XACX0440) defaults to 32 but this can be changed using the DIP switch on the device (this is covered in the manual).
You are using function code 5 - 'Write Single Coil'. Coils are bits so can be on or off.
The 'output address' indicates which coil on the device you wish to write to. The meaning of this address varies from device to device (generally there will be a table in the documentation that explains this). For your device this is in table 7.1 ("Register list") in the manual.
The value is what to write. For the 'write single coil' function this must be one of two values:
value | meaning |
---|---|
0x0000 | Off |
0xFF00 | On |
All other values are invalid. However many libraries (incluing pymodbus) will handle this detail for you allowing you to pass True
/False
.
Putting this all together you will need something like:
client.write_coil(0, True, unit=32)
Note: You have asked a lot of questions so this may not fully answer them but hopefully points you in the right direction (way too much info to put into the comments!).
Upvotes: 0