Files
templates/python/protocan/legacycan.py

227 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""UI-independent model of the historical CAN_terminal protocol.
The catalog is migrated from ``CAN_terminal/Projects.ini``. Both desktop
SETGUI and Android keep only presentation and transport code in the apps;
wire layouts and project metadata live in the shared templates repository.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from enum import Enum
class LegacyCanFormat(Enum):
ROTATING_THREE_WORDS = "rotating_three_words"
ADDRESS_IN_IDENTIFIER = "address_in_identifier"
class LegacyCanSource(Enum):
TO_DEVICE = "TX"
FROM_DEVICE = "RX"
@dataclass(frozen=True)
class LegacyCanPacket:
address: int
mask: int
values: tuple[int, ...]
source: LegacyCanSource
@property
def present_values(self) -> tuple[tuple[int, int], ...]:
return tuple(
(self.address + index, value)
for index, value in enumerate(self.values)
if self.mask == 0xFF or self.mask & (4 >> index)
)
@dataclass(frozen=True)
class LegacyCanWireFrame:
can_id: int
data: bytes
@dataclass(frozen=True)
class LegacyCanRegisterValue:
address: int
value: int = 0
source: LegacyCanSource | None = None
revision: int = 0
@dataclass(frozen=True)
class LegacyCanNode:
index: int
rs_address: int
can_address: int
rx_id: int
tx_id: int
name: str
@dataclass(frozen=True)
class LegacyCanProject:
name: str
format: LegacyCanFormat
base_id: int
id_offset: int
nodes: tuple[LegacyCanNode, ...]
command_names: tuple[str, ...]
def node_for(self, can_id: int) -> LegacyCanNode | None:
routed = routing_id(self.format, can_id)
return next(
(node for node in self.nodes
if node.rx_id == routed or node.tx_id == routed),
None,
)
def empty_register_bank() -> tuple[LegacyCanRegisterValue, ...]:
return tuple(LegacyCanRegisterValue(index) for index in range(128))
def apply_packet(
bank: tuple[LegacyCanRegisterValue, ...],
packet: LegacyCanPacket,
revision: int,
) -> tuple[LegacyCanRegisterValue, ...]:
if len(bank) != 128:
raise ValueError("Банк должен содержать 128 регистров")
updates = dict(packet.present_values)
return tuple(
replace(item, value=updates[item.address], source=packet.source,
revision=revision)
if item.address in updates else item
for item in bank
)
def routing_id(format_: LegacyCanFormat, can_id: int) -> int:
if format_ is LegacyCanFormat.ROTATING_THREE_WORDS:
return can_id & 0x1FFFFFFF
return can_id & 0x1FF00000
def decode(
format_: LegacyCanFormat,
node: LegacyCanNode,
can_id: int,
data: bytes,
) -> LegacyCanPacket | None:
route = routing_id(format_, can_id)
if route == node.tx_id:
source = LegacyCanSource.TO_DEVICE
elif route == node.rx_id:
source = LegacyCanSource.FROM_DEVICE
else:
return None
if format_ is LegacyCanFormat.ROTATING_THREE_WORDS:
if len(data) != 8:
return None
mask = (data[4] >> 5) & 7
address = ((data[4] & 0x1F) << 8) | data[5]
values = (_u16be(data, 6), _u16be(data, 0), _u16be(data, 2))
return LegacyCanPacket(address, mask, values, source)
if not data or len(data) > 8 or len(data) % 2:
return None
return LegacyCanPacket(
can_id & 0x7F,
0xFF,
tuple(_u16be(data, offset) for offset in range(0, len(data), 2)),
source,
)
def encode_write(
format_: LegacyCanFormat,
can_id: int,
address: int,
values: tuple[int, ...] | list[int],
) -> LegacyCanWireFrame:
if not 0 <= address <= 127:
raise ValueError("Адрес регистра должен быть в диапазоне 0..127")
maximum = 4 if format_ is LegacyCanFormat.ADDRESS_IN_IDENTIFIER else 3
if not 1 <= len(values) <= maximum:
raise ValueError(f"Нужно от 1 до {maximum} слов данных")
if any(not 0 <= value <= 0xFFFF for value in values):
raise ValueError("Значение должно быть в диапазоне 0..65535")
if format_ is LegacyCanFormat.ADDRESS_IN_IDENTIFIER:
data = b"".join(int(value).to_bytes(2, "big") for value in values)
return LegacyCanWireFrame((can_id & 0x1FF00000) + address, data)
padded = tuple(values) + (0,) * (3 - len(values))
mask = (4, 6, 7)[len(values) - 1]
data = (
padded[1].to_bytes(2, "big")
+ padded[2].to_bytes(2, "big")
+ bytes(((mask << 5) | (address >> 8), address & 0xFF))
+ padded[0].to_bytes(2, "big")
)
return LegacyCanWireFrame(can_id & 0x1FFFFFFF, data)
def encode_command(
project: LegacyCanProject,
node: LegacyCanNode,
command_index: int,
) -> LegacyCanWireFrame:
if not 0 <= command_index <= 16:
raise ValueError("Номер команды должен быть в диапазоне 0..16")
value = 1 << command_index if command_index < 16 else 0
return encode_write(project.format, node.rx_id, 127, (value,))
def signed_word(value: int) -> int:
return value if value < 0x8000 else value - 0x10000
def _u16be(data: bytes, offset: int) -> int:
return int.from_bytes(data[offset:offset + 2], "big")
_DEFAULT_COMMANDS = (
"Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret",
"Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset",
"Nothing at all",
)
def _commands(**changes: str) -> tuple[str, ...]:
result = list(_DEFAULT_COMMANDS)
for index, name in changes.items():
result[int(index)] = name
return tuple(result)
def _project(
name: str,
specs: tuple[tuple[int, int, int, str], ...],
base_id: int = 0,
offset: int = 0x10,
format_: LegacyCanFormat = LegacyCanFormat.ROTATING_THREE_WORDS,
commands: tuple[str, ...] = _DEFAULT_COMMANDS,
) -> LegacyCanProject:
shift = 20 if format_ is LegacyCanFormat.ADDRESS_IN_IDENTIFIER else 0
actual_offset = 1 << 28 if format_ is LegacyCanFormat.ADDRESS_IN_IDENTIFIER else offset
nodes = tuple(
LegacyCanNode(index, rs, can, base_id + (can << shift),
base_id + actual_offset + (can << shift), node_name)
for index, can, rs, node_name in specs
)
return LegacyCanProject(name, format_, base_id, actual_offset, nodes, commands)
PROJECTS = (
_project("Буксир", ((0,0,1,"УКСС СБ"),(1,1,2,"БКСС ГД"),(2,2,3,"УКСВЭП"),(3,3,4,"Задатчик")), base_id=0x00318200),
_project("СЭДБМ", ((0,0,0,"УКСС СК1 СБ1"),(1,1,1,"УКСС СК2 СБ1"),(2,2,2,"УКСС СК3 СБ1"),(3,3,3,"УКСС СК4 СБ1"),(4,4,4,"УКССВЭП СБ1"),(5,5,5,"Задатчик СБ1"),(6,6,6,"БТР ИТЭС"),(8,0x20,8,"УКСС СК1 СБ2"),(9,0x21,9,"УКСС СК2 СБ2"),(10,0x22,10,"УКСС СК3 СБ2"),(11,0x23,11,"УКСС СК4 СБ2"),(12,0x24,12,"УКССВЭП СБ2"),(13,0x25,13,"Задатчик СБ2"),(14,0x26,14,"УКСС БОИН"),(15,0x27,15,"УКСВЭП БОИН")), base_id=0x01051020, commands=_commands(**{"7":"Raw","8":"HiVolt"})),
_project("Ледокол", ((0,0,1,"УКСС БВ1 ПЧ1"),(8,1,2,"УКСС БВ1 ПЧ2"),(1,2,3,"УКСС БВ1 ПЧ1"),(9,3,4,"УКСС БВ2 ПЧ2"),(2,4,5,"УКСС БИ1 ПЧ1"),(10,5,6,"УКСС БИ1 ПЧ2"),(3,6,7,"УКСС БИ2 ПЧ1"),(11,7,8,"УКСС БИ2 ПЧ2"),(4,8,9,"УКССВЭП1 ПЧ1"),(12,9,10,"УКССВЭП1 ПЧ2"),(5,10,11,"УКССВЭП2 ПЧ1"),(13,11,12,"УКССВЭП2 ПЧ2")), base_id=0x001CE020, offset=-0x20, commands=_commands(**{"4":"Raw","5":"Read","6":"ExtLamp","7":"ExtLite","8":"No log"})),
_project("Бальзам", ((0,0,1,"БКСС Тр1"),(8,1,2,"БКСС Тр2"),(1,2,3,"УКСС СБ1"),(9,3,4,"УКСС СБ2"),(2,4,5,"УКСС УМП1"),(10,5,6,"УКСС УМП2"),(3,6,7,"БКСС ГД"),(4,7,9,"Задатчик"),(5,8,11,"УКСС ВЭП")), base_id=0x00BA0000, commands=_commands(**{"6":"Stop","7":"Start","8":"Init","9":"Tune","10":"Secret","11":"Light","12":"Raw"})),
_project("23550", ((0,0,1,"Задатчик"),(1,1,2,"Выносной пульт"),(2,2,3,"УКСВЭП"),(3,3,4,"БКСС ГД")), base_id=0x00235500, commands=_commands(**{"5":"Read","7":"Send","8":"-"})),
_project("23550.X", ((0,0,1,"Задатчик"),(1,1,2,"Выносной пульт"),(2,2,3,"УКСВЭП"),(3,3,4,"БКСС ГД")), format_=LegacyCanFormat.ADDRESS_IN_IDENTIFIER, commands=_commands(**{"5":"Read","7":"Send","8":"-"})),
_project("23550.2", ((0,0,1,"Задатчик"),(1,1,2,"Выносной пульт"),(2,2,3,"БКСС ГД"),(3,4,4,"УКСС СИ СБ1"),(4,6,6,"УКСС СВФ СБ1"),(5,8,8,"УКСВЭП СБ1"),(11,5,5,"УКСС СИ СБ2"),(12,7,7,"УКСС СВФ СБ2"),(13,9,9,"УКСВЭП СБ2"),(16,0x1F,16,"BroadCast")), format_=LegacyCanFormat.ADDRESS_IN_IDENTIFIER, commands=_commands(**{"5":"Calc","7":"Send"})),
_project("Янтарь", ((0,0,1,"УКСС БВ"),(1,1,2,"УКСС БИ1"),(2,2,3,"УКСС БИ2"),(3,3,4,"БКСС ГД"),(4,4,5,"УКСВЭП"),(5,5,6,"Задатчик"),(6,6,7,"Выносной пульт")), base_id=0x00213000),
_project("23550 БСУ", ((0,0,0,"БСУ1"),(1,1,1,"БСУ2")), base_id=0x0CEB0F1, offset=-0x10, commands=("-",) * 16 + ("Nothing at all",)),
)