diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/controller/controller_reifined.py b/controller/controller_reifined.py
index 94ccdd2..0049574 100644
--- a/controller/controller_reifined.py
+++ b/controller/controller_reifined.py
@@ -169,11 +169,20 @@ class ControllerInterface:
error(e)
return False
- def sendPacketWithResponse(self, command_code: int, target: tuple, data: list, throw_if_error=False) -> list:
+ def sendPacketWithResponse(self, command_code: int, target: tuple, data: list, throw_if_error=False, retry=2) -> list:
try:
- self.controller.sendRequest(command_code, target, data)
- self.controller.packet_id = (self.controller.packet_id + 1) % 255
- return self.controller.readData()
+ for i in range(retry):
+ debug(f'Try #{i}')
+ self.controller.sendRequest(command_code, target, data)
+ self.controller.packet_id = (self.controller.packet_id + 1) % 255
+ res = self.controller.readData()
+ debug(f'Read data: {res}')
+ if res:
+ return res
+
+ time.sleep(0.5)
+ debug(f'Failed after {retry} tries')
+ return []
except SerialException or IndexError as e:
error(e)
if throw_if_error:
@@ -280,7 +289,7 @@ class ControllerInterface:
if not mode:
return []
modes.append(mode)
- time.sleep(0.05)
+ time.sleep(0.1) # TODO CHECK
i = 0
for m in modes:
diff --git a/final.py b/final.py
new file mode 100644
index 0000000..7ad5d4a
--- /dev/null
+++ b/final.py
@@ -0,0 +1,485 @@
+import logging
+import os
+import sys
+import time
+from datetime import datetime
+from logging import debug, info
+from threading import Timer
+
+from PyQt6 import QtCore, QtWidgets, QtGui
+from PyQt6.QtCore import QRegularExpression
+from PyQt6.QtGui import QRegularExpressionValidator
+
+import controller.controller_reifined as controller
+from all import Ui_Controller
+
+
+class Window(QtWidgets.QMainWindow):
+ def __init__(self):
+ QtWidgets.QMainWindow.__init__(self)
+
+ self.ui = Ui_Controller()
+ self.ui.setupUi(self)
+
+ self.controller = controller.ControllerInterface(controller.ControllerRefined(0xFFFFFFFFFFFF, 0x112233445542))
+ self.device = ""
+
+ # dev selection
+ self.hoverFilter = self.ComboBoxHoverEventFilter(self.ui.DeviceSelection, self.refreshDeviceList)
+ self.connected = False
+
+ # object containers
+ self.currentFields = [self.ui.Current1Field, self.ui.Current2Field, self.ui.Current3Field, self.ui.Current4Field]
+ self.voltageFields = [self.ui.Voltage1Field, self.ui.Voltage2Field, self.ui.Voltage3Field, self.ui.Voltage4Field]
+
+ # qt styles
+ self.redLineEdit = "QLineEdit { background: rgb(255, 0, 0); selection-background-color: rgb(111, 136, 222); }"
+ self.disLineEdit = "QLineEdit { background: rgb(117, 116, 114); selection-background-color: rgb(117, 116, 114); }"
+ self.defLineEdit = "QLineEdit {}"
+ self.redCombobox = "QComboBox { background: rgb(255, 0, 0); selection-background-color: rgb(111, 136, 222); }"
+ self.defCombobox = "QComboBox {}"
+
+ # Other state vars
+ self.last_click_time = 0
+ self.savedStatus = ""
+ self.modes = []
+
+ # Mode table fields
+ self.imageDialog = None
+ self.imageLabel = None
+ self.pixmap = None
+
+
+ # connect signals and validation
+ self.connectMethods()
+ self.applyValidators()
+
+ def connectMethods(self):
+ self.ui.DeviceSelection.activated.connect(self.deviceSelectionCallback)
+ self.ui.DeviceSelection.installEventFilter(self.hoverFilter)
+ self.ui.Read.clicked.connect(self.readCallback)
+ self.ui.Write.clicked.connect(self.writeCallback)
+ self.ui.ModeInfo.clicked.connect(self.showModeTable)
+
+ def applyValidators(self):
+ # 1 - 511
+ self.ui.DmxAddressField.setValidator(QRegularExpressionValidator(QRegularExpression("^([1-9]|[1-9][0-9]|[1-4][0-9]{2}|50[0-9]|511)$")))
+
+ # 100 - 2550
+ for f in self.currentFields:
+ f.setValidator(QRegularExpressionValidator(
+ QRegularExpression("^(?:[1-9][0-9]{2}|1[0-9]{3}|2[0-4][0-9]{2}|25[0-4][0-9]|2550)$")))
+
+ # 2 - 60
+ for f in self.voltageFields:
+ f.setValidator(QRegularExpressionValidator(
+ QRegularExpression("^(?:[2-9]|[1-5][0-9]|60)$")))
+
+ # 5 - 255
+ self.ui.PowerField.setValidator(QRegularExpressionValidator(
+ QRegularExpression("^(?:[5-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")))
+
+ def resetAllStyles(self) -> None:
+ for f in self.currentFields:
+ f.setStyleSheet(self.defLineEdit)
+ for f in self.voltageFields:
+ f.setStyleSheet(self.defLineEdit)
+
+ self.ui.ModeSelection.setStyleSheet(self.defCombobox)
+ self.ui.PDop.setStyleSheet(self.defLineEdit)
+ self.ui.DmxAddressField.setStyleSheet(self.defLineEdit)
+
+ def clearAllFields(self) -> None:
+ self.ui.DmxAddressField.clear()
+ self.ui.ModeSelection.clear()
+
+ for f in self.currentFields:
+ f.setText("")
+ f.setReadOnly(False)
+
+ for f in self.voltageFields:
+ f.setText("")
+ f.setReadOnly(False)
+
+ self.ui.PowerField.setText("")
+ self.ui.PowerField.setReadOnly(False)
+
+ def resetStatus(self) -> None:
+ self.setStatus(self.savedStatus)
+
+ def setStatus(self, text: str) -> None:
+ print(text)
+ self.ui.Status.setText(text)
+
+ app.processEvents()
+
+ def setTempStatus(self, new_status: str, reset_time: float) -> None:
+ if self.ui.Status.text() != new_status:
+ self.savedStatus = self.ui.Status.text()
+ self.setStatus(new_status)
+ Timer(reset_time, self.resetStatus).start()
+
+ def setText(self, field, text) -> None:
+ field.setReadOnly(False)
+ field.setStyleSheet(self.defLineEdit)
+ field.setText(text)
+
+ def setReadonly(self, fields: list) -> None:
+ for f in fields:
+ f.setReadOnly(True)
+ f.setStyleSheet(self.disLineEdit)
+
+ def setCurrents(self, currents: list) -> None:
+ for i, f in enumerate(self.currentFields):
+ self.setText(f, str(currents[i] * 10))
+
+ def getCurrents(self) -> list:
+ currents = []
+ for f in self.currentFields:
+ currents.append(int(f.text()) if f.text() != "" else 0)
+ return currents
+
+ def setVoltages(self, voltages: list) -> None:
+ for i, f in enumerate(self.voltageFields):
+ self.setText(f, str(int(voltages[i] * 0.235)))
+
+ def getVoltages(self) -> list:
+ voltages = []
+ for f in self.voltageFields:
+ voltages.append(int(f.text()) if f.text() != "" else 0)
+ return voltages
+
+ def setPower(self, power: int) -> None:
+ self.setText(self.ui.PowerField, str(power))
+
+ def connectionLost(self) -> None:
+ self.controller.close()
+ self.setStatus('Пожалуйста, выберите порт')
+ self.setTempStatus(f'Подключение к {self.controller.controller.ser.port} потеряно!', 2)
+ self.resetAllStyles()
+ self.clearAllFields()
+ self.ui.DeviceSelection.clear() # Manually clear device selection combobox
+
+ def determine_capabilities(self, readModes=True) -> tuple[bool, tuple]:
+ # 1.1
+ self.setStatus(f'Чтение адреса {self.controller.controller.ser.port}...')
+ address = self.controller.readAddress()
+ if address == -1:
+ self.setStatus(f'Ошибка чтения адреса {self.controller.controller.ser.port}')
+ return False, ()
+
+ # 1.2
+ self.setStatus(f'Чтение режима {self.controller.controller.ser.port}...')
+ dev_info = self.controller.readDeviceInfo()
+ debug(dev_info)
+ if not dev_info:
+ self.setStatus(f'Ошибка чтения режима {self.controller.controller.ser.port}')
+ return False, ()
+
+ # 1.2 (after all)
+ if readModes:
+ self.modes = []
+ mode_count = dev_info[2]
+ for index in range(mode_count):
+ self.setStatus(f'Чтение режима {index + 1} {self.controller.controller.ser.port}...')
+ mode = self.controller.readMode(index)
+ if not mode:
+ return False, ()
+
+ self.modes.append(mode)
+ time.sleep(0.05)
+
+ # 1.3
+ self.setStatus(f'Чтение токов {self.controller.controller.ser.port}...')
+ currents = self.controller.readCurrents()
+
+ # 1.4
+ self.setStatus(f'Чтение эл. параметров {self.controller.controller.ser.port}...')
+ powers = self.controller.readPowers()
+ return True, (address, dev_info, self.modes, currents, powers)
+
+ def readData(self, connecting=False) -> bool:
+ self.connected, data = self.determine_capabilities()
+ if not self.connected:
+ self.setStatus(f'{"Не удалось подключиться" if connecting else "Потеряно подключение"} к {self.device}')
+ self.ui.DeviceSelection.clear()
+ self.controller.close()
+ return False
+
+ address, dev_info, self.modes, currents, powers = data
+ self.setStatus(f'Выбранный порт: {self.device}')
+ self.ui.DmxAddressField.setText(str(address))
+
+ self.ui.ModeSelection.setStyleSheet(self.defCombobox)
+ self.ui.ModeSelection.clear()
+ mode_names = []
+ for index, m in enumerate(self.modes):
+ mode_names.append(f'{index + 1}:{m[1]}')
+
+ self.ui.ModeSelection.addItems(mode_names)
+ self.ui.ModeSelection.setCurrentText(mode_names[dev_info[1] - 1])
+
+ if currents:
+ self.setCurrents(currents)
+ else:
+ self.setReadonly(self.currentFields)
+
+ if powers:
+ self.setVoltages(powers[4:8])
+ self.setPower(powers[8])
+ else:
+ self.setReadonly([*self.voltageFields, self.ui.PowerField])
+
+ return True
+
+ def writeData(self) -> None:
+ success, data = self.determine_capabilities(readModes=False)
+ if not success:
+ self.connectionLost()
+ return
+
+ address, dev_info, _, currents, powers = data
+
+ self.setStatus(f'Запись адреса в {self.controller.controller.ser.port}...')
+ if self.ui.DmxAddressField.text() != "" and not self.controller.writeAddress(int(self.ui.DmxAddressField.text())):
+ self.connectionLost()
+ return
+
+ self.setStatus(f'Запись режима в {self.controller.controller.ser.port}...')
+ if not self.controller.writeMode(self.ui.ModeSelection.currentIndex() + 1):
+ self.connectionLost()
+ return
+
+ if currents:
+ self.setStatus(f'Запись токов в {self.controller.controller.ser.port}...')
+ currents_controller = [int(self.getCurrents()[i] * 0.1) for i in range(4)]
+ if not self.controller.writeCurrents(currents_controller):
+ self.connectionLost()
+ return
+
+ if powers:
+ currents = self.getCurrents()
+ voltages = self.getVoltages()
+ # FIX ROUNDING ISSUE
+ for i in range(4):
+ voltages[i] = voltages[i] + 1
+
+ p_poss = int(self.ui.PowerField.text())
+ p_max = powers[17]
+
+ pwm_max = int.from_bytes([powers[15], powers[16]], byteorder='big')
+
+ currents_ma = [currents[i] * 0.001 for i in range(4)]
+
+ currents_controller = [int(currents[i] * 0.1) for i in range(4)]
+ voltages_controller = [int(voltages[i] / 0.235) for i in range(4)]
+
+ powers = [currents_ma[i] * voltages[i] for i in range(4)]
+ p_sum = sum(powers)
+ ka = [powers[i] / p_sum for i in range(4)]
+ km = min(p_poss / p_sum, 1)
+ Amax = int(km * pwm_max)
+ kn = [min(int(ka[i] * 256), 255) for i in range(4)]
+
+ info(f'Currents: {currents}')
+ info(f'Currents controller: {currents_controller}')
+ info(f'Currents ma: {currents_ma}')
+ info(f'Voltages: {voltages}')
+ info(f'Voltages controller: {voltages_controller}')
+ info(f'Powers: {powers}')
+ info(f'Power sum: {p_sum}')
+ info(f'Ka: {ka}')
+ info(f'Km: {km}')
+ info(f'Pmax: {p_max}')
+ info(f'Pext: {p_poss}')
+ info(f'PwmMax: {pwm_max}')
+ info(f'Amax: {Amax}')
+ info(f'Kn: {kn}')
+
+ self.setStatus(f'Запись эл. параметров в {self.controller.controller.ser.port}...')
+ if not self.controller.writePowers(currents_controller, voltages_controller, p_poss, kn, Amax):
+ self.connectionLost()
+ return
+
+ if not self.ui.Check.isChecked():
+ self.setStatus(f'Выбранный порт: {self.controller.controller.ser.port}')
+ self.setTempStatus(f'Данные были записаны в {self.controller.controller.ser.port}', 1)
+ else:
+ time.sleep(0.5)
+ dmx_save = self.ui.DmxAddressField.text()
+ pers_save = self.ui.ModeSelection.currentText()
+ currents_save = []
+ for entry in self.currentFields:
+ currents_save.append(entry.text())
+ voltages_save = []
+ for entry in self.voltageFields:
+ voltages_save.append(entry.text())
+ p_poss_save = self.ui.PowerField.text()
+
+ self.setStatus(f'Чтение адреса: {self.controller.controller.ser.port}...')
+ dmx_real = self.controller.readAddress()
+ if dmx_real == -1:
+ self.connectionLost()
+ return
+
+ self.setStatus(f'Чтение режима: {self.controller.controller.ser.port}...')
+ dev_info = self.controller.readDeviceInfo()
+ if not dev_info:
+ self.connectionLost()
+ return
+ pers_real = f'{dev_info[1]}:{self.modes[dev_info[1] - 1][1]}'
+
+ currents_real = []
+ if currents:
+ self.setStatus(f'Чтение токов: {self.controller.controller.ser.port}...')
+ currents_real = [a * 10 for a in self.controller.readCurrents()]
+ if not currents_real:
+ self.connectionLost()
+ return
+
+ voltages_real = []
+ p_poss_real = 0
+ if powers:
+ self.setStatus(f'Чтение эл. параметров: {self.controller.controller.ser.port}...')
+ powers_real = self.controller.readPowers()
+ if not powers_real:
+ self.connectionLost()
+ return
+
+ voltages_real = [int(powers_real[4 + i] * 0.235) for i in range(4)]
+ p_poss_real = powers_real[8]
+ if not currents:
+ currents_real = powers_real[0:4]
+
+ print(f'Save: {dmx_save}, {pers_save}, {currents_save}, {voltages_save}, {p_poss_save}')
+ print(f'Real: {dmx_real}, {pers_real}, {currents_real}, {voltages_real}, {p_poss_real}')
+
+ failed = False
+ if dmx_save == "" or int(dmx_save) != dmx_real:
+ failed = True
+ self.ui.DmxAddressField.setStyleSheet(self.redLineEdit)
+ else:
+ self.ui.DmxAddressField.setStyleSheet(self.defLineEdit)
+
+ if pers_save != pers_real:
+ failed = True
+ self.ui.ModeSelection.clear()
+ self.ui.ModeSelection.setStyleSheet('QComboBox { background: rgb(255, 0, 0); selection-background-color: rgb(111, 136, 222); }')
+ else:
+ self.ui.ModeSelection.setStyleSheet('QComboBox {}')
+
+ for index in range(4):
+ if currents:
+ if currents_save[index] == "" or int(currents_save[index]) != currents_real[index]:
+ failed = True
+ print(f'Failed current: {index}')
+ self.currentFields[index].setStyleSheet(self.redLineEdit)
+ else:
+ self.currentFields[index].setStyleSheet(self.defLineEdit)
+ if powers:
+ if voltages_save[index] == "" or int(voltages_save[index]) != voltages_save[index]:
+ failed = True
+ print(f'Failed voltage: {index}')
+ self.voltageFields[index].setStyleSheet(self.redLineEdit)
+ else:
+ self.voltageFields[index].setStyleSheet(self.defLineEdit)
+ if powers:
+ if p_poss_save != p_poss_real:
+ failed = True
+ self.ui.PowerField.setStyleSheet(self.redLineEdit)
+ else:
+ self.ui.PowerField.setStyleSheet(self.defLineEdit)
+
+ self.setStatus(f'Выбранный порт: {self.controller.controller.ser.port}')
+ if failed:
+ self.setTempStatus('Обнаружены ошибки, повторите запись', 1)
+ else:
+ self.setTempStatus('Проверка прошла успешно', 1)
+
+ def deviceSelectionCallback(self, deviceIndex: int):
+ self.resetAllStyles()
+ self.clearAllFields()
+
+ self.device = self.ui.DeviceSelection.itemText(deviceIndex)
+
+ self.connected = False
+ self.controller.close()
+
+ self.setStatus(f'Подключение к {self.device}...')
+ self.controller.connect(self.device)
+ self.readData(connecting=True)
+
+ def readCallback(self):
+ if time.time() - self.last_click_time < 1:
+ return
+ self.last_click_time = time.time()
+
+ if self.connected:
+ if self.readData():
+ self.setStatus(f'Выбранный порт: {self.controller.controller.ser.port}')
+ self.setTempStatus(f'Данные прочитаны, порт: {self.controller.controller.ser.port}', 1)
+ else:
+ self.setTempStatus('Порт не выбран!', 1)
+
+ def writeCallback(self):
+ if time.time() - self.last_click_time < 2:
+ return
+
+ self.last_click_time = time.time()
+
+ if self.connected:
+ self.writeData()
+ else:
+ self.setTempStatus('Порт не выбран!', 1)
+
+ def refreshDeviceList(self):
+ selected = self.ui.DeviceSelection.currentText()
+ self.ui.DeviceSelection.clear()
+ self.ui.DeviceSelection.addItems(self.controller.readComPorts())
+ self.ui.DeviceSelection.setCurrentText(selected)
+
+ def showModeTable(self) -> None:
+ self.pixmap = QtGui.QPixmap("../table.png")
+ debug(self.pixmap.width())
+
+ self.imageDialog = QtWidgets.QDialog()
+ self.imageDialog.resize(self.pixmap.width(), self.pixmap.height())
+ self.imageLabel = QtWidgets.QLabel(self.imageDialog)
+ self.imageLabel.resize(self.pixmap.width(), self.pixmap.height())
+ self.imageLabel.setPixmap(self.pixmap)
+ self.imageDialog.exec()
+
+ class ComboBoxHoverEventFilter(QtCore.QObject):
+ def __init__(self, parent, onEnter):
+ super().__init__(parent)
+ self.parent = parent
+ self.onEnter = onEnter
+
+ def eventFilter(self, obj, event):
+ if event.type() == QtCore.QEvent.Type.Enter:
+ self.onEnter()
+ return super().eventFilter(obj, event)
+
+
+# 2, 37, 33, 60
+app = QtWidgets.QApplication(sys.argv)
+
+
+def setup_logs() -> None:
+ os.makedirs("logs", exist_ok=True)
+ logging.basicConfig(
+ level=logging.DEBUG,
+ format="%(asctime)s [%(levelname)s] %(message)s",
+ handlers=[
+ logging.FileHandler(f'./logs/{datetime.now().strftime("%Y%m%d-%H%M%S")}.log'),
+ logging.StreamHandler()
+ ]
+ )
+
+
+if __name__ == '__main__':
+ setup_logs()
+ window = Window()
+ window.show()
+
+ sys.exit(app.exec())
diff --git a/main.py b/main.py
index 5609b6f..1556b0e 100644
--- a/main.py
+++ b/main.py
@@ -321,5 +321,5 @@ def stress_test() -> None:
tests.start()
if __name__ == '__main__':
- start_ui()
- # stress_test()
\ No newline at end of file
+ #start_ui()
+ stress_test()
\ No newline at end of file
diff --git a/tests.py b/tests.py
index 6d7a9bc..0bcd68f 100644
--- a/tests.py
+++ b/tests.py
@@ -14,7 +14,7 @@ import controller.controller_reifined as c
def setup_logs() -> None:
os.makedirs("logs", exist_ok=True)
logging.basicConfig(
- level=logging.INFO,
+ level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler(f'./logs/{datetime.now().strftime("%Y%m%d-%H%M%S")}.log'),
@@ -93,6 +93,11 @@ def start() -> None:
# )
batch_test(
[
+ (test_power_write, 30, 2),
+ (test_dev_info_read, 10, 0),
+ (test_mode_read, 10, 0),
+
+ (test_dev_info_write, 30, 1),
(test_address_read, 30, 0),
(test_address_write, 30, 1),
(test_dev_info_write, 30, 2),
@@ -296,7 +301,6 @@ def test_power_write(controller: c.ControllerInterface) -> bool:
random.randint(voltage_min, voltage_max),
]
-
currents_ma = [currents[i] * 0.001 for i in range(4)]
currents_controller = [int(currents[i] * 0.1) for i in range(4)]
@@ -307,7 +311,7 @@ def test_power_write(controller: c.ControllerInterface) -> bool:
ka = [powers[i] / p_sum for i in range(4)]
km = min(p_poss / p_sum, 1)
Amax = int(km * pwm_max)
- kn = [min(int(ka[i] * 256), 255) for i in range(4)] # TODO check if 255
+ kn = [min(int(ka[i] * 256), 255) for i in range(4)]
info(f'Currents: {currents}')
info(f'Currents controller: {currents_controller}')
@@ -325,9 +329,9 @@ def test_power_write(controller: c.ControllerInterface) -> bool:
info(f'Kn: {kn}')
data = (currents_controller + voltages_controller + [p_poss] + kn
- + [Amax.to_bytes(2, byteorder='big')[0], Amax.to_bytes(2, byteorder='big')[1]]
+ + [Amax.to_bytes(2, byteorder='big')[0], Amax.to_bytes(2, byteorder='big')[1]]
# + [kn[2], kn[3]] # TODO баг в прошивке
- )
+ )
info(f'Writing powers: {data}...')
res = controller.writePowers(currents_controller, voltages_controller, p_poss, kn, Amax)
@@ -355,4 +359,3 @@ def test_power_write(controller: c.ControllerInterface) -> bool:
error('Failed to read powers')
return False
-
diff --git a/ui/ControllerUI/ControllerUI.pyproject b/ui/ControllerUI/ControllerUI.pyproject
index bed7562..ffc0bb3 100644
--- a/ui/ControllerUI/ControllerUI.pyproject
+++ b/ui/ControllerUI/ControllerUI.pyproject
@@ -4,6 +4,7 @@
"currents.ui",
"question mark.png",
"power.ui",
- "all.ui"
+ "all.ui",
+ "table.ui"
]
}
diff --git a/ui/ControllerUI/ControllerUI.pyproject.user b/ui/ControllerUI/ControllerUI.pyproject.user
index 7fbd86c..c36e90b 100644
--- a/ui/ControllerUI/ControllerUI.pyproject.user
+++ b/ui/ControllerUI/ControllerUI.pyproject.user
@@ -1,6 +1,6 @@
-
+
EnvironmentId
diff --git a/ui/ControllerUI/table.ui b/ui/ControllerUI/table.ui
new file mode 100644
index 0000000..99353cd
--- /dev/null
+++ b/ui/ControllerUI/table.ui
@@ -0,0 +1,19 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Form
+
+
+
+
+