---
# System prepended metadata

title: Modbus with pymodbus
tags: [notes, ' RT_blog', ' pymodbus', ' modbus', ' python']

---

---
tags: notes, RT_blog, python, modbus, pymodbus
---

# Modbus with pymodbus
Hence I can not understand well the usage from the [official document](https://pymodbus.readthedocs.io/en/latest/index.html), here is the note for pymodbus. This note is written with `pymodbus==2.3.0`. You can also visit [the github page](https://github.com/riptideio/pymodbus) for more information. It's okay that you don't have a modbus slave device, such as PLC. You can use a simlation software as an modbus slave, see [modbus: Simulation Tools](https://hackmd.io/LBIFwUj_QNqCT7wg2FtCkw?view).

To install the library:
```
pip install pymodbus
```

## Modbus Simple Introduction
- Modbus point type
  Note that the table below shows the maximum range defined by [modbus official spec](https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf). However, in most applications, PDU addresses are no bigger then 1000, and the absolute addresses are denoted as 00001, 10001, 30001 and 40001.
  | Modbus point type    | Absolute Address | PDU Address | Data type              | Master Access |
  | -------------------- |:---------------- | ----------- | ---------------------- | ------------- |
  | DO(Coil Status)      | 000001           | 0~65535     | Boolean                | Read/Wrtie    |
  | DI(Input Status)     | 100001           | 0~65535     | Boolean                | Read only     |
  | AI(Input Register)   | 300001           | 0~65535     | unsigned word(2 bytes) | Read only     |
  | AO(Holding Register) | 400001           | 0~65535     | unsigned word(2 bytes) | Read/Write    |
  > For AI and AO, each register is 1 word = 16 bits = 2 bytes.
  > You have to specify a modbus point type when accessing data by PDU address, which is the case of functions of pymodbus.

- Funtion code
  The library `pymodbus` already handles the function code of modbus well, but it's good to know there is such a thing in case you may have to sniff the packages for debugging.
  | Dec (Hex) | Description                                    |
  | --------- | ---------------------------------------------- |
  | 01 (0x01) | Read DO(Up to 2000 contiguous bits)            |
  | 02 (0x02) | Read DI(Up to 2000 contiguous bits)            |
  | 03 (0x03) | Read AO(Up to 125 contiguous words)            |
  | 04 (0x04) | Read AI(Up to 125 contiguous words)            |
  | 05 (0x05) | Write single DO                                |
  | 15 (0x06) | Write single AO                                |
  | 16 (0x10) | Write multiple DO(Up to 1968 contiguous bits)  |
  | 22 (0x16) | Write multiple AO(Up to 120  contiguous words) | 
  > For AI and AO, each register is 1 word = 16 bits = 2 bytes.

- Exception code
  When a modbus master(client) send a unexpected requests to the modbus slave(server), the slave would return the request with exception code.
  | Exception code | Represents                 | Description                                                                    |
  | -------------- | -------------------------- | ------------------------------------------------------------------------------ |
  | 01             | illegal function code      |                                                                                |
  | 02             | illegal data address       |                                                                                |
  | 03             | illegal data value         |                                                                                |
  | 04             | slave device failure       |                                                                                |
  | 05             | acknowledge                | The requests is accepted, but the slave requires much time to finish the task. |
  | 06             | slave device busy          |                                                                                |
  | 07             | negative acknowledge       |                                                                                |
  | 08             | memory parity error        |                                                                                |
  | 10, 0x0A       | gateway path unavailable   |                                                                                |
  | 11, 0x0B       | gatewat target no response |                                                                                |
  [Exception code reference](https://www.simplymodbus.ca/exceptions.htm)







## Finally pymodbus
Initialize a TCP connection:
```python
from pymodbus.client.sync import ModbusTcpClient
client = ModbusTcpClient(host='192.168.87.87', port=123)
```
or RTU(serial)
```python
from pymodbus.client.sync import ModbusSerialClient
client = ModbusSerialClient(method='rtu', port='COM8')
```
`ModbusSerialClient()` parameters:
  - `method`: The method to use for connection (`ascii`, `rtu`, `binary`)
  - `port`: The serial port to attach to
  - `stopbits`: The number of stop bits to use
  - `bytesize`: The bytesize of the serial messages
  - `parity`: Which kind of parity to use
  - `baudrate`: The baud rate to use for the serial device
  - `timeout`: The timeout between serial requests (default 3s)
  - `strict`:  Use Inter char timeout for baudrates <= 19200 (adhere to modbus standards)
Make a connection.:
```python
# Once it's connected, this function always returns true even the physical connection is dead.
# It returns false after .close()
client.connect()  # returns True if connection established
```
### Read from the connected slave
Read 2 registers from 400001:
```python
res = client.read_holding_registers(address=0, count=2, unit=1)
res.registers # shows the result, type: list
```
- Reading functions:
  | function name               | aka                  |
  | --------------------------- | -------------------- |
  | `.read_coils`               | (DO)Coil Status      |
  | `.read_discrete_inputs()`   | (DI)Input Status     |
  | `.read_holding_registers()` | (AI)Input Register   |
  | `.read_input_registers()`   | (AO)Holding Register |
- Parameters
  Four read funcions listed above have same parameters:
  - `address`: The starting address to read from. Note that this is absolute address. For example, an address `400001` on a PLC manual is belong to AI. You should read it with relative address by removing leading `4` and ubstract by `1`. Here are few examples:
    | Absolute | Relative |
    | --------:| --------:|
    |    40001 |        0 |
    |   400001 |        0 |
    |    30021 |       20 |
  - `count`: The number of registers to read
  - `unit`: The slave unit(device ID) this request is targeting
- Return:
  - `.bits`: A list of reading result. Only available for DO and DI.
  - `.registers`: A list of reading result. Only available for AI and AO. 
  - `.isError()`: True if error.
  - `.message`: The error meesage. Only availabel if `.isError()` returns true.

### Write to the connected slave
- `.write_coil()`
- `.write_coils()`
- `.write_register()`
- `.write_registers()`
- Return

### Example
Read and writing coil status(DO).
```python=
from pymodbus.client.sync import ModbusTcpClient
import random
import time

SERVER_IP = 'localhost' # aka modbus slave
SERVER_PORT = 502
ADDRESS_START  = 0000
ADDRESS_LENGTH = 10
DEVICE_ID = 1


client = ModbusTcpClient(host=SERVER_IP, port=SERVER_PORT)
if not client.connect() :  # .connect() returns True if connection established
  print("[Error] Fail to connect to modbus slave %s:%d." % (SERVER_IP, SERVER_PORT))
  exit()

# client.read_coils(address, count, unit)
t1 = time.time()
coil_response = client.read_coils(address=ADDRESS_START, count=ADDRESS_LENGTH, unit=DEVICE_ID) # the unit is the device ID
t2 = time.time()
print("[INFO] Read time cost {:7f} second.".format(t2-t1))
if coil_response.isError():  # .isError() returns True if the modbus slave returns exception
  print("[Error] The modbus slave connected but exception code returned: %d." % coil_response.exception_code)
else:
  # coil_response has attribute .bits if no error return from the modbus slave
  print("[INFO] Read success, coil response:", coil_response.bits)

# write an random value
print("[INFO] Now writing a random boolean value to address= {}.".format(ADDRESS_START))
rand_value = random.choice([True,False])
coil_response = client.write_coil(address=ADDRESS_START, value=rand_value, unit=DEVICE_ID)
if coil_response.isError():
  print("[Error] The modbus slave connected but exception code returned: %d." % coil_response.exception_code)
else:
  coil_response = client.read_coils(address=ADDRESS_START, count=1, unit=DEVICE_ID)
  if not coil_response.isError() and coil_response.bits[0] == rand_value:
    print("[INFO] Write success. The value read is the same as the value that just wrote.")
    print("[INFO] Coil response:", coil_response.bits)

client.close()
```

