M lazy-lock.json M lua/custom/configs/lspconfig.lua M lua/custom/init.lua A lua/custom/journal.lua A nvim_venv/bin/Activate.ps1 A nvim_venv/bin/activate A nvim_venv/bin/activate.csh A nvim_venv/bin/activate.fish
278 lines
9.6 KiB
Python
278 lines
9.6 KiB
Python
"""File Record Read/Write Messages."""
|
|
from __future__ import annotations
|
|
|
|
import struct
|
|
from dataclasses import dataclass
|
|
|
|
from pymodbus.datastore import ModbusSlaveContext
|
|
from pymodbus.exceptions import ModbusException
|
|
from pymodbus.pdu.pdu import ModbusPDU
|
|
|
|
|
|
@dataclass
|
|
class FileRecord:
|
|
"""Represents a file record and its relevant data."""
|
|
|
|
file_number: int = 0
|
|
record_number: int = 0
|
|
record_data: bytes = b''
|
|
record_length: int = 0
|
|
|
|
def __post_init__(self) -> None:
|
|
"""Initialize a new instance."""
|
|
if self.record_data:
|
|
if self.record_length:
|
|
raise ModbusException("Use either record_data= or record_length=")
|
|
self.record_length = len(self.record_data)
|
|
if self.record_length % 2:
|
|
raise ModbusException("length of record_data must be a multiple of 2")
|
|
self.record_length //= 2
|
|
|
|
|
|
class ReadFileRecordRequest(ModbusPDU):
|
|
"""ReadFileRecordRequest."""
|
|
|
|
function_code = 0x14
|
|
rtu_byte_count_pos = 2
|
|
|
|
def __init__(self, records: list[FileRecord] | None = None, dev_id: int = 1, transaction_id: int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.records: list[FileRecord] = records or []
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the request packet."""
|
|
packet = struct.pack("B", len(self.records) * 7)
|
|
for record in self.records:
|
|
packet += struct.pack(
|
|
">BHHH",
|
|
0x06,
|
|
record.file_number,
|
|
record.record_number,
|
|
record.record_length,
|
|
)
|
|
return packet
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode the incoming request."""
|
|
self.records = []
|
|
byte_count = int(data[0])
|
|
for count in range(1, byte_count, 7):
|
|
decoded = struct.unpack(">BHHH", data[count : count + 7])
|
|
record = FileRecord(
|
|
file_number=decoded[1],
|
|
record_number=decoded[2],
|
|
record_length=decoded[3],
|
|
)
|
|
self.records.append(record)
|
|
|
|
def get_response_pdu_size(self) -> int:
|
|
"""Get response pdu size.
|
|
|
|
Func_code (1 byte) + Quantity of record (each 7 bytes),
|
|
"""
|
|
return 1 + 7 * len(self.records)
|
|
|
|
async def update_datastore(self, _context: ModbusSlaveContext) -> ModbusPDU:
|
|
"""Run a read exception status request against the store."""
|
|
for record in self.records:
|
|
record.record_data = b'SERVER DUMMY RECORD.'
|
|
record.record_length = len(record.record_data) // 2
|
|
return ReadFileRecordResponse(records=self.records,dev_id=self.dev_id, transaction_id=self.transaction_id)
|
|
|
|
|
|
class ReadFileRecordResponse(ModbusPDU):
|
|
"""ReadFileRecordResponse."""
|
|
|
|
function_code = 0x14
|
|
rtu_byte_count_pos = 2
|
|
|
|
def __init__(self, records: list[FileRecord] | None = None, dev_id: int = 1, transaction_id: int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.records: list[FileRecord] = records or []
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the response."""
|
|
total = sum(len(record.record_data) + 1 + 1 for record in self.records)
|
|
packet = struct.pack("B", total)
|
|
for record in self.records:
|
|
packet += struct.pack(">BB", len(record.record_data) + 1, 0x06)
|
|
packet += record.record_data
|
|
return packet
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode the response."""
|
|
count, self.records = 1, []
|
|
byte_count = int(data[0])
|
|
while count < byte_count:
|
|
calc_length, _ = struct.unpack(
|
|
">BB", data[count : count + 2]
|
|
)
|
|
count += 2
|
|
|
|
record_length = calc_length - 1 # response length includes the type byte
|
|
record = FileRecord(
|
|
record_data=data[count : count + record_length],
|
|
)
|
|
count += record_length
|
|
self.records.append(record)
|
|
|
|
|
|
class WriteFileRecordRequest(ModbusPDU):
|
|
"""WriteFileRecordRequest."""
|
|
|
|
function_code = 0x15
|
|
rtu_byte_count_pos = 2
|
|
|
|
def __init__(self, records: list[FileRecord] | None = None, dev_id: int = 1, transaction_id: int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.records: list[FileRecord] = records or []
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the request packet."""
|
|
total_length = sum((record.record_length * 2) + 7 for record in self.records)
|
|
packet = struct.pack("B", total_length)
|
|
|
|
for record in self.records:
|
|
packet += struct.pack(
|
|
">BHHH",
|
|
0x06,
|
|
record.file_number,
|
|
record.record_number,
|
|
record.record_length,
|
|
)
|
|
packet += record.record_data
|
|
return packet
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode the incoming request."""
|
|
byte_count = int(data[0])
|
|
count, self.records = 1, []
|
|
while count < byte_count:
|
|
decoded = struct.unpack(">BHHH", data[count : count + 7])
|
|
calc_length = decoded[3] * 2
|
|
count += calc_length + 7
|
|
record = FileRecord(
|
|
file_number=decoded[1],
|
|
record_number=decoded[2],
|
|
record_data=data[count - calc_length : count],
|
|
)
|
|
record.record_length = decoded[3]
|
|
self.records.append(record)
|
|
|
|
def get_response_pdu_size(self) -> int:
|
|
"""Get response pdu size.
|
|
|
|
Func_code (1 byte) + Quantity of record (each 7 bytes),
|
|
"""
|
|
return 1 + 7 * len(self.records)
|
|
|
|
async def update_datastore(self, _context: ModbusSlaveContext) -> ModbusPDU:
|
|
"""Run the write file record request against the context."""
|
|
return WriteFileRecordResponse(records=self.records, dev_id=self.dev_id, transaction_id=self.transaction_id)
|
|
|
|
|
|
class WriteFileRecordResponse(ModbusPDU):
|
|
"""The normal response is an echo of the request."""
|
|
|
|
function_code = 0x15
|
|
rtu_byte_count_pos = 2
|
|
|
|
def __init__(self, records: list[FileRecord] | None = None, dev_id: int = 1, transaction_id: int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.records: list[FileRecord] = records or []
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the response."""
|
|
total_length = sum((record.record_length * 2) + 7 for record in self.records)
|
|
packet = struct.pack("B", total_length)
|
|
for record in self.records:
|
|
packet += struct.pack(
|
|
">BHHH",
|
|
0x06,
|
|
record.file_number,
|
|
record.record_number,
|
|
record.record_length,
|
|
)
|
|
packet += record.record_data
|
|
return packet
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode the incoming request."""
|
|
count, self.records = 1, []
|
|
byte_count = int(data[0])
|
|
while count < byte_count:
|
|
decoded = struct.unpack(">BHHH", data[count : count + 7])
|
|
calc_length = decoded[3] * 2
|
|
count += calc_length + 7
|
|
record = FileRecord(
|
|
file_number=decoded[1],
|
|
record_number=decoded[2],
|
|
record_data=data[count - calc_length : count],
|
|
)
|
|
record.record_length = decoded[3]
|
|
self.records.append(record)
|
|
|
|
|
|
class ReadFifoQueueRequest(ModbusPDU):
|
|
"""ReadFifoQueueRequest."""
|
|
|
|
function_code = 0x18
|
|
rtu_frame_size = 6
|
|
|
|
def __init__(self, address: int = 0, dev_id: int = 1, transaction_id: int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.address = address
|
|
self.validateAddress()
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the request packet."""
|
|
return struct.pack(">H", self.address)
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode the incoming request."""
|
|
self.address = struct.unpack(">H", data)[0]
|
|
|
|
async def update_datastore(self, _context: ModbusSlaveContext) -> ModbusPDU:
|
|
"""Run a read exception status request against the store."""
|
|
values = [0, 1, 2, 3] # server dummy response (should be in datastore)
|
|
return ReadFifoQueueResponse(values=values, dev_id=self.dev_id, transaction_id=self.transaction_id)
|
|
|
|
|
|
class ReadFifoQueueResponse(ModbusPDU):
|
|
"""ReadFifoQueueResponse."""
|
|
|
|
function_code = 0x18
|
|
|
|
@classmethod
|
|
def calculateRtuFrameSize(cls, buffer: bytes) -> int:
|
|
"""Calculate the size of the message."""
|
|
hi_byte = int(buffer[2])
|
|
lo_byte = int(buffer[3])
|
|
return (hi_byte << 16) + lo_byte + 6
|
|
|
|
def __init__(self, values: list[int] | None = None, dev_id: int = 1, transaction_id:int = 0) -> None:
|
|
"""Initialize a new instance."""
|
|
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
|
|
self.values = values or []
|
|
|
|
def encode(self) -> bytes:
|
|
"""Encode the response."""
|
|
length = len(self.values) * 2
|
|
packet = struct.pack(">HH", 2 + length, length)
|
|
for value in self.values:
|
|
packet += struct.pack(">H", value)
|
|
return packet
|
|
|
|
def decode(self, data: bytes) -> None:
|
|
"""Decode a the response."""
|
|
self.values = []
|
|
_, count = struct.unpack(">HH", data[0:4])
|
|
for index in range(0, count - 4):
|
|
idx = 4 + index * 2
|
|
self.values.append(struct.unpack(">H", data[idx : idx + 2])[0])
|