Добавить общее ядро DEIF, эмуляцию приборов и переносимые панели Python/Qt. Расширить загрузчики F2812 и F28335, обновление Flash и профиль STM Invertor. Добавить альтернативный МНК Eigen, модели неисправностей и диагностику сигналов. Обновить справочники устройств, проекты CCS и регрессионные проверки.
139 lines
6.4 KiB
Python
139 lines
6.4 KiB
Python
"""Asynchronous IGBT register transactions with probe and readback."""
|
|
import struct
|
|
|
|
from .qt_compat import QObject, QTimer, Signal, QSerialPort # Клиент общий для Qt6 и Qt5 и не импортирует приложение-потребитель.
|
|
from set_devices.qt_ports.stm_settings import request
|
|
from set_devices.tms_terminal import crc16_modbus
|
|
from .emulator_serial import EmulatorSerialPort, response_timeout # Тот же транспорт SET v2/RTU обслуживает эмуляторы всех поддержанных MCU.
|
|
|
|
|
|
class IgbtClient(QObject):
|
|
finished = Signal(bool, str, object)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.serial = EmulatorSerialPort(self)
|
|
self.serial.readyRead.connect(self._receive)
|
|
self.serial.errorOccurred.connect(self._error)
|
|
self.timer = QTimer(self)
|
|
self.timer.setSingleShot(True)
|
|
self.timer.timeout.connect(lambda: self._finish(False, 'Тайм-аут ответа IGBT'))
|
|
self.active = False
|
|
self.rx = bytearray()
|
|
|
|
def start(self, port, unit, baud, timeout, writes=()):
|
|
if self.active:
|
|
return
|
|
limits = {2: (0, 1), 6: (0, 255), 7: (0, 1000), 8: (1, 255),
|
|
9: (0, 0), 12: (0, 1), 13: (0, 255), 14: (1, 255)}
|
|
channel_limits = [(0, 7), (0, 3), (0, 1000), (1, 1000), (0, 1000),
|
|
(1, 1000), (1, 60000), (0, 3), (0, 60000)]
|
|
for ch in range(8):
|
|
for field, bounds in enumerate(channel_limits):
|
|
limits[16 + ch * 12 + field] = bounds
|
|
self.writes = list(writes)
|
|
if any(off not in limits or not limits[off][0] <= value <= limits[off][1]
|
|
for off, value in self.writes):
|
|
self.finished.emit(False, 'Недопустимые параметры IGBT', None)
|
|
return
|
|
self.unit, self.timeout_ms = unit, timeout
|
|
self.active, self.write_sent = 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, 0x1400, 16)
|
|
|
|
def _send(self, function, register, value):
|
|
self.rx.clear()
|
|
self.packet = request(self.unit, function, register, value)
|
|
self.write_sent |= function == 6
|
|
self.timer.start(response_timeout(self.serial, self.timeout_ms))
|
|
if self.serial.write(self.packet) != len(self.packet):
|
|
self._finish(False, 'Ошибка передачи IGBT')
|
|
|
|
def _receive(self):
|
|
chunk = bytes(self.serial.readAll())
|
|
if not self.active:
|
|
return
|
|
self.rx.extend(chunk)
|
|
function = self.packet[1]
|
|
while len(self.rx) >= 3:
|
|
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 (5 + self.rx[2] if function == 3 else 8)
|
|
if len(self.rx) < size:
|
|
return
|
|
raw = bytes(self.rx[:size])
|
|
self.timer.stop()
|
|
if crc16_modbus(raw):
|
|
self._finish(False, 'Неверная CRC ответа IGBT')
|
|
return
|
|
if raw[1] & 0x80:
|
|
self._finish(False, f'Устройство отклонило запрос IGBT, код {raw[2]}')
|
|
return
|
|
if function == 6:
|
|
if raw != self.packet:
|
|
self._finish(False, 'Неверное подтверждение записи IGBT')
|
|
return
|
|
self._next()
|
|
return
|
|
count = int.from_bytes(self.packet[4:6], 'big')
|
|
if len(raw) != 5 + count * 2 or raw[2] != count * 2:
|
|
self._finish(False, 'Неверная длина состояния IGBT')
|
|
return
|
|
words = struct.unpack(f'>{count}H', raw[3:-2])
|
|
if words[:2] != (0x4947, 3) or words[10:12] != (8, 1) or words[15] != 12:
|
|
self._finish(False, 'Требуется новая прошивка с IGBT версии 3 и настройкой каналов')
|
|
return
|
|
if self.stage == 'probe':
|
|
self.stage = 'state'
|
|
self._send(3, 0x1400, 112)
|
|
return
|
|
if self.stage == 'state' and self.writes:
|
|
self.pending = list(self.writes)
|
|
self.stage = 'write'
|
|
self._next()
|
|
else:
|
|
# The edge counter may advance immediately after being reset.
|
|
expected = dict(self.writes)
|
|
if any(words[off] != value for off, value in expected.items() if off not in (9, 14)):
|
|
self._finish(False, 'Записанные параметры IGBT не подтвердились')
|
|
else:
|
|
self._finish(True, 'IGBT: параметры записаны и проверены' if self.writes
|
|
else 'IGBT: состояние прочитано', words)
|
|
return
|
|
|
|
def _next(self):
|
|
if self.pending:
|
|
off, value = self.pending.pop(0)
|
|
self._send(6, 0x1400 + off, value)
|
|
else:
|
|
self.stage = 'verify'
|
|
self._send(3, 0x1400, 112)
|
|
|
|
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):
|
|
self._finish(False, 'Обмен IGBT отменён')
|
|
|
|
def _finish(self, ok, message, words=None):
|
|
if not self.active:
|
|
return
|
|
self.active = False
|
|
self.timer.stop()
|
|
self.serial.close()
|
|
if not ok and self.write_sent:
|
|
message += '. Часть настроек могла примениться; прочитайте состояние повторно.'
|
|
self.finished.emit(ok, message, words)
|