Объединить протокол терминала TMS2812 для Python и Android

This commit is contained in:
2026-10-01 15:41:43 +03:00
parent e3e30fdad7
commit 3457657979
14 changed files with 729 additions and 9 deletions

View File

@@ -8,6 +8,7 @@ from dataclasses import dataclass
import struct
from protocan.tms2812 import crc16_modbus, build_upload_request, decode_upload_reply
from protocan.tms_terminal import build_fixed_request, validate_response
CMD_LOAD = 51
CMD_UPLOAD = 52
@@ -17,8 +18,7 @@ CMD_EXTEND = 60
def packet(controller: int, command: int, payload: bytes) -> bytes:
raw = bytes((controller, command)) + payload
return raw + struct.pack("<H", crc16_modbus(raw))
return build_fixed_request(controller, command, payload)
def init_load(controller: int, ram: int, size: int) -> bytes:
@@ -52,8 +52,11 @@ def normalize_reply(raw: bytes, controller: int, command: int, size: int) -> byt
decode_upload_reply(full, controller, size - 8)
except ValueError:
return None
elif crc16_modbus(full[:-4]) != int.from_bytes(full[-4:-2], "little"):
return None
else:
try:
validate_response(raw, controller, command, size, True)
except ValueError:
return None
return full

View File

@@ -0,0 +1,134 @@
"""Thin host binding for legacy TMS2812 requests and ordinary responses.
The wire implementation is c/set-protocol/src/tms2812.c, also used by JNI.
This is not the SETProtocol v2 wire format.
"""
import ctypes as ct
from .native import get_native_protocol, NativeProtocolUnavailable
from .tms2812 import crc16_modbus
PEEK = 56
POKE = 57
PING_ADDRESS = 0x100
def _api(name, arguments, result=ct.c_size_t):
try:
function = getattr(get_native_protocol().lib, "pcan_abi_tms2812_" + name)
except AttributeError as error:
raise NativeProtocolUnavailable("Пересоберите templates: отсутствует TMS2812 terminal API") from error
function.argtypes = arguments
function.restype = result
return function
def _range(value, maximum, name):
if not isinstance(value, int) or not 0 <= value <= maximum:
raise ValueError(f"{name} вне диапазона 0…{maximum}")
def build_fixed_request(controller, command, payload):
_range(controller, 255, "Адрес контроллера")
_range(command, 255, "Команда")
payload = bytes(payload)
output = ct.create_string_buffer(len(payload) + 4)
function = _api("build_fixed", [ct.c_uint8, ct.c_uint8, ct.c_void_p, ct.c_size_t, ct.c_void_p, ct.c_size_t])
size = function(controller, command, payload, len(payload), output, len(output))
if not size:
raise ValueError("Не удалось сформировать команду TMS2812")
return output.raw[:size]
def _memory(controller, command, address, value):
_range(controller, 255, "Адрес контроллера")
_range(address, 0xFFFFFFFF, "Адрес слова")
_range(value, 0xFFFF, "Значение")
output = ct.create_string_buffer(12)
function = _api("build_memory", [ct.c_uint8, ct.c_uint8, ct.c_uint32, ct.c_uint16, ct.c_void_p, ct.c_size_t])
size = function(controller, command, address, value, output, len(output))
if not size:
raise ValueError("Не удалось сформировать команду памяти TMS2812")
return output.raw[:size]
def request(controller, address, value=None):
return _memory(controller, PEEK if value is None else POKE, address, 0 if value is None else value)
def ping(controller):
return _memory(controller, 0, 0, 0)
def build_test_request(address, duration, pause, digits):
_range(address, 255, "Адрес контроллера")
_range(duration, 65535, "Длительность")
_range(pause, 65535, "Пауза")
digits = bytes(digits)
if len(digits) != 12:
raise ValueError("CMD_TEST_ALL: требуется 12 байт управления")
output = ct.create_string_buffer(20)
function = _api("build_test", [ct.c_uint8, ct.c_uint16, ct.c_uint16, ct.c_void_p, ct.c_size_t, ct.c_void_p, ct.c_size_t])
size = function(address, duration, pause, digits, len(digits), output, len(output))
if not size:
raise ValueError("Не удалось сформировать CMD_TEST_ALL")
return output.raw[:size]
def validate_response(raw, controller, command, full_size, allow_short_tail=False):
_range(controller, 255, "Адрес контроллера")
_range(command, 255, "Команда")
if full_size < 6:
raise ValueError("Неверная длина ответа TMS2812")
function = _api("validate_response", [ct.c_void_p, ct.c_size_t, ct.c_uint8, ct.c_uint8, ct.c_size_t, ct.c_int], ct.c_int)
status = function(bytes(raw), len(raw), controller, command, full_size, bool(allow_short_tail))
errors = {-1: "Неверный формат ответа TMS2812", -3: "Неверная длина ответа TMS2812",
-4: "CRC ответа TMS2812 не совпадает", -5: "Неверный адрес или команда ответа TMS2812"}
if status:
raise ValueError(errors.get(status, f"Ошибка TMS2812: {status}"))
def decode_reply(raw, controller, command):
if command not in (PEEK, POKE):
raise ValueError("Ожидается CMD_PEEK или CMD_POKE")
validate_response(raw, controller, command, 10 if command == PEEK else 6, True)
return int.from_bytes(raw[2:4], "little") if command == PEEK else None
def memory_response(raw, controller, command):
"""Find a PEEK/POKE response in a UART buffer, as Android feedLegacy does.
Returns None until data plus CRC are present. A short valid result still
needs the transport's settle timer before the next request. Ignore noise,
an optional preliminary ACK, and bytes after the complete response.
"""
_range(controller, 255, "Адрес контроллера")
if command not in (PEEK, POKE):
raise ValueError("Ожидается CMD_PEEK или CMD_POKE")
header = bytes((controller, command))
start = bytes(raw).find(header)
if start < 0:
return None
data = bytes(raw[start:])
size = 10 if command == PEEK else 6
if size > 6:
for ack_size in (6, 4):
if data[ack_size:ack_size + 2] != header:
continue
try:
validate_response(data[:ack_size], controller, command, 6, True)
except ValueError:
continue
data = data[ack_size:]
break
if len(data) >= size:
packet = data[:size]
validate_response(packet, controller, command, size)
return packet
if len(data) >= size - 2:
try:
validate_response(data, controller, command, size, True)
except ValueError:
return None
return data
return None