Added powers to controller
This commit is contained in:
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
10
.idea/PowerUI.iml
generated
Normal file
10
.idea/PowerUI.iml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/PowerUI.iml" filepath="$PROJECT_DIR$/.idea/PowerUI.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
467
controller/controller.py
Normal file
467
controller/controller.py
Normal file
@ -0,0 +1,467 @@
|
||||
import glob
|
||||
import sys
|
||||
import time
|
||||
|
||||
from serial import *
|
||||
|
||||
|
||||
class Controller:
|
||||
def __init__(self, controller_id: int, programmator_id: int):
|
||||
self.id = controller_id
|
||||
self.prog_id = programmator_id
|
||||
self.ser = Serial()
|
||||
self.packet_id = 0
|
||||
self.checksum = 0
|
||||
self.buffer = bytearray()
|
||||
self.read_buffer = bytearray()
|
||||
|
||||
def connect(self, port: str) -> None:
|
||||
self.ser = Serial(
|
||||
port, baudrate=250_000,
|
||||
bytesize=8, parity='N', stopbits=2, timeout=0,
|
||||
xonxoff=False, rtscts=False
|
||||
|
||||
)
|
||||
print(f'Connected to {port}')
|
||||
|
||||
def close(self) -> None:
|
||||
self.ser.close()
|
||||
print(f'Closed connection to {self.ser.port}')
|
||||
|
||||
def writeBytes(self, bytesToWrite: bytes) -> None:
|
||||
for b in bytesToWrite:
|
||||
self.buffer.append(b)
|
||||
self.checksum += b
|
||||
|
||||
def readBytes(self, cnt: int) -> bytes:
|
||||
buf = self.ser.read(cnt)
|
||||
for b in buf:
|
||||
self.read_buffer.append(b)
|
||||
|
||||
return buf
|
||||
|
||||
def flush(self) -> None:
|
||||
self.ser.write(self.buffer)
|
||||
print(self.buffer)
|
||||
self.buffer.clear()
|
||||
self.checksum = 0
|
||||
|
||||
def validateChecksum(self, checksum: int) -> bool:
|
||||
s = 0
|
||||
for b in self.read_buffer:
|
||||
s += b
|
||||
return checksum == s
|
||||
|
||||
def sendCondition(self) -> None:
|
||||
self.ser.baudrate = 250_000 / 5
|
||||
self.ser.write(b'\x00')
|
||||
self.ser.baudrate = 250_000
|
||||
|
||||
def sendPacketHead(self, packetLen: int) -> None:
|
||||
self.writeBytes(b'\xCC\x01')
|
||||
self.writeBytes(packetLen.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(self.id.to_bytes(6, byteorder='big'))
|
||||
self.writeBytes(self.prog_id.to_bytes(6, byteorder='big'))
|
||||
self.writeBytes(self.packet_id.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(b'\x01')
|
||||
self.writeBytes(bytes(3))
|
||||
|
||||
def waitForStartBytes(self) -> bool:
|
||||
x = 0
|
||||
counter = 10
|
||||
while counter > 0:
|
||||
prev = x
|
||||
time.sleep(0.05)
|
||||
x = self.readBytes(1)
|
||||
if x == prev:
|
||||
counter = counter - 1
|
||||
|
||||
print(f'x: {x}, prev: {prev}')
|
||||
|
||||
if prev == b'\xCC' and x == b'\x01':
|
||||
break
|
||||
|
||||
if counter == 0:
|
||||
print('No data!')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# CURRENTS #
|
||||
def sendWriteCurrentsPacket(self, currents: list) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + len(currents) + 2)
|
||||
self.writeBytes(b'\x30\xBE\xEE')
|
||||
self.writeBytes(len(currents).to_bytes(1, byteorder='big'))
|
||||
for current in currents:
|
||||
self.writeBytes(current.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def sendReadCurrentsPacket(self) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 2)
|
||||
self.writeBytes(b'\x20\xBE\xEF\x00')
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readCurrentsPacket(self) -> list:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return []
|
||||
|
||||
self.readBytes(21)
|
||||
|
||||
valueCount = self.readBytes(1)
|
||||
values = []
|
||||
for _ in range(valueCount[0]):
|
||||
values.append(self.readBytes(1)[0])
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return []
|
||||
|
||||
return values
|
||||
|
||||
# DMX addresses #
|
||||
def sendWriteAddressPacket(self, address: int) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 2 + 2)
|
||||
self.writeBytes(b'\x30\xBF\x20\x02')
|
||||
self.writeBytes(address.to_bytes(2, byteorder='big'))
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readWriteAddressPacket(self) -> bool:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return False
|
||||
|
||||
self.readBytes(22)
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def sendReadAddressPacket(self) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 2)
|
||||
self.writeBytes(b'\x20\xBF\x20\x00')
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
self.flush()
|
||||
|
||||
def readAddressPacket(self) -> int:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return -1
|
||||
|
||||
self.readBytes(22)
|
||||
address = int.from_bytes(self.readBytes(2), byteorder='big')
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return -1
|
||||
|
||||
return address
|
||||
|
||||
# Device info #
|
||||
def sendReadDevInfoPacket(self) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 2)
|
||||
self.writeBytes(b'\x20\xBF\x00\x00')
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readDevInfoPacket(self) -> tuple:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return ()
|
||||
|
||||
self.readBytes(32)
|
||||
mode_footprint = int.from_bytes(self.readBytes(2), byteorder='big')
|
||||
mode_number = int.from_bytes(self.readBytes(1), byteorder='big')
|
||||
mode_count = int.from_bytes(self.readBytes(1), byteorder='big')
|
||||
address = int.from_bytes(self.readBytes(2), byteorder='big')
|
||||
self.readBytes(3)
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return ()
|
||||
|
||||
return mode_footprint, mode_number, mode_count, address
|
||||
|
||||
# Modes #
|
||||
def sendWriteModePacket(self, mode_number: int) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 1 + 2)
|
||||
self.writeBytes(b'\x30\xBF\x10\x01')
|
||||
self.writeBytes(mode_number.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readWriteModePacket(self) -> bool:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return False
|
||||
|
||||
self.readBytes(22)
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def sendReadModeInfoPacket(self, mode_number: int) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 1 + 2)
|
||||
self.writeBytes(b'\x20\xBF\x11\x01')
|
||||
self.writeBytes(mode_number.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readModeInfoPacket(self) -> tuple:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return ()
|
||||
|
||||
self.readBytes(23)
|
||||
mode_footprint = int.from_bytes(self.readBytes(2), byteorder='big')
|
||||
mode_name = self.readBytes(12).decode()
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return ()
|
||||
|
||||
return mode_footprint, mode_name
|
||||
|
||||
# Power #
|
||||
def sendWritePowerPacket(self, powers: list) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + len(powers) + 2)
|
||||
self.writeBytes(b'\x30\xBE\x11')
|
||||
self.writeBytes(len(powers).to_bytes(1, byteorder='big'))
|
||||
for data in powers:
|
||||
self.writeBytes(data.to_bytes(1, byteorder='big'))
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def sendReadPowerPacket(self) -> None:
|
||||
self.sendCondition()
|
||||
self.sendPacketHead(1 + 6 * 2 + 1 + 4 + 4 + 2)
|
||||
self.writeBytes(b'\x20\xBE\x10\x00')
|
||||
self.writeBytes(self.checksum.to_bytes(2, byteorder='big'))
|
||||
|
||||
self.flush()
|
||||
|
||||
def readPowerPacket(self) -> list:
|
||||
self.read_buffer.clear()
|
||||
|
||||
if not self.waitForStartBytes():
|
||||
return []
|
||||
|
||||
self.readBytes(21)
|
||||
|
||||
valueCount = self.readBytes(1)
|
||||
values = []
|
||||
for _ in range(valueCount[0]):
|
||||
values.append(self.readBytes(1)[0])
|
||||
|
||||
chsum = int.from_bytes(self.ser.read(2), byteorder='big')
|
||||
if not self.validateChecksum(chsum):
|
||||
print('Invalid checksum!')
|
||||
return []
|
||||
|
||||
return values
|
||||
|
||||
|
||||
|
||||
class ControllerInterface:
|
||||
def __init__(self, controller: Controller):
|
||||
self.controller = controller
|
||||
|
||||
def connect(self, port: str) -> None:
|
||||
self.controller.connect(port)
|
||||
|
||||
def close(self) -> None:
|
||||
self.controller.close()
|
||||
|
||||
# Currents #
|
||||
def writeCurrents(self, currents: list) -> bool:
|
||||
try:
|
||||
self.controller.sendWriteCurrentsPacket(currents)
|
||||
time.sleep(0.002)
|
||||
self.controller.sendWriteCurrentsPacket(currents)
|
||||
time.sleep(1)
|
||||
self.controller.sendWriteCurrentsPacket(currents)
|
||||
time.sleep(0.002)
|
||||
self.controller.sendWriteCurrentsPacket(currents)
|
||||
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return True
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def readCurrents(self) -> list:
|
||||
try:
|
||||
self.controller.sendReadCurrentsPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return self.controller.readCurrentsPacket()
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return []
|
||||
|
||||
# DMX Addresses #
|
||||
def writeAddress(self, address: int) -> bool:
|
||||
try:
|
||||
self.controller.sendWriteAddressPacket(address)
|
||||
self.controller.readWriteAddressPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return True
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def readAddress(self) -> int:
|
||||
try:
|
||||
self.controller.sendReadAddressPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return self.controller.readAddressPacket()
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return -1
|
||||
|
||||
# Device info #
|
||||
def readDeviceInfo(self) -> tuple:
|
||||
try:
|
||||
self.controller.sendReadDevInfoPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return self.controller.readDevInfoPacket()
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return ()
|
||||
|
||||
# Modes #
|
||||
def writeMode(self, mode_number: int) -> bool:
|
||||
try:
|
||||
self.controller.sendWriteModePacket(mode_number)
|
||||
self.controller.readWriteModePacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return True
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def read_mode(self, mode_index: int) -> tuple:
|
||||
try:
|
||||
self.controller.sendReadModeInfoPacket(mode_index + 1)
|
||||
return self.controller.readModeInfoPacket()
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return ()
|
||||
|
||||
def readModes(self) -> list:
|
||||
try:
|
||||
self.controller.sendReadDevInfoPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
info = self.controller.readDevInfoPacket()
|
||||
if not info:
|
||||
return []
|
||||
|
||||
mode_footprint, mode_number, mode_count, address = info
|
||||
modes = []
|
||||
for i in range(mode_count):
|
||||
mode = self.read_mode(i)
|
||||
if not mode:
|
||||
return []
|
||||
modes.append(mode)
|
||||
time.sleep(0.05)
|
||||
|
||||
i = 0
|
||||
for m in modes:
|
||||
i = i + 1
|
||||
print(f'Mode {i}: {m}')
|
||||
|
||||
return modes
|
||||
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return []
|
||||
|
||||
# Powers #
|
||||
def writePowers(self, currents: list, voltages: list, power: int, koeffs: list, a_max: int) -> bool:
|
||||
try:
|
||||
data = currents + voltages + [power] + koeffs + [a_max]
|
||||
print(f'Data: {data}')
|
||||
|
||||
self.controller.sendWritePowerPacket(data)
|
||||
time.sleep(0.002)
|
||||
self.controller.sendWritePowerPacket(data)
|
||||
time.sleep(1)
|
||||
self.controller.sendWritePowerPacket(data)
|
||||
time.sleep(0.002)
|
||||
self.controller.sendWritePowerPacket(data)
|
||||
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return True
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return False
|
||||
|
||||
def readPowers(self) -> list:
|
||||
try:
|
||||
self.controller.sendReadPowerPacket()
|
||||
self.controller.packet_id = self.controller.packet_id + 1
|
||||
|
||||
return self.controller.readPowerPacket()
|
||||
except SerialException as e:
|
||||
print(e)
|
||||
return []
|
||||
|
||||
# source: https://stackoverflow.com/a/14224477
|
||||
@staticmethod
|
||||
def readComPorts() -> list:
|
||||
ports = []
|
||||
if sys.platform.startswith('win'):
|
||||
ports = ['COM%s' % (i + 1) for i in range(256)]
|
||||
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
|
||||
ports = glob.glob('/dev/tty[A-Za-z]*')
|
||||
|
||||
result = []
|
||||
for port in ports:
|
||||
try:
|
||||
s = Serial(port)
|
||||
s.close()
|
||||
result.append(port)
|
||||
except (OSError, SerialException):
|
||||
pass
|
||||
return result
|
467
interface.py
Normal file
467
interface.py
Normal file
@ -0,0 +1,467 @@
|
||||
import base64
|
||||
import time
|
||||
from threading import Timer
|
||||
|
||||
import controller.controller as controller
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
controller = controller.ControllerInterface(controller.Controller(0xFFFFFFFFFFFF, 0x112233445542))
|
||||
|
||||
win = tk.Tk()
|
||||
|
||||
device_choice = ttk.Combobox()
|
||||
selected_port = tk.StringVar()
|
||||
|
||||
dmx_address_field = ttk.Entry()
|
||||
personality_combobox = ttk.Combobox(win)
|
||||
|
||||
failed_to_read_style = ttk.Style(win)
|
||||
failed_to_read_style.configure('failedToRead.TEntry', foreground="red")
|
||||
|
||||
status = tk.StringVar()
|
||||
status.set('Пожалуйста, выберите COM порт')
|
||||
saved_status = ''
|
||||
|
||||
current_entries = list()
|
||||
modes = []
|
||||
|
||||
connected = False
|
||||
|
||||
last_click_time = time.time()
|
||||
|
||||
check = tk.IntVar()
|
||||
|
||||
|
||||
def setStatus(new_status: str):
|
||||
status.set(new_status)
|
||||
win.update()
|
||||
win.update_idletasks()
|
||||
|
||||
|
||||
def resetStatus() -> None:
|
||||
global saved_status
|
||||
setStatus(saved_status)
|
||||
|
||||
|
||||
def setTempStatus(new_status: str, reset_time: float):
|
||||
global saved_status
|
||||
if status.get() != new_status:
|
||||
saved_status = status.get()
|
||||
setStatus(new_status)
|
||||
Timer(reset_time, resetStatus).start()
|
||||
|
||||
|
||||
def resetAllStyles() -> None:
|
||||
for e in current_entries:
|
||||
e.configure(style='TEntry')
|
||||
dmx_address_field.configure(style='TEntry')
|
||||
personality_combobox.configure(style='TCombobox')
|
||||
|
||||
|
||||
def clearAllFields() -> None:
|
||||
dmx_address_field.delete(0, tk.END)
|
||||
personality_combobox.set('')
|
||||
personality_combobox.configure(values=[])
|
||||
for e in current_entries:
|
||||
e.delete(0, tk.END)
|
||||
|
||||
|
||||
def comPortSelected(event) -> None:
|
||||
global connected
|
||||
|
||||
connected = False
|
||||
resetAllStyles()
|
||||
clearAllFields()
|
||||
|
||||
setStatus(f'Подключение к {selected_port.get()}...')
|
||||
|
||||
controller.close()
|
||||
controller.connect(selected_port.get())
|
||||
|
||||
print(selected_port.get())
|
||||
|
||||
if readControllerCurrentsAndData():
|
||||
setStatus(f'Выбранный порт: {selected_port.get()}')
|
||||
connected = True
|
||||
else:
|
||||
setStatus(f'Не удалось подключиться к {selected_port.get()}')
|
||||
device_choice.set('')
|
||||
connected = False
|
||||
controller.close()
|
||||
|
||||
|
||||
def refreshComPorts(event) -> None:
|
||||
device_choice['values'] = controller.readComPorts()
|
||||
|
||||
|
||||
def connectionLost() -> None:
|
||||
controller.close()
|
||||
setStatus('Пожалуйста, выберите порт')
|
||||
setTempStatus(f'Подключение к {controller.controller.ser.port} потеряно!', 2)
|
||||
resetAllStyles()
|
||||
device_choice.set('')
|
||||
clearAllFields()
|
||||
|
||||
|
||||
def readControllerCurrentsAndData() -> bool:
|
||||
setStatus(f'Чтение токов {controller.controller.ser.port}...')
|
||||
values = controller.readCurrents()
|
||||
|
||||
if not values:
|
||||
for e in current_entries:
|
||||
e.configure(style='failedToRead.TEntry')
|
||||
else:
|
||||
index = 0
|
||||
for e in current_entries:
|
||||
e.configure(style='TEntry')
|
||||
e.delete(0, tk.END)
|
||||
e.insert(tk.END, values[index] * 10)
|
||||
index = index + 1
|
||||
|
||||
setStatus(f'Чтение адреса {controller.controller.ser.port}...')
|
||||
address = controller.readAddress()
|
||||
|
||||
if address == -1:
|
||||
dmx_address_field.configure(style='failedToRead.TEntry')
|
||||
else:
|
||||
dmx_address_field.configure(style='TEntry')
|
||||
dmx_address_field.delete(0, tk.END)
|
||||
dmx_address_field.insert(tk.END, str(address))
|
||||
|
||||
global modes
|
||||
modes = []
|
||||
|
||||
setStatus(f'Чтение режимов {controller.controller.ser.port}...')
|
||||
dev_info = controller.readDeviceInfo()
|
||||
if dev_info:
|
||||
mode_count = dev_info[2]
|
||||
|
||||
for index in range(mode_count):
|
||||
setStatus(f'Чтение режима {index + 1} {controller.controller.ser.port}...')
|
||||
mode = controller.read_mode(index)
|
||||
if not mode:
|
||||
modes = []
|
||||
break
|
||||
modes.append(mode)
|
||||
time.sleep(0.05)
|
||||
|
||||
if not modes:
|
||||
personality_combobox.set('ERROR')
|
||||
else:
|
||||
personality_combobox.configure(style='TCombobox')
|
||||
personality_combobox.configure(values=modes)
|
||||
|
||||
mode_names = []
|
||||
for index, m in enumerate(modes):
|
||||
mode_names.append(f'{index + 1}:{m[1]}')
|
||||
|
||||
personality_combobox.configure(values=mode_names)
|
||||
personality_combobox.set(mode_names[dev_info[1] - 1])
|
||||
|
||||
return values or address != -1 or modes
|
||||
|
||||
|
||||
def writeCurrentsAddressAndMode() -> None:
|
||||
toWrite = []
|
||||
for e in current_entries:
|
||||
x = e.get()
|
||||
if x != "":
|
||||
toWrite.append(int(x) // 10)
|
||||
else:
|
||||
toWrite.append(0)
|
||||
|
||||
setStatus(f'Запись токов в {controller.controller.ser.port}...')
|
||||
print(f'Writing... {toWrite}')
|
||||
if not controller.writeCurrents(toWrite):
|
||||
connectionLost()
|
||||
return
|
||||
|
||||
setStatus(f'Запись адреса в {controller.controller.ser.port}...')
|
||||
if dmx_address_field.get() != "" and not controller.writeAddress(int(dmx_address_field.get())):
|
||||
connectionLost()
|
||||
return
|
||||
|
||||
setStatus(f'Запись режима в {controller.controller.ser.port}...')
|
||||
if not controller.writeMode(personality_combobox.current() + 1):
|
||||
connectionLost()
|
||||
return
|
||||
|
||||
if check.get() == 0:
|
||||
setStatus(f'Выбранный порт: {controller.controller.ser.port}')
|
||||
setTempStatus(f'Данные были записаны в {controller.controller.ser.port}', 1)
|
||||
else:
|
||||
time.sleep(0.5)
|
||||
dmx_save = dmx_address_field.get()
|
||||
pers_save = personality_combobox.get()
|
||||
currents_save = []
|
||||
for entry in current_entries:
|
||||
currents_save.append(entry.get())
|
||||
|
||||
global modes
|
||||
setStatus(f'Чтение адреса: {controller.controller.ser.port}...')
|
||||
dmx_real = controller.readAddress()
|
||||
setStatus(f'Чтение режима: {controller.controller.ser.port}...')
|
||||
dev_info = controller.readDeviceInfo()
|
||||
pers_real = f'{dev_info[1]}:{modes[dev_info[1] - 1][1]}'
|
||||
setStatus(f'Чтение токов: {controller.controller.ser.port}...')
|
||||
time.sleep(1)
|
||||
currents_real = controller.readCurrents()
|
||||
|
||||
if dmx_real == -1 or not currents_real or not dev_info:
|
||||
connectionLost()
|
||||
return
|
||||
|
||||
failed = False
|
||||
if dmx_save == "" or int(dmx_save) != dmx_real:
|
||||
failed = True
|
||||
dmx_address_field.configure(style='failedToRead.TEntry')
|
||||
else:
|
||||
dmx_address_field.configure(style='TEntry')
|
||||
|
||||
if pers_save != pers_real:
|
||||
failed = True
|
||||
personality_combobox.set('ERROR')
|
||||
else:
|
||||
personality_combobox.configure(style='TCombobox')
|
||||
|
||||
for index in range(4):
|
||||
if currents_save[index] == "" or int(currents_save[index]) != currents_real[index] * 10:
|
||||
failed = True
|
||||
current_entries[index].configure(style='failedToRead.TEntry')
|
||||
else:
|
||||
current_entries[index].configure(style='TEntry')
|
||||
|
||||
setStatus(f'Выбранный порт: {controller.controller.ser.port}')
|
||||
if failed:
|
||||
setTempStatus('Обнаружены ошибки, повторите запись', 1)
|
||||
else:
|
||||
setTempStatus('Проверка прошла успешно', 1)
|
||||
|
||||
|
||||
def show_personality_callback() -> None:
|
||||
popup = tk.Toplevel(win)
|
||||
popup.grab_set()
|
||||
popup.resizable(False, False)
|
||||
popup.title('Информация о режимах')
|
||||
|
||||
columns = (
|
||||
("Номер", 50),
|
||||
("Название", 100),
|
||||
("Каналов", 65),
|
||||
("Плавность", 80),
|
||||
("Поведение без DMX", 160),
|
||||
("Описание", 200),
|
||||
)
|
||||
|
||||
for index, c in enumerate(columns):
|
||||
label = tk.Label(popup, text=f'{c[0]}', width=c[1] // 8, borderwidth=1, relief="solid")
|
||||
label.grid(row=0, column=index, sticky="nsew")
|
||||
|
||||
mode_info = [
|
||||
(1, "1CH MAX FST", 'нет', 'максимальная яркость'),
|
||||
(2, "1CH MAX SLO", 'есть', 'максимальная яркость'),
|
||||
(3, "1CH KEEP FST", 'нет', 'сохранение кадра'),
|
||||
(4, "1CH KEEP SLO", 'есть', 'сохранение кадра'),
|
||||
(5, "1CH DARK FST", 'нет', 'гашение'),
|
||||
(6, "1CH DARK SLO", 'есть', 'гашение'),
|
||||
(7, "2CH MAX FST", 'нет', 'максимальная яркость'),
|
||||
(8, "2CH MAX SLO", 'есть', 'максимальная яркость'),
|
||||
(9, "2CH KEEP FST", 'нет', 'сохранение кадра'),
|
||||
(10, "2CH KEEP SLO", 'есть', 'сохранение кадра'),
|
||||
(11, "2CH DARK FST", 'нет', 'гашение'),
|
||||
(12, "2CH DARK FST", 'есть', 'гашение'),
|
||||
(13, "3 COLOR CHG", 'нет', 'сохранение кадра'),
|
||||
(14, "4 COLOR CHG", 'нет', 'сохранение кадра'),
|
||||
(15, "4CH MAXA FST", 'нет', 'максимальная яркость всех'),
|
||||
(16, "4CH MAXA SLO", 'есть', 'максимальная яркость всех'),
|
||||
(17, "4CH MAXW FST", 'нет', 'максимальная яркость белый'),
|
||||
(18, "4CH MAXW SLO", 'есть', 'максимальная яркость.белый'),
|
||||
(19, "4CH KEEP FST", 'нет', 'сохранение кадра'),
|
||||
(20, "4CH KEEP SLO", 'есть', 'сохранение кадра'),
|
||||
(21, "4CH DARK FST", 'нет', 'гашение'),
|
||||
(22, "4CH DARK SLO", 'есть', 'гашение'),
|
||||
(23, "4CH RB3S FST", 'нет', 'RGB перелив синхронизированный'),
|
||||
(24, "4CH RB3S SLO", 'есть', 'RGB перелив синхронизированный'),
|
||||
(25, "4CH RB3A FST", 'нет', 'RGB перелив индивидуальный'),
|
||||
(26, "4CH RB3A SLO", 'есть', 'RGB перелив индивидуальный'),
|
||||
(27, "4CH RB4S FST", 'нет', 'RGBW перелив синхронизированный'),
|
||||
(28, "4CH RB4S SLO", 'есть', 'RGBW перелив синхронизированный'),
|
||||
(29, "4CH RB4A FST", 'нет', ' RGBW перелив индивидуальный '),
|
||||
(30, "4CH RB4A SLO", 'есть', 'RGBW перелив индивидуальный '),
|
||||
]
|
||||
|
||||
for index in range(len(mode_info)):
|
||||
for j in range(2):
|
||||
label = tk.Label(popup, text=f'{mode_info[index][j]}', borderwidth=1, relief="solid")
|
||||
label.grid(row=index + 1, column=j, sticky="nsew")
|
||||
|
||||
for index in range(len(mode_info)):
|
||||
for j in range(2, 4):
|
||||
label = tk.Label(popup, text=f'{mode_info[index][j]}', borderwidth=1, relief="solid")
|
||||
label.grid(row=index + 1, column=j + 1, sticky="nsew")
|
||||
|
||||
descr = [
|
||||
('Режим одноканального диммера. \nВсе выходные каналы работают параллельно', 6),
|
||||
('Режим двухканального диммера. \nВыходные каналы 1,2 и 3,4 работают параллельно', 6),
|
||||
(
|
||||
'Режим RGB перелива с изменением яркости каждого канала. \nW не управляется. Последний канал – скорость перелива. ',
|
||||
1),
|
||||
(
|
||||
'Режим RGB перелива с изменением яркости каждого канала. \nW управляется отдельно по DMX. Последний канал – скорость перелива',
|
||||
1),
|
||||
('Режим четырехканального диммера. \nВсе выходные каналы работают независимо', 16)
|
||||
]
|
||||
|
||||
delta = 0
|
||||
for index, c in enumerate(descr):
|
||||
label = tk.Label(popup, text=f'{c[0]}', borderwidth=1, relief="solid")
|
||||
label.grid(row=delta + 1, column=5, sticky="nsew", rowspan=c[1])
|
||||
delta += c[1]
|
||||
|
||||
chan = [(1, 6), (2, 6), (4, 1), (5, 1), (4, 16)]
|
||||
delta = 0
|
||||
for index, c in enumerate(chan):
|
||||
label = tk.Label(popup, text=f'{c[0]}', borderwidth=1, relief="solid")
|
||||
label.grid(row=delta + 1, column=2, sticky="nsew", rowspan=c[1])
|
||||
delta += c[1]
|
||||
|
||||
|
||||
def readCallback() -> None:
|
||||
global last_click_time
|
||||
if time.time() - last_click_time < 1:
|
||||
return
|
||||
|
||||
last_click_time = time.time()
|
||||
|
||||
if connected:
|
||||
if not readControllerCurrentsAndData():
|
||||
connectionLost()
|
||||
return
|
||||
|
||||
setStatus(f'Выбранный порт: {controller.controller.ser.port}')
|
||||
setTempStatus(f'Токи прочитаны, порт: {controller.controller.ser.port}', 1)
|
||||
else:
|
||||
setTempStatus('Порт не выбран!', 1)
|
||||
|
||||
|
||||
def writeCallback() -> None:
|
||||
global last_click_time
|
||||
if time.time() - last_click_time < 2:
|
||||
return
|
||||
|
||||
last_click_time = time.time()
|
||||
|
||||
if connected:
|
||||
writeCurrentsAddressAndMode()
|
||||
else:
|
||||
setTempStatus('Порт не выбран!', 1)
|
||||
|
||||
|
||||
def validate_dmx_address_entry(entry_data: str) -> bool:
|
||||
if entry_data.isdigit():
|
||||
return 1 <= int(entry_data) <= 512
|
||||
|
||||
if not entry_data:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def validate_current_entry(entry_data: str) -> bool:
|
||||
if entry_data.isdigit():
|
||||
return int(entry_data) <= 2500
|
||||
|
||||
if not entry_data:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def finish():
|
||||
win.destroy()
|
||||
controller.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
controller.writePowers([100, 200, 300, 400], [300, 2000, 2341, 10], 49, [100, 240, 30, 239], 52)
|
||||
|
||||
win.configure(width=270, height=350)
|
||||
win.resizable(False, False)
|
||||
win.title("Controllers")
|
||||
win.protocol("WM_DELETE_WINDOW", finish)
|
||||
|
||||
com_port_label = ttk.Label(win)
|
||||
com_port_label.configure(font="{Arial} 12 {}", text='COM порт:')
|
||||
com_port_label.place(anchor="nw", relx=0.05, rely=0.05)
|
||||
|
||||
device_choice = ttk.Combobox(win, name="device_choice", textvariable=selected_port)
|
||||
device_choice.configure(justify="center")
|
||||
device_choice.place(anchor="nw", relx=0.40, rely=0.04, relwidth=0.4, relheight=0.08)
|
||||
device_choice['values'] = controller.readComPorts()
|
||||
device_choice['state'] = 'readonly'
|
||||
device_choice.bind('<<ComboboxSelected>>', comPortSelected)
|
||||
device_choice.bind('<Enter>', refreshComPorts)
|
||||
|
||||
dmx_address_label = ttk.Label(win)
|
||||
dmx_address_label.configure(font="{Arial} 12 {}", text='DMX Адрес:')
|
||||
dmx_address_label.place(anchor="nw", relx=0.05, rely=0.15)
|
||||
|
||||
validate_callback = win.register(validate_dmx_address_entry)
|
||||
dmx_address_field = ttk.Entry(win)
|
||||
dmx_address_field.configure(font="{Arial} 12 {}", justify="center", validate='key',
|
||||
validatecommand=(validate_callback, '%P'))
|
||||
dmx_address_field.place(anchor="nw", relx=0.40, rely=0.15, relwidth=0.2, relheight=0.06)
|
||||
|
||||
personality_label = ttk.Label(win)
|
||||
personality_label.configure(font="{Arial} 12 {}", text='Personality:')
|
||||
personality_label.place(anchor="nw", relx=0.05, rely=0.26)
|
||||
|
||||
image = base64.b64decode(
|
||||
b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAnxJREFUOE+lk01IVUEUx/9zZ+Z+Pg2DsIWiSQaBYrgoREqkyFUEQWihqyQR+6KtywrbVERUhkQSCG1q1aJNkYmiqSUUQQuJapFZfvR8+nz3Y07cufLSPlYd7r0wc87/N/9zZ4bhP4P9S3/wzAgFyoCKFAgBXtxu+GvtH5NN5yZpMcsRKQD5LIPgBBVm8bKvboMmPzh0fpgWcx6yOQIzGOqrCrGzzMGqrzD8LoMPX/3ErAow3lu7Dr3Ww96uKYrFnstxrasc1RUuokhBKUIUEXoezODpm2VtS0U+xm/u0hD9iW1/zxgAY/GDnvZScM7wZCKNw3WFqCqz8fmbj/ZbMzofR255Dq9665Phno4pinumeMQYXJvDV0BEDNXlNq6eKMaX+RCtN2JAsgiiABPXqxiL//Zc1knMGAzEGHIhIYgYGGe4cHwL9td4GBjKoH8ok3cAAiavVDLWeGqM0jlLr7ziK2R9giEMCJOjs6kIbY2FePvJR/fDNCK9etIDEWH04jbGGjpHaCl0sbAUIlTQQmEK3fedzmLkAoWT/T+wFDJwYegdSnaD8Ly7hLF9HYP0cd5FEIslh7SS90BNCs31KUzPRrg75kPGYMlh8AQgoPD47NbET+mRMVIG10UaYAuYtkCBJ0BSwHQETEvofOwiDmM1g0enSxPAjqODtMK8NUAirq2wcflYEZ69D3D/tYLlSj2vWzSAgRbv1zmIIRXNo0SmDdPiunB3pYNLLUUYng7RNx7B9kxYnoTjSrD0LO61lWwExJDtrRPEbUsDTFuieLNEzhCQjtQAr0DC9Bfy4vxJXH8jK9uGyE5tguk6MB0JyxVIeQKmEeief7+9PwEI7uIRQxNy9wAAAABJRU5ErkJgggAA')
|
||||
|
||||
personality_button = ttk.Button(win, command=show_personality_callback)
|
||||
question_image = tk.PhotoImage(data=image, format='png')
|
||||
personality_button.configure(compound="right", default="normal", image=question_image)
|
||||
personality_button.place(anchor="nw", relx=0.87, rely=0.246, relwidth=0.1)
|
||||
|
||||
personality_combobox_font = ('TkDefaultFont', 9)
|
||||
personality_combobox = ttk.Combobox(win, font=personality_combobox_font)
|
||||
personality_combobox.configure(
|
||||
exportselection=True,
|
||||
justify="left",
|
||||
state="readonly",
|
||||
takefocus=False)
|
||||
personality_combobox.place(anchor="nw", relx=0.40, rely=0.246, relwidth=0.45, relheight=0.085)
|
||||
|
||||
for i in range(4):
|
||||
channel_label = ttk.Label(win)
|
||||
channel_label.configure(font="{Arial} 12 {}", text=f'Ток канал {i + 1}:')
|
||||
channel_label.place(anchor="nw", relx=0.05, rely=0.38 + 0.08 * i)
|
||||
|
||||
validate_callback = win.register(validate_current_entry)
|
||||
ent = ttk.Entry(win)
|
||||
ent.configure(font="{Arial} 12 {}", justify="center", validate='key',
|
||||
validatecommand=(validate_callback, '%P'))
|
||||
ent.place(anchor="nw", relx=0.40, rely=0.38 + 0.08 * i, relwidth=0.2, relheight=0.06)
|
||||
current_entries.append(ent)
|
||||
|
||||
ma_label = ttk.Label(win)
|
||||
ma_label.configure(font="{Arial} 12 {}", text='mA')
|
||||
ma_label.place(anchor="nw", relx=0.65, rely=0.38 + 0.08 * i)
|
||||
|
||||
read_button = ttk.Button(win)
|
||||
read_button.configure(text='Чтение', command=readCallback)
|
||||
read_button.place(anchor="nw", relx=0.05, rely=0.7, relwidth=0.37)
|
||||
|
||||
write_button = ttk.Button(win)
|
||||
write_button.configure(text='Запись', command=writeCallback)
|
||||
write_button.place(anchor="nw", relx=0.6, rely=0.7, relwidth=0.37)
|
||||
|
||||
check_checkbox = ttk.Checkbutton(win, variable=check)
|
||||
check_checkbox.configure(text='Проверить')
|
||||
check_checkbox.place(anchor="n", relx=0.5, rely=0.8)
|
||||
|
||||
status_label = ttk.Label(win, textvariable=status)
|
||||
status_label.place(anchor="center", relx=0.5, rely=0.91)
|
||||
|
||||
win.mainloop()
|
16
main.py
Normal file
16
main.py
Normal file
@ -0,0 +1,16 @@
|
||||
# This is a sample Python script.
|
||||
|
||||
# Press Shift+F10 to execute it or replace it with your code.
|
||||
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
|
||||
|
||||
|
||||
def print_hi(name):
|
||||
# Use a breakpoint in the code line below to debug your script.
|
||||
print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint.
|
||||
|
||||
|
||||
# Press the green button in the gutter to run the script.
|
||||
if __name__ == '__main__':
|
||||
print_hi('PyCharm')
|
||||
|
||||
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
|
Reference in New Issue
Block a user