initial checkin
This commit is contained in:
56
app/src/modbus.py
Normal file
56
app/src/modbus.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import struct
|
||||
|
||||
if __name__ == "app.src.modbus":
|
||||
from app.src.singleton import Singleton
|
||||
else: # pragma: no cover
|
||||
from singleton import Singleton
|
||||
|
||||
#######
|
||||
# TSUN uses the Modbus in the RTU transmission mode.
|
||||
# see: https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf
|
||||
#
|
||||
# A Modbus PDU consists of: 'Function-Code' + 'Data'
|
||||
# A Modbus RTU message consists of: 'Addr' + 'Modbus-PDU' + 'CRC-16'
|
||||
#
|
||||
# The 16-bit CRC is known as CRC-16-ANSI(reverse)
|
||||
# see: https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks
|
||||
#######
|
||||
|
||||
CRC_POLY = 0xA001 # (LSBF/reverse)
|
||||
CRC_INIT = 0xFFFF
|
||||
|
||||
|
||||
class Modbus(metaclass=Singleton):
|
||||
MB_WRITE_SINGLE_REG = 6
|
||||
MB_READ_SINGLE_REG = 3
|
||||
__crc_tab = []
|
||||
|
||||
def __init__(self):
|
||||
self.__build_crc_tab(CRC_POLY)
|
||||
|
||||
def build_msg(self, addr, func, reg, val):
|
||||
msg = struct.pack('>BBHH', addr, func, reg, val)
|
||||
msg += struct.pack('<H', self.__calc_crc(msg))
|
||||
return msg
|
||||
|
||||
def check_crc(self, msg) -> bool:
|
||||
return 0 == self.__calc_crc(msg)
|
||||
|
||||
def __calc_crc(self, buffer: bytes) -> int:
|
||||
crc = CRC_INIT
|
||||
|
||||
for cur in buffer:
|
||||
crc = (crc >> 8) ^ self.__crc_tab[(crc ^ cur) & 0xFF]
|
||||
return crc
|
||||
|
||||
def __build_crc_tab(self, poly) -> None:
|
||||
for index in range(256):
|
||||
data = index << 1
|
||||
crc = 0
|
||||
for _ in range(8, 0, -1):
|
||||
data >>= 1
|
||||
if (data ^ crc) & 1:
|
||||
crc = (crc >> 1) ^ poly
|
||||
else:
|
||||
crc >>= 1
|
||||
self.__crc_tab.append(crc)
|
||||
9
app/src/singleton.py
Normal file
9
app/src/singleton.py
Normal file
@@ -0,0 +1,9 @@
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
# logger_mqtt.debug('singleton: __call__')
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton,
|
||||
cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
21
app/tests/test_modbus.py
Normal file
21
app/tests/test_modbus.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# test_with_pytest.py
|
||||
# import pytest, logging
|
||||
from app.src.modbus import Modbus
|
||||
|
||||
|
||||
def test_modbus_crc():
|
||||
mb = Modbus()
|
||||
assert 0x0b02 == mb._Modbus__calc_crc(b'\x01\x06\x20\x08\x00\x04')
|
||||
assert 0 == mb._Modbus__calc_crc(b'\x01\x06\x20\x08\x00\x04\x02\x0b')
|
||||
assert mb.check_crc(b'\x01\x06\x20\x08\x00\x04\x02\x0b')
|
||||
|
||||
assert 0xc803 == mb._Modbus__calc_crc(b'\x01\x06\x20\x08\x00\x00')
|
||||
assert 0 == mb._Modbus__calc_crc(b'\x01\x06\x20\x08\x00\x00\x03\xc8')
|
||||
assert mb.check_crc(b'\x01\x06\x20\x08\x00\x00\x03\xc8')
|
||||
|
||||
def test_build_modbus_pdu():
|
||||
mb = Modbus()
|
||||
pdu = mb.build_msg(1,6,0x2000,0x12)
|
||||
assert pdu == b'\x01\x06\x20\x00\x00\x12\x02\x07'
|
||||
assert mb.check_crc(pdu)
|
||||
|
||||
Reference in New Issue
Block a user