Skip to content

Commit 1f080ae

Browse files
committed
Features: drivers/[MODBUS]
1 parent 3223ab0 commit 1f080ae

File tree

5 files changed

+374
-0
lines changed

5 files changed

+374
-0
lines changed

m5stack/libs/driver/manifest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
"tcs3472.py",
3434
"timer_thread.py",
3535
"vl53l0x.py",
36+
"modbus/master/__init__.py",
37+
"modbus/master/uConst.py",
38+
"modbus/master/uFunctions.py",
39+
"modbus/master/uSerial.py",
3640
),
3741
base_path="..",
3842
opt=0,
File renamed without changes.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Source: https://github.com/pycom/pycom-modbus/tree/master/uModbus (2018-07-16)
2+
3+
# function codes
4+
READ_COILS = 0x01
5+
READ_DISCRETE_INPUTS = 0x02
6+
READ_HOLDING_REGISTERS = 0x03
7+
READ_INPUT_REGISTER = 0x04
8+
9+
WRITE_SINGLE_COIL = 0x05
10+
WRITE_SINGLE_REGISTER = 0x06
11+
WRITE_MULTIPLE_COILS = 0x0F
12+
WRITE_MULTIPLE_REGISTERS = 0x10
13+
14+
READ_WRITE_MULTIPLE_REGISTERS = 0x17
15+
MASK_WRITE_REGISTER = 0x16
16+
READ_FIFO_QUEUE = 0x18
17+
18+
READ_FILE_RECORD = 0x14
19+
WRITE_FILE_RECORD = 0x15
20+
21+
READ_EXCEPTION_STATUS = 0x07
22+
DIAGNOSTICS = 0x08
23+
GET_COM_EVENT_COUNTER = 0x0B
24+
GET_COM_EVENT_LOG = 0x0C
25+
REPORT_SERVER_ID = 0x11
26+
READ_DEVICE_IDENTIFICATION = 0x2B
27+
28+
# exception codes
29+
ILLEGAL_FUNCTION = 0x01
30+
ILLEGAL_DATA_ADDRESS = 0x02
31+
ILLEGAL_DATA_VALUE = 0x03
32+
SERVER_DEVICE_FAILURE = 0x04
33+
ACKNOWLEDGE = 0x05
34+
SERVER_DEVICE_BUSY = 0x06
35+
MEMORY_PARITY_ERROR = 0x08
36+
GATEWAY_PATH_UNAVAILABLE = 0x0A
37+
DEVICE_FAILED_TO_RESPOND = 0x0B
38+
39+
# PDU constants
40+
CRC_LENGTH = 0x02
41+
ERROR_BIAS = 0x80
42+
RESPONSE_HDR_LENGTH = 0x02
43+
ERROR_RESP_LEN = 0x05
44+
FIXED_RESP_LEN = 0x08
45+
MBAP_HDR_LENGTH = 0x07
46+
47+
CRC16_TABLE = (
48+
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601,
49+
0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0,
50+
0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81,
51+
0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941,
52+
0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01,
53+
0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0,
54+
0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081,
55+
0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
56+
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00,
57+
0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0,
58+
0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981,
59+
0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
60+
0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700,
61+
0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0,
62+
0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281,
63+
0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
64+
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01,
65+
0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1,
66+
0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80,
67+
0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541,
68+
0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101,
69+
0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0,
70+
0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481,
71+
0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
72+
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801,
73+
0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1,
74+
0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581,
75+
0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341,
76+
0x4100, 0x81C1, 0x8081, 0x4040
77+
)
78+
79+
""" Code to generate the CRC-16 lookup table:
80+
def generate_crc16_table():
81+
crc_table = []
82+
for byte in range(256):
83+
crc = 0x0000
84+
for _ in range(8):
85+
if (byte ^ crc) & 0x0001:
86+
crc = (crc >> 1) ^ 0xa001
87+
else:
88+
crc >>= 1
89+
byte >>= 1
90+
crc_table.append(crc)
91+
return crc_table
92+
"""
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Source: https://github.com/pycom/pycom-modbus/tree/master/uModbus (2018-07-16)
2+
from . import uConst as Const
3+
import struct
4+
5+
6+
def read_coils(starting_address, quantity):
7+
if not (1 <= quantity <= 2000):
8+
raise ValueError('invalid number of coils')
9+
10+
return struct.pack('>BHH', Const.READ_COILS, starting_address, quantity)
11+
12+
def read_discrete_inputs(starting_address, quantity):
13+
if not (1 <= quantity <= 2000):
14+
raise ValueError('invalid number of discrete inputs')
15+
16+
return struct.pack('>BHH', Const.READ_DISCRETE_INPUTS, starting_address, quantity)
17+
18+
def read_holding_registers(starting_address, quantity):
19+
if not (1 <= quantity <= 125):
20+
raise ValueError('invalid number of holding registers')
21+
22+
return struct.pack('>BHH', Const.READ_HOLDING_REGISTERS, starting_address, quantity)
23+
24+
def read_input_registers(starting_address, quantity):
25+
if not (1 <= quantity <= 125):
26+
raise ValueError('invalid number of input registers')
27+
28+
return struct.pack('>BHH', Const.READ_INPUT_REGISTER, starting_address, quantity)
29+
30+
def write_single_coil(output_address, output_value):
31+
if output_value not in [0x0000, 0xFF00]:
32+
raise ValueError('Illegal coil value')
33+
34+
return struct.pack('>BHH', Const.WRITE_SINGLE_COIL, output_address, output_value)
35+
36+
def write_single_register(register_address, register_value, signed=True):
37+
fmt = 'h' if signed else 'H'
38+
39+
return struct.pack('>BH' + fmt, Const.WRITE_SINGLE_REGISTER, register_address, register_value)
40+
41+
def write_multiple_coils(starting_address, value_list):
42+
sectioned_list = [value_list[i:i + 8]
43+
for i in range(0, len(value_list), 8)]
44+
45+
output_value = []
46+
for index, byte in enumerate(sectioned_list):
47+
output = sum(v << i for i, v in enumerate(byte))
48+
output_value.append(output)
49+
50+
fmt = 'B' * len(output_value)
51+
52+
return struct.pack('>BHHB' + fmt, Const.WRITE_MULTIPLE_COILS, starting_address,
53+
len(value_list), (len(value_list) // 8) + 1, *output_value)
54+
55+
def write_multiple_registers(starting_address, register_values, signed=True):
56+
quantity = len(register_values)
57+
58+
if not (1 <= quantity <= 123):
59+
raise ValueError('invalid number of registers')
60+
61+
fmt = ('h' if signed else 'H') * quantity
62+
return struct.pack('>BHHB' + fmt, Const.WRITE_MULTIPLE_REGISTERS, starting_address,
63+
quantity, quantity * 2, *register_values)
64+
65+
def validate_resp_data(data, function_code, address, value=None, quantity=None, signed=True):
66+
if function_code in [Const.WRITE_SINGLE_COIL, Const.WRITE_SINGLE_REGISTER]:
67+
fmt = '>H' + ('h' if signed else 'H')
68+
resp_addr, resp_value = struct.unpack(fmt, data)
69+
70+
if (address == resp_addr) and (value == resp_value):
71+
return True
72+
73+
elif function_code in [Const.WRITE_MULTIPLE_COILS, Const.WRITE_MULTIPLE_REGISTERS]:
74+
resp_addr, resp_qty = struct.unpack('>HH', data)
75+
76+
if (address == resp_addr) and (quantity == resp_qty):
77+
return True
78+
79+
return False
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Source: https://github.com/pycom/pycom-modbus/tree/master/uModbus (2018-07-16)
2+
# This file has been modified and differ from its source version.
3+
from . import uFunctions as functions
4+
from . import uConst as Const
5+
from machine import UART
6+
from machine import Pin
7+
import struct
8+
import time
9+
import machine
10+
11+
12+
class uSerial:
13+
14+
def __init__(self, uart_id, tx, rx, baudrate=9600, data_bits=8, stop_bits=1, parity=None, ctrl_pin=None, debug=False):
15+
self._mdbus_uart = UART(uart_id, tx=tx, rx=rx, baudrate=baudrate, bits=data_bits, parity=parity,
16+
stop=stop_bits, timeout_char=10)
17+
if ctrl_pin is not None:
18+
self._ctrlPin = Pin(ctrl_pin, mode=Pin.OUT)
19+
else:
20+
self._ctrlPin = None
21+
self.char_time_ms = (1000 * (data_bits + stop_bits + 2)) // baudrate
22+
self._modbus_debug = debug
23+
24+
def _calculate_crc16(self, data):
25+
crc = 0xFFFF
26+
27+
for char in data:
28+
crc = (crc >> 8) ^ Const.CRC16_TABLE[((crc) ^ char) & 0xFF]
29+
return struct.pack('<H', crc)
30+
31+
def _bytes_to_bool(self, byte_list):
32+
bool_list = []
33+
for index, byte in enumerate(byte_list):
34+
bool_list.extend([bool(byte & (1 << n)) for n in range(8)])
35+
return bool_list
36+
37+
def _to_short(self, byte_array, signed=True):
38+
response_quantity = int(len(byte_array) / 2)
39+
fmt = '>' + (('h' if signed else 'H') * response_quantity)
40+
41+
return struct.unpack(fmt, byte_array)
42+
43+
def _exit_read(self, response):
44+
if response[1] >= Const.ERROR_BIAS:
45+
if len(response) < Const.ERROR_RESP_LEN:
46+
return False
47+
elif (Const.READ_COILS <= response[1] <= Const.READ_INPUT_REGISTER):
48+
expected_len = Const.RESPONSE_HDR_LENGTH + \
49+
1 + response[2] + Const.CRC_LENGTH
50+
if len(response) < expected_len:
51+
return False
52+
elif len(response) < Const.FIXED_RESP_LEN:
53+
return False
54+
return True
55+
56+
def _mdbus_uart_read(self, timeout=2000):
57+
response = bytearray()
58+
loop_max = int(timeout / 50)
59+
for x in range(1, loop_max):
60+
if self._mdbus_uart.any():
61+
response.extend(self._mdbus_uart.read())
62+
# variable length function codes may require multiple reads
63+
if self._exit_read(response):
64+
break
65+
time.sleep(0.05)
66+
return response
67+
68+
def _send_receive(self, modbus_pdu, slave_addr, count, timeout=2000):
69+
serial_pdu = bytearray()
70+
serial_pdu.append(slave_addr)
71+
serial_pdu.extend(modbus_pdu)
72+
73+
crc = self._calculate_crc16(serial_pdu)
74+
serial_pdu.extend(crc)
75+
76+
# flush the Rx FIFO
77+
read_data = self._mdbus_uart.read()
78+
if read_data is not None:
79+
self.print_debug("ModBus", "R <= {}".format(self.BytesToHexStr(read_data)))
80+
if self._ctrlPin:
81+
self._ctrlPin(1)
82+
83+
self.print_debug("ModBus", "T => {}".format(self.BytesToHexStr(serial_pdu)))
84+
self._mdbus_uart.write(serial_pdu)
85+
if self._ctrlPin:
86+
while not self._mdbus_uart.wait_tx_done(2):
87+
machine.idle()
88+
time.sleep_ms(1 + self.char_time_ms)
89+
self._ctrlPin(0)
90+
return self._validate_resp_hdr(self._mdbus_uart_read(timeout), slave_addr, modbus_pdu[0], count)
91+
92+
def _validate_resp_hdr(self, response, slave_addr, function_code, count):
93+
94+
if len(response):
95+
self.print_debug("ModBus", "R <= {}".format(self.BytesToHexStr(response)))
96+
97+
resp_crc = response[-Const.CRC_LENGTH:]
98+
expected_crc = self._calculate_crc16(
99+
response[0:len(response) - Const.CRC_LENGTH])
100+
if (resp_crc[0] != expected_crc[0]) or (resp_crc[1] != expected_crc[1]):
101+
raise OSError('invalid response CRC')
102+
103+
if (response[0] != slave_addr):
104+
raise ValueError('wrong slave address')
105+
106+
if (response[1] == (function_code + Const.ERROR_BIAS)):
107+
raise ValueError(
108+
'slave returned exception code: {:d}'.format(response[2]))
109+
110+
hdr_length = (Const.RESPONSE_HDR_LENGTH + 1) if count else Const.RESPONSE_HDR_LENGTH
111+
return response[hdr_length: len(response) - Const.CRC_LENGTH]
112+
113+
def read_coils(self, slave_addr, starting_addr, coil_qty, timeout=2000):
114+
modbus_pdu = functions.read_coils(starting_addr, coil_qty)
115+
116+
resp_data = self._send_receive(modbus_pdu, slave_addr, True, timeout)
117+
if resp_data != None:
118+
status_pdu = self._bytes_to_bool(resp_data)
119+
return status_pdu
120+
121+
def read_discrete_inputs(self, slave_addr, starting_addr, input_qty, timeout=2000):
122+
modbus_pdu = functions.read_discrete_inputs(starting_addr, input_qty)
123+
124+
resp_data = self._send_receive(modbus_pdu, slave_addr, True, timeout)
125+
if resp_data != None:
126+
status_pdu = self._bytes_to_bool(resp_data)
127+
return status_pdu
128+
129+
def read_holding_registers(self, slave_addr, starting_addr, register_qty, signed=True, timeout=2000):
130+
modbus_pdu = functions.read_holding_registers(
131+
starting_addr, register_qty)
132+
133+
resp_data = self._send_receive(modbus_pdu, slave_addr, True, timeout)
134+
if resp_data != None:
135+
register_value = self._to_short(resp_data, signed)
136+
return list(register_value)
137+
138+
def read_input_registers(self, slave_addr, starting_address, register_quantity, signed=True, timeout=2000):
139+
modbus_pdu = functions.read_input_registers(
140+
starting_address, register_quantity)
141+
142+
resp_data = self._send_receive(modbus_pdu, slave_addr, True, timeout)
143+
if resp_data != None:
144+
register_value = self._to_short(resp_data, signed)
145+
return list(register_value)
146+
147+
def write_single_coil(self, slave_addr, output_address, output_value, timeout=2000):
148+
modbus_pdu = functions.write_single_coil(output_address, output_value)
149+
150+
resp_data = self._send_receive(modbus_pdu, slave_addr, False, timeout)
151+
if resp_data != None:
152+
operation_status = functions.validate_resp_data(resp_data, Const.WRITE_SINGLE_COIL,
153+
output_address, value=output_value, signed=False)
154+
return operation_status
155+
156+
def write_single_register(self, slave_addr, register_address, register_value, signed=True, timeout=2000):
157+
modbus_pdu = functions.write_single_register(
158+
register_address, register_value, signed)
159+
160+
resp_data = self._send_receive(modbus_pdu, slave_addr, False, timeout)
161+
if resp_data != None:
162+
operation_status = functions.validate_resp_data(resp_data, Const.WRITE_SINGLE_REGISTER,
163+
register_address, value=register_value, signed=signed)
164+
return operation_status
165+
166+
def write_multiple_coils(self, slave_addr, starting_address, output_values, timeout=2000):
167+
modbus_pdu = functions.write_multiple_coils(
168+
starting_address, output_values)
169+
170+
resp_data = self._send_receive(modbus_pdu, slave_addr, False, timeout)
171+
if resp_data != None:
172+
operation_status = functions.validate_resp_data(resp_data, Const.WRITE_MULTIPLE_COILS,
173+
starting_address, quantity=len(output_values))
174+
return operation_status
175+
176+
def write_multiple_registers(self, slave_addr, starting_address, register_values, signed=True, timeout=2000):
177+
modbus_pdu = functions.write_multiple_registers(
178+
starting_address, register_values, signed)
179+
180+
resp_data = self._send_receive(modbus_pdu, slave_addr, False, timeout)
181+
if resp_data != None:
182+
operation_status = functions.validate_resp_data(resp_data, Const.WRITE_MULTIPLE_REGISTERS,
183+
starting_address, quantity=len(register_values))
184+
return operation_status
185+
186+
def BytesToHexStr(self, bins):
187+
return ''.join(["%02X" % x for x in bins]).strip()
188+
189+
def print_debug(self, tag, msg):
190+
if self._modbus_debug:
191+
print("[ \033[32m{}\033[0m ] {}".format(tag, msg))
192+
193+
def close(self):
194+
if self._mdbus_uart is None:
195+
return
196+
try:
197+
self._mdbus_uart.deinit()
198+
except Exception:
199+
pass

0 commit comments

Comments
 (0)