pamoja.modbus
Idiomatic Modbus RTU facade.
Modbus over RS485 is what cheap industrial sensing speaks: energy meters, soil
probes, water-quality transmitters, pump controllers. Each request builder here
returns a complete frame with its CRC, ready to write to a port, and a reply comes
back through parse_frame() as an object that reads its own values.
1"""Idiomatic Modbus RTU facade. 2 3Modbus over RS485 is what cheap industrial sensing speaks: energy meters, soil 4probes, water-quality transmitters, pump controllers. Each request builder here 5returns a complete frame with its CRC, ready to write to a port, and a reply comes 6back through :func:`parse_frame` as an object that reads its own values. 7""" 8 9from __future__ import annotations 10 11import enum 12from typing import Sequence 13 14from pamoja._native import ModbusFrame 15from pamoja._native import modbus_crc16 as _crc16 16from pamoja._native import modbus_parse_frame as _parse_frame 17from pamoja._native import modbus_raw as _raw 18from pamoja._native import modbus_read_coils as _read_coils 19from pamoja._native import modbus_read_discrete_inputs as _read_discrete_inputs 20from pamoja._native import modbus_read_holding_registers as _read_holding_registers 21from pamoja._native import ( 22 modbus_read_holding_registers_reply as _read_holding_registers_reply, 23) 24from pamoja._native import modbus_read_input_registers as _read_input_registers 25from pamoja._native import ( 26 modbus_read_input_registers_reply as _read_input_registers_reply, 27) 28from pamoja._native import modbus_write_multiple_coils as _write_multiple_coils 29from pamoja._native import modbus_write_multiple_registers as _write_multiple_registers 30from pamoja._native import modbus_write_single_coil as _write_single_coil 31from pamoja._native import modbus_write_single_register as _write_single_register 32 33__all__ = [ 34 "Exception_", 35 "Function", 36 "ModbusFrame", 37 "crc16", 38 "parse_frame", 39 "raw", 40 "read_coils", 41 "read_discrete_inputs", 42 "read_holding_registers", 43 "read_holding_registers_reply", 44 "read_input_registers", 45 "read_input_registers_reply", 46 "write_multiple_coils", 47 "write_multiple_registers", 48 "write_single_coil", 49 "write_single_register", 50] 51 52 53class Function(int, enum.Enum): 54 """The function codes this SDK names, as they appear on the wire.""" 55 56 #: Read one or more coils (read/write bits). 57 READ_COILS = 0x01 58 #: Read one or more discrete inputs (read-only bits). 59 READ_DISCRETE_INPUTS = 0x02 60 #: Read one or more holding registers (read/write 16-bit words). 61 READ_HOLDING_REGISTERS = 0x03 62 #: Read one or more input registers (read-only 16-bit words). 63 READ_INPUT_REGISTERS = 0x04 64 #: Write a single coil. 65 WRITE_SINGLE_COIL = 0x05 66 #: Write a single holding register. 67 WRITE_SINGLE_REGISTER = 0x06 68 #: Write a contiguous block of coils. 69 WRITE_MULTIPLE_COILS = 0x0F 70 #: Write a contiguous block of holding registers. 71 WRITE_MULTIPLE_REGISTERS = 0x10 72 73 74class Exception_(int, enum.Enum): 75 """The reason a device gives for refusing a request. 76 77 Named with a trailing underscore because ``Exception`` is a Python builtin. 78 """ 79 80 #: The function code is not allowed for this device. 81 ILLEGAL_FUNCTION = 0x01 82 #: The data address is not allowed for this device. 83 ILLEGAL_DATA_ADDRESS = 0x02 84 #: A value in the request is not allowed for this device. 85 ILLEGAL_DATA_VALUE = 0x03 86 #: The device failed while serving the request. 87 SERVER_DEVICE_FAILURE = 0x04 88 #: The device accepted a long-running request and is still processing it. 89 ACKNOWLEDGE = 0x05 90 #: The device is busy with a long-running request; retry later. 91 SERVER_DEVICE_BUSY = 0x06 92 #: The device detected a parity error in its memory. 93 MEMORY_PARITY_ERROR = 0x08 94 #: A gateway could not route the request to the target path. 95 GATEWAY_PATH_UNAVAILABLE = 0x0A 96 #: A gateway reached the target device but got no response. 97 GATEWAY_TARGET_FAILED_TO_RESPOND = 0x0B 98 99 100def crc16(data: bytes) -> int: 101 """Compute the CRC-16/MODBUS that every RTU frame ends with. 102 103 :param data: The frame contents, without the trailing checksum. 104 :returns: The checksum. 105 """ 106 return _crc16(bytes(data)) 107 108 109def read_coils(address: int, start: int, count: int) -> bytes: 110 """Build a read-coils request (function ``0x01``). 111 112 :param address: The unit address to ask. 113 :param start: The address of the first coil. 114 :param count: How many coils to read. 115 :returns: The frame to send. 116 """ 117 return _read_coils(address, start, count) 118 119 120def read_discrete_inputs(address: int, start: int, count: int) -> bytes: 121 """Build a read-discrete-inputs request (function ``0x02``). 122 123 :param address: The unit address to ask. 124 :param start: The address of the first input. 125 :param count: How many inputs to read. 126 :returns: The frame to send. 127 """ 128 return _read_discrete_inputs(address, start, count) 129 130 131def read_holding_registers_reply(address: int, values: list[int]) -> bytes: 132 """Build the reply a device sends to a read-holding-registers request. 133 134 This is the answering half of :func:`read_holding_registers`, so a client can be 135 written and tested against what a device sends without a device on the line. 136 137 :param address: The unit address the reply comes from. 138 :param values: The register values the device reports, in address order. 139 :returns: The frame the device would send. 140 :raises PamojaError: If there are no values, or more than one frame can carry. 141 """ 142 return _read_holding_registers_reply(address, values) 143 144 145def read_input_registers_reply(address: int, values: list[int]) -> bytes: 146 """Build the reply a device sends to a read-input-registers request. 147 148 :param address: The unit address the reply comes from. 149 :param values: The register values the device reports, in address order. 150 :returns: The frame the device would send. 151 :raises PamojaError: If there are no values, or more than one frame can carry. 152 """ 153 return _read_input_registers_reply(address, values) 154 155 156def read_holding_registers(address: int, start: int, count: int) -> bytes: 157 """Build a read-holding-registers request (function ``0x03``). 158 159 :param address: The unit address to ask. 160 :param start: The address of the first register. 161 :param count: How many registers to read. 162 :returns: The frame to send. 163 """ 164 return _read_holding_registers(address, start, count) 165 166 167def read_input_registers(address: int, start: int, count: int) -> bytes: 168 """Build a read-input-registers request (function ``0x04``). 169 170 :param address: The unit address to ask. 171 :param start: The address of the first register. 172 :param count: How many registers to read. 173 :returns: The frame to send. 174 """ 175 return _read_input_registers(address, start, count) 176 177 178def write_single_coil(address: int, coil: int, on: bool) -> bytes: 179 """Build a write-single-coil request (function ``0x05``). 180 181 :param address: The unit address to write to. 182 :param coil: The coil address. 183 :param on: The state to write. 184 :returns: The frame to send. 185 """ 186 return _write_single_coil(address, coil, on) 187 188 189def write_single_register(address: int, register: int, value: int) -> bytes: 190 """Build a write-single-register request (function ``0x06``). 191 192 :param address: The unit address to write to. 193 :param register: The register address. 194 :param value: The 16-bit value to write. 195 :returns: The frame to send. 196 """ 197 return _write_single_register(address, register, value) 198 199 200def write_multiple_registers(address: int, start: int, values: Sequence[int]) -> bytes: 201 """Build a write-multiple-registers request (function ``0x10``). 202 203 :param address: The unit address to write to. 204 :param start: The address of the first register. 205 :param values: The 16-bit values, at most 123 of them. 206 :returns: The frame to send. 207 :raises PamojaError: If there are no values, or more than one request carries. 208 """ 209 return _write_multiple_registers(address, start, list(values)) 210 211 212def write_multiple_coils(address: int, start: int, values: Sequence[bool]) -> bytes: 213 """Build a write-multiple-coils request (function ``0x0F``). 214 215 :param address: The unit address to write to. 216 :param start: The address of the first coil. 217 :param values: One state per coil, at most 1968 of them. 218 :returns: The frame to send. 219 :raises PamojaError: If there are no values, or more than one request carries. 220 """ 221 return _write_multiple_coils(address, start, [bool(value) for value in values]) 222 223 224def raw(address: int, function_code: int, data: bytes) -> bytes: 225 """Build a request from a raw function code and data. 226 227 This is the escape hatch for the function codes this SDK does not name. 228 229 :param address: The unit address to send to. 230 :param function_code: The function code byte. 231 :param data: The bytes that follow it, used verbatim. 232 :returns: The frame to send. 233 :raises PamojaError: If the data is longer than a PDU may be. 234 """ 235 return _raw(address, function_code, bytes(data)) 236 237 238def parse_frame(data: bytes) -> ModbusFrame: 239 """Parse a received RTU frame, verifying its CRC. 240 241 :param data: The frame as it came off the wire, checksum included. 242 :returns: The validated frame, which reads its own registers and coils. 243 :raises PamojaError: If the frame is truncated, oversized, or its CRC does not 244 match its contents. 245 """ 246 return _parse_frame(bytes(data))
75class Exception_(int, enum.Enum): 76 """The reason a device gives for refusing a request. 77 78 Named with a trailing underscore because ``Exception`` is a Python builtin. 79 """ 80 81 #: The function code is not allowed for this device. 82 ILLEGAL_FUNCTION = 0x01 83 #: The data address is not allowed for this device. 84 ILLEGAL_DATA_ADDRESS = 0x02 85 #: A value in the request is not allowed for this device. 86 ILLEGAL_DATA_VALUE = 0x03 87 #: The device failed while serving the request. 88 SERVER_DEVICE_FAILURE = 0x04 89 #: The device accepted a long-running request and is still processing it. 90 ACKNOWLEDGE = 0x05 91 #: The device is busy with a long-running request; retry later. 92 SERVER_DEVICE_BUSY = 0x06 93 #: The device detected a parity error in its memory. 94 MEMORY_PARITY_ERROR = 0x08 95 #: A gateway could not route the request to the target path. 96 GATEWAY_PATH_UNAVAILABLE = 0x0A 97 #: A gateway reached the target device but got no response. 98 GATEWAY_TARGET_FAILED_TO_RESPOND = 0x0B
The reason a device gives for refusing a request.
Named with a trailing underscore because Exception is a Python builtin.
54class Function(int, enum.Enum): 55 """The function codes this SDK names, as they appear on the wire.""" 56 57 #: Read one or more coils (read/write bits). 58 READ_COILS = 0x01 59 #: Read one or more discrete inputs (read-only bits). 60 READ_DISCRETE_INPUTS = 0x02 61 #: Read one or more holding registers (read/write 16-bit words). 62 READ_HOLDING_REGISTERS = 0x03 63 #: Read one or more input registers (read-only 16-bit words). 64 READ_INPUT_REGISTERS = 0x04 65 #: Write a single coil. 66 WRITE_SINGLE_COIL = 0x05 67 #: Write a single holding register. 68 WRITE_SINGLE_REGISTER = 0x06 69 #: Write a contiguous block of coils. 70 WRITE_MULTIPLE_COILS = 0x0F 71 #: Write a contiguous block of holding registers. 72 WRITE_MULTIPLE_REGISTERS = 0x10
The function codes this SDK names, as they appear on the wire.
A received Modbus RTU frame whose CRC has been verified.
The exception code a device reported, or None when the frame is not an
exception response.
101def crc16(data: bytes) -> int: 102 """Compute the CRC-16/MODBUS that every RTU frame ends with. 103 104 :param data: The frame contents, without the trailing checksum. 105 :returns: The checksum. 106 """ 107 return _crc16(bytes(data))
Compute the CRC-16/MODBUS that every RTU frame ends with.
Parameters
- data: The frame contents, without the trailing checksum. :returns: The checksum.
239def parse_frame(data: bytes) -> ModbusFrame: 240 """Parse a received RTU frame, verifying its CRC. 241 242 :param data: The frame as it came off the wire, checksum included. 243 :returns: The validated frame, which reads its own registers and coils. 244 :raises PamojaError: If the frame is truncated, oversized, or its CRC does not 245 match its contents. 246 """ 247 return _parse_frame(bytes(data))
Parse a received RTU frame, verifying its CRC.
Parameters
- data: The frame as it came off the wire, checksum included. :returns: The validated frame, which reads its own registers and coils.
Raises
- PamojaError: If the frame is truncated, oversized, or its CRC does not match its contents.
225def raw(address: int, function_code: int, data: bytes) -> bytes: 226 """Build a request from a raw function code and data. 227 228 This is the escape hatch for the function codes this SDK does not name. 229 230 :param address: The unit address to send to. 231 :param function_code: The function code byte. 232 :param data: The bytes that follow it, used verbatim. 233 :returns: The frame to send. 234 :raises PamojaError: If the data is longer than a PDU may be. 235 """ 236 return _raw(address, function_code, bytes(data))
Build a request from a raw function code and data.
This is the escape hatch for the function codes this SDK does not name.
Parameters
- address: The unit address to send to.
- function_code: The function code byte.
- data: The bytes that follow it, used verbatim. :returns: The frame to send.
Raises
- PamojaError: If the data is longer than a PDU may be.
110def read_coils(address: int, start: int, count: int) -> bytes: 111 """Build a read-coils request (function ``0x01``). 112 113 :param address: The unit address to ask. 114 :param start: The address of the first coil. 115 :param count: How many coils to read. 116 :returns: The frame to send. 117 """ 118 return _read_coils(address, start, count)
Build a read-coils request (function 0x01).
Parameters
- address: The unit address to ask.
- start: The address of the first coil.
- count: How many coils to read. :returns: The frame to send.
121def read_discrete_inputs(address: int, start: int, count: int) -> bytes: 122 """Build a read-discrete-inputs request (function ``0x02``). 123 124 :param address: The unit address to ask. 125 :param start: The address of the first input. 126 :param count: How many inputs to read. 127 :returns: The frame to send. 128 """ 129 return _read_discrete_inputs(address, start, count)
Build a read-discrete-inputs request (function 0x02).
Parameters
- address: The unit address to ask.
- start: The address of the first input.
- count: How many inputs to read. :returns: The frame to send.
157def read_holding_registers(address: int, start: int, count: int) -> bytes: 158 """Build a read-holding-registers request (function ``0x03``). 159 160 :param address: The unit address to ask. 161 :param start: The address of the first register. 162 :param count: How many registers to read. 163 :returns: The frame to send. 164 """ 165 return _read_holding_registers(address, start, count)
Build a read-holding-registers request (function 0x03).
Parameters
- address: The unit address to ask.
- start: The address of the first register.
- count: How many registers to read. :returns: The frame to send.
132def read_holding_registers_reply(address: int, values: list[int]) -> bytes: 133 """Build the reply a device sends to a read-holding-registers request. 134 135 This is the answering half of :func:`read_holding_registers`, so a client can be 136 written and tested against what a device sends without a device on the line. 137 138 :param address: The unit address the reply comes from. 139 :param values: The register values the device reports, in address order. 140 :returns: The frame the device would send. 141 :raises PamojaError: If there are no values, or more than one frame can carry. 142 """ 143 return _read_holding_registers_reply(address, values)
Build the reply a device sends to a read-holding-registers request.
This is the answering half of read_holding_registers(), so a client can be
written and tested against what a device sends without a device on the line.
Parameters
- address: The unit address the reply comes from.
- values: The register values the device reports, in address order. :returns: The frame the device would send.
Raises
- PamojaError: If there are no values, or more than one frame can carry.
168def read_input_registers(address: int, start: int, count: int) -> bytes: 169 """Build a read-input-registers request (function ``0x04``). 170 171 :param address: The unit address to ask. 172 :param start: The address of the first register. 173 :param count: How many registers to read. 174 :returns: The frame to send. 175 """ 176 return _read_input_registers(address, start, count)
Build a read-input-registers request (function 0x04).
Parameters
- address: The unit address to ask.
- start: The address of the first register.
- count: How many registers to read. :returns: The frame to send.
146def read_input_registers_reply(address: int, values: list[int]) -> bytes: 147 """Build the reply a device sends to a read-input-registers request. 148 149 :param address: The unit address the reply comes from. 150 :param values: The register values the device reports, in address order. 151 :returns: The frame the device would send. 152 :raises PamojaError: If there are no values, or more than one frame can carry. 153 """ 154 return _read_input_registers_reply(address, values)
Build the reply a device sends to a read-input-registers request.
Parameters
- address: The unit address the reply comes from.
- values: The register values the device reports, in address order. :returns: The frame the device would send.
Raises
- PamojaError: If there are no values, or more than one frame can carry.
213def write_multiple_coils(address: int, start: int, values: Sequence[bool]) -> bytes: 214 """Build a write-multiple-coils request (function ``0x0F``). 215 216 :param address: The unit address to write to. 217 :param start: The address of the first coil. 218 :param values: One state per coil, at most 1968 of them. 219 :returns: The frame to send. 220 :raises PamojaError: If there are no values, or more than one request carries. 221 """ 222 return _write_multiple_coils(address, start, [bool(value) for value in values])
Build a write-multiple-coils request (function 0x0F).
Parameters
- address: The unit address to write to.
- start: The address of the first coil.
- values: One state per coil, at most 1968 of them. :returns: The frame to send.
Raises
- PamojaError: If there are no values, or more than one request carries.
201def write_multiple_registers(address: int, start: int, values: Sequence[int]) -> bytes: 202 """Build a write-multiple-registers request (function ``0x10``). 203 204 :param address: The unit address to write to. 205 :param start: The address of the first register. 206 :param values: The 16-bit values, at most 123 of them. 207 :returns: The frame to send. 208 :raises PamojaError: If there are no values, or more than one request carries. 209 """ 210 return _write_multiple_registers(address, start, list(values))
Build a write-multiple-registers request (function 0x10).
Parameters
- address: The unit address to write to.
- start: The address of the first register.
- values: The 16-bit values, at most 123 of them. :returns: The frame to send.
Raises
- PamojaError: If there are no values, or more than one request carries.
179def write_single_coil(address: int, coil: int, on: bool) -> bytes: 180 """Build a write-single-coil request (function ``0x05``). 181 182 :param address: The unit address to write to. 183 :param coil: The coil address. 184 :param on: The state to write. 185 :returns: The frame to send. 186 """ 187 return _write_single_coil(address, coil, on)
Build a write-single-coil request (function 0x05).
Parameters
- address: The unit address to write to.
- coil: The coil address.
- on: The state to write. :returns: The frame to send.
190def write_single_register(address: int, register: int, value: int) -> bytes: 191 """Build a write-single-register request (function ``0x06``). 192 193 :param address: The unit address to write to. 194 :param register: The register address. 195 :param value: The 16-bit value to write. 196 :returns: The frame to send. 197 """ 198 return _write_single_register(address, register, value)
Build a write-single-register request (function 0x06).
Parameters
- address: The unit address to write to.
- register: The register address.
- value: The 16-bit value to write. :returns: The frame to send.