"""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("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)