171 lines
7.6 KiB
Python
171 lines
7.6 KiB
Python
"""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)
|