Files
templates/python/set_devices/qt_ports/stm_settings.py

171 lines
7.6 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.
"""Transactional STM configuration extension (FC03/FC06, 0x1210)."""
import struct
from .qt_compat import QObject, QTimer, Signal
from .qt_compat import QSerialPort
from set_devices.tms_terminal import crc16_modbus
BAUDRATES = (9600, 19200, 38400, 57600, 115200)
def request(unit, function, register, value):
body = struct.pack(">BBHH", unit, function, register, value)
return body + struct.pack("<H", crc16_modbus(body))
def decode_config(raw):
if len(raw) != 25:
raise ValueError("Нужна прошивка STM с выбором режима эмуляции")
words = struct.unpack(">10H", raw[3:-2])
if words[:2] != (0x5343, 2):
raise ValueError("Прошивка STM не поддерживает настройку связи (нужна новая версия)")
address, tms, rate = words[2:5]
if not 1 <= address <= 247 or not 1 <= tms <= 255 or address == tms or rate >= len(BAUDRATES) or words[8] > 1 or words[9] > 1:
raise ValueError("STM вернула неверные параметры связи")
return words
class StmSettingsClient(QObject):
finished = Signal(bool, str, object)
def __init__(self, parent=None):
super().__init__(parent)
self.serial = QSerialPort(self)
self.serial.readyRead.connect(self._receive)
self.serial.errorOccurred.connect(self._error)
self.timeout = QTimer(self)
self.timeout.setSingleShot(True)
self.timeout.timeout.connect(lambda: self._finish(False, "Тайм-аут настройки STM"))
self.delay = QTimer(self)
self.delay.setSingleShot(True)
self.delay.timeout.connect(self._next)
self.active = False
self.committed = False
self.rx = bytearray()
def start(self, port, unit, baud, timeout, desired=None):
if self.active:
return
if desired is not None:
addr, tms, rate, mode = desired
if not 1 <= addr <= 247 or not 1 <= tms <= 255 or addr == tms or rate not in BAUDRATES or mode not in (0, 1):
self.finished.emit(False, "Адреса УМП и 2812 должны различаться; проверьте диапазоны и скорость", None)
return
self.unit, self.baud, self.timeout_ms = unit, baud, timeout
self.desired = desired
self.active, self.committed = True, False
self.stage = "probe"
self.serial.setPortName(port)
self.serial.setBaudRate(baud)
self.serial.setDataBits(QSerialPort.DataBits.Data8)
self.serial.setParity(QSerialPort.Parity.NoParity)
self.serial.setStopBits(QSerialPort.StopBits.OneStop)
self.serial.setFlowControl(QSerialPort.FlowControl.NoFlowControl)
if not self.serial.open(QSerialPort.OpenModeFlag.ReadWrite):
self._finish(False, self.serial.errorString())
return
self._send(3, 0x1210, 10)
def _send(self, function, register, value):
self.rx.clear()
self.packet = request(self.unit, function, register, value)
self.timeout.start(self.timeout_ms)
if self.stage == "commit":
# If ACK is lost, the board may nevertheless switch settings.
self.committed = True
if self.serial.write(self.packet) != len(self.packet):
self._finish(False, "Ошибка передачи настройки STM")
def _receive(self):
chunk = bytes(self.serial.readAll())
if not self.active or not self.timeout.isActive():
return
self.rx.extend(chunk)
function = self.packet[1]
while len(self.rx) >= 2:
if self.rx[0] != self.unit or self.rx[1] not in (function, function | 0x80):
del self.rx[0]
continue
size = 5 if self.rx[1] & 0x80 else (25 if function == 3 else 8)
if len(self.rx) < size:
return
raw = bytes(self.rx[:size])
del self.rx[:size]
if crc16_modbus(raw):
self._finish(False, "Неверная CRC ответа STM")
return
if raw[1] & 0x80:
self._finish(False, "STM отклонила настройку, код %d. Для старой прошивки требуется обновление." % raw[2])
return
if (function == 6 and raw != self.packet) or (function == 3 and raw[2] != 20):
self._finish(False, "Неверный ответ настройки STM")
return
self.timeout.stop()
try:
self._accept(raw)
except ValueError as error:
self._finish(False, str(error))
return
def _accept(self, raw):
if self.stage == "probe":
words = decode_config(raw)
if self.desired is None:
self._finish(True, "Настройки STM прочитаны", (words[2], words[3], BAUDRATES[words[4]], words[8]))
return
addr, tms, baud, mode = self.desired
self.staged = (addr, tms, BAUDRATES.index(baud), mode)
self.steps = [(6, 0x1218, 0), (6, 0x1215, addr), (6, 0x1216, tms),
(6, 0x1217, self.staged[2]), (6, 0x1219, mode), (3, 0x1210, 10)]
self.stage = "stage"
elif self.stage == "stage" and self.packet[1] == 3:
words = decode_config(raw)
if (*words[5:8], words[9]) != self.staged:
raise ValueError("STM не подтвердила подготовленные настройки")
self.stage = "commit"
self.steps = [(6, 0x1218, 0xA55A)]
elif self.stage == "commit":
self.stage = "switch"
self.delay.start(700)
return
elif self.stage == "verify":
words = decode_config(raw)
if (*words[2:5], words[8]) != self.staged:
raise ValueError("Новые настройки STM не подтвердились")
self._finish(True, "Настройки применены в STM и проверены. Действуют до перезагрузки платы.", self.desired)
return
self.delay.start(20)
def _next(self):
if not self.active:
return
if self.stage == "switch":
self.unit, _tms, baud, _mode = self.desired
if not self.serial.setBaudRate(baud):
self._finish(False, "Не удалось переключить скорость COM")
return
self.stage = "verify"
self._send(3, 0x1210, 10)
else:
self._send(*self.steps.pop(0))
def _error(self, error):
if self.active and error not in (QSerialPort.SerialPortError.NoError, QSerialPort.SerialPortError.TimeoutError):
self._finish(False, self.serial.errorString())
def cancel(self):
if self.active:
self._finish(False, "Настройка прервана")
def _finish(self, ok, message, values=None):
if not self.active:
return
self.active = False
self.timeout.stop()
self.delay.stop()
if self.serial.isOpen():
self.serial.close()
if not ok and self.committed:
message += " Возможно, STM уже применила новые значения: адрес УМП %d, адрес 2812 %d, %d бод, режим %d (0=УМП, 1=2812). Повторите чтение по ним или перезагрузите плату." % self.desired
self.finished.emit(ok, message, values)