Добавить общий протокол Periph28335
This commit is contained in:
98
python/protocan/periph28335.py
Normal file
98
python/protocan/periph28335.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
"""Portable RS command helpers from Set_Terminal_28335.
|
||||||
|
|
||||||
|
The byte order and CRC match ``DTrans.pas``/``UNiiefa.pas``. This module is
|
||||||
|
deliberately UI- and serial-port-independent so desktop and Android clients
|
||||||
|
can share the same request builders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
def crc16_modbus(data: bytes, crc: int = 0xFFFF) -> int:
|
||||||
|
for byte in data:
|
||||||
|
crc ^= byte
|
||||||
|
for _ in range(8):
|
||||||
|
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
|
||||||
|
return crc & 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def with_crc(payload: bytes) -> bytes:
|
||||||
|
crc = crc16_modbus(payload)
|
||||||
|
return bytes(payload) + crc.to_bytes(2, "little")
|
||||||
|
|
||||||
|
|
||||||
|
def build_read_registers(controller: int, start: int, count: int) -> bytes:
|
||||||
|
_range("Адрес контроллера", controller, 0xFF)
|
||||||
|
_range("Начальный регистр", start, 0xFFFF)
|
||||||
|
if not 1 <= count <= 128 or start + count > 128:
|
||||||
|
raise ValueError("Диапазон регистров должен находиться в 0..127")
|
||||||
|
return with_crc(bytes((controller, 3)) + start.to_bytes(2, "big")
|
||||||
|
+ count.to_bytes(2, "big"))
|
||||||
|
|
||||||
|
|
||||||
|
def build_write_register(controller: int, address: int, value: int) -> bytes:
|
||||||
|
_range("Адрес контроллера", controller, 0xFF)
|
||||||
|
_range("Адрес регистра", address, 127)
|
||||||
|
_range("Значение", value, 0xFFFF)
|
||||||
|
return with_crc(bytes((controller, 6)) + address.to_bytes(2, "big")
|
||||||
|
+ value.to_bytes(2, "big"))
|
||||||
|
|
||||||
|
|
||||||
|
def build_command(controller: int, command_index: int) -> bytes:
|
||||||
|
if not 0 <= command_index <= 16:
|
||||||
|
raise ValueError("Номер команды должен быть в диапазоне 0..16")
|
||||||
|
value = 1 << command_index if command_index < 16 else 0
|
||||||
|
return build_write_register(controller, 127, value)
|
||||||
|
|
||||||
|
|
||||||
|
def expected_read_response_size(count: int) -> int:
|
||||||
|
return count * 2 + 5
|
||||||
|
|
||||||
|
|
||||||
|
def decode_read_response(data: bytes, count: int) -> tuple[int, ...]:
|
||||||
|
expected = expected_read_response_size(count)
|
||||||
|
if len(data) != expected:
|
||||||
|
raise ValueError(f"Ожидалось {expected} байт, получено {len(data)}")
|
||||||
|
if crc16_modbus(data[:-2]) != int.from_bytes(data[-2:], "little"):
|
||||||
|
raise ValueError("Ошибка CRC ответа")
|
||||||
|
# Historical replies have a three-byte header; registers are big-endian.
|
||||||
|
body = data[3:-2]
|
||||||
|
if len(body) != count * 2:
|
||||||
|
raise ValueError("Неверная длина данных ответа")
|
||||||
|
return tuple(int.from_bytes(body[offset:offset + 2], "big")
|
||||||
|
for offset in range(0, len(body), 2))
|
||||||
|
|
||||||
|
|
||||||
|
def bits_lsb_first(value: int) -> tuple[bool, ...]:
|
||||||
|
_range("Значение", value, 0xFFFF)
|
||||||
|
return tuple(bool(value & (1 << bit)) for bit in range(16))
|
||||||
|
|
||||||
|
|
||||||
|
def word_from_bits(bits) -> int:
|
||||||
|
values = tuple(bool(item) for item in bits)
|
||||||
|
if len(values) != 16:
|
||||||
|
raise ValueError("Должно быть ровно 16 бит")
|
||||||
|
return sum(1 << bit for bit, checked in enumerate(values) if checked)
|
||||||
|
|
||||||
|
|
||||||
|
def signed_word(value: int) -> int:
|
||||||
|
_range("Значение", value, 0xFFFF)
|
||||||
|
return value if value < 0x8000 else value - 0x10000
|
||||||
|
|
||||||
|
|
||||||
|
def _range(name: str, value: int, maximum: int) -> None:
|
||||||
|
if not 0 <= value <= maximum:
|
||||||
|
raise ValueError(f"{name} вне диапазона 0..{maximum}")
|
||||||
|
|
||||||
|
|
||||||
|
PROJECT_COMMANDS = {
|
||||||
|
"По умолчанию": ("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"23470": ("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"23550": ("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"23550.2": ("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all"),
|
||||||
|
"ICE 22220.1-3": ("Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"ICE 22220.4-5": ("Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"Бальзам 161": ("Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"Бальзам 162": ("Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
"Бальзам 163": ("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
||||||
|
}
|
||||||
23
python/tests/test_periph28335.py
Normal file
23
python/tests/test_periph28335.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from protocan.periph28335 import (
|
||||||
|
bits_lsb_first, build_command, build_read_registers,
|
||||||
|
build_write_register, crc16_modbus, word_from_bits,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_request_matches_delphi_byte_order():
|
||||||
|
request = build_read_registers(16, 24, 64)
|
||||||
|
assert request[:6] == bytes.fromhex("10 03 00 18 00 40")
|
||||||
|
assert int.from_bytes(request[-2:], "little") == crc16_modbus(request[:-2])
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_and_command_use_register_127():
|
||||||
|
request = build_write_register(16, 7, 0x1234)
|
||||||
|
assert request[:6] == bytes.fromhex("10 06 00 07 12 34")
|
||||||
|
command = build_command(16, 15)
|
||||||
|
assert command[:6] == bytes.fromhex("10 06 00 7F 80 00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_bits_keep_original_lsb_first_order():
|
||||||
|
bits = bits_lsb_first(0x8005)
|
||||||
|
assert bits[0] and bits[2] and bits[15]
|
||||||
|
assert word_from_bits(bits) == 0x8005
|
||||||
Reference in New Issue
Block a user