123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- #!/usr/bin/env python3
-
- # CaseLights Linux Qt System Tray client
- # depends on:
- # - python-pyqt5
- # - python-pyserial
-
- import subprocess
- import sys
- import os.path
- import threading
- import time
- import colorsys
- import serial, serial.tools, serial.tools.list_ports
- from PyQt5 import QtWidgets, QtGui, QtCore
- from PyQt5.QtWidgets import QSystemTrayIcon, QAction, QMenu
- from PyQt5.QtGui import QIcon, QPixmap, QCursor
- from PyQt5.QtCore import QCoreApplication, QSettings
-
- class CaseLights():
- name = "CaseLights"
- vendor = "xythobuz"
- version = "0.2"
-
- iconPath = "/usr/share/pixmaps/"
- iconName = "caselights_icon.png"
-
- staticColors = [
- [ "Off", "0", "0", "0", None ],
- [ "Red", "255", "0", "0", None ],
- [ "Green", "0", "255", "0", None ],
- [ "Blue", "0", "0", "255", None ],
- [ "White", "255", "255", "255", None ],
- ]
-
- slowFadeUpdateFreq = 5
- fastFadeUpdateFreq = 20
- cpuUsageUpdateFreq = 2
- fadeSaturation = 1.0
- fadeValue = 1.0
- fadeHueCounter = 0
-
- usedPort = None
- serial = None
- animation = None
- animationRunning = False
- menu = None
- portMenu = None
- portActions = None
- refreshAction = None
- quitAction = None
-
- def __init__(self):
- app = QtWidgets.QApplication(sys.argv)
- QCoreApplication.setApplicationName(self.name)
-
- if not QSystemTrayIcon.isSystemTrayAvailable():
- print("System Tray is not available on this platform!")
- sys.exit(0)
-
- self.readSettings()
- if self.usedPort is not None:
- self.connect()
-
- self.menu = QMenu()
-
- colorMenu = QMenu("&Colors")
- for color in self.staticColors:
- color[4] = QAction(color[0])
- colorMenu.addAction(color[4])
- colorMenu.triggered.connect(self.setStaticColor)
- self.menu.addMenu(colorMenu)
-
- animMenu = QMenu("&Animations")
- noFadeAction = QAction("Off")
- noFadeAction.triggered.connect(self.animOff)
- animMenu.addAction(noFadeAction)
- slowFadeAction = QAction("Slow Fade")
- slowFadeAction.triggered.connect(self.slowFadeOn)
- animMenu.addAction(slowFadeAction)
- fastFadeAction = QAction("Fast Fade")
- fastFadeAction.triggered.connect(self.fastFadeOn)
- animMenu.addAction(fastFadeAction)
- self.menu.addMenu(animMenu)
-
- visualMenu = QMenu("&Visualizations")
- noVisualAction = QAction("Off")
- noVisualAction.triggered.connect(self.animOff)
- visualMenu.addAction(noVisualAction)
- cpuUsageAction = QAction("CPU Usage")
- cpuUsageAction.triggered.connect(self.cpuUsageOn)
- visualMenu.addAction(cpuUsageAction)
- self.menu.addMenu(visualMenu)
-
- lightMenu = QMenu("&UV-Light")
- lightOnAction = QAction("O&n")
- lightOnAction.triggered.connect(self.lightsOn)
- lightMenu.addAction(lightOnAction)
- lightOffAction = QAction("O&ff")
- lightOffAction.triggered.connect(self.lightsOff)
- lightMenu.addAction(lightOffAction)
- self.menu.addMenu(lightMenu)
-
- self.refreshSerialPorts()
-
- self.quitAction = QAction("&Quit")
- self.quitAction.triggered.connect(self.exit)
- self.menu.addAction(self.quitAction)
-
- iconPathName = ""
- if os.path.isfile(self.iconName):
- iconPathName = self.iconName
- elif os.path.isfile(self.iconPath + self.iconName):
- iconPathName = self.iconPath + self.iconName
- else:
- print("no icon found")
-
- icon = QIcon()
- if iconPathName != "":
- pic = QPixmap(32, 32)
- pic.load(iconPathName)
- icon = QIcon(pic)
-
- trayIcon = QSystemTrayIcon(icon)
- trayIcon.setToolTip(self.name + " " + self.version)
- trayIcon.setContextMenu(self.menu)
- trayIcon.activated.connect(self.showHide)
- trayIcon.setVisible(True)
-
- sys.exit(app.exec_())
-
- def showHide(self, activationReason):
- if activationReason == QSystemTrayIcon.Trigger:
- self.menu.popup(QCursor.pos())
-
- def exit(self):
- if self.serial is not None:
- if self.serial.is_open:
- print("stopping animations")
- self.animOff()
- print("turning off lights")
- self.serial.write(b'RGB 0 0 0\n')
- self.serial.write(b'UV 0\n')
- print("closing connection")
- self.serial.close()
- QCoreApplication.quit()
-
- def readSettings(self):
- settings = QSettings(self.vendor, self.name)
- self.usedPort = settings.value("serial_port")
- if self.usedPort is not None:
- print("serial port stored: " + self.usedPort)
- else:
- print("no serial port stored")
-
- def writeSettings(self):
- settings = QSettings(self.vendor, self.name)
- settings.setValue("serial_port", self.usedPort)
- if self.usedPort is not None:
- print("storing serial port: " + self.usedPort)
- else:
- print("not storing any serial port")
- del settings
-
- def refreshSerialPorts(self):
- self.portMenu = QMenu("Port")
- ports = serial.tools.list_ports.comports()
- self.portActions = []
- for port in ports:
- action = QAction(port.device)
- self.portActions.append(action)
- self.portMenu.addAction(action)
- self.portMenu.triggered.connect(self.selectSerialPort)
-
- if self.refreshAction == None:
- self.refreshAction = QAction("&Refresh")
- self.refreshAction.triggered.connect(self.refreshSerialPorts)
- self.portMenu.addAction(self.refreshAction)
- self.menu.insertMenu(self.quitAction, self.portMenu)
-
- def selectSerialPort(self, action):
- self.usedPort = action.text()
- self.writeSettings()
- if self.connect():
- self.portMenu.setActiveAction(action)
-
- def connect(self):
- if self.usedPort is None:
- print("not connecting to any serial port")
- return False
-
- if self.serial is not None:
- print("closing previous port")
- self.serial.close()
-
- self.serial = serial.Serial()
- self.serial.port = self.usedPort
- self.serial.baudrate = 115200
-
- try:
- self.serial.open()
- if self.serial.is_open:
- print("connected to: " + self.usedPort)
- else:
- print("error connecting to: " + self.usedPort)
- return self.serial.is_open
- except serial.serialutil.SerialException:
- print("error connecting to: " + self.usedPort)
- return False
-
- def printRGBStrings(self, rs, gs, bs):
- if self.serial.is_open:
- r = str.encode(rs)
- g = str.encode(gs)
- b = str.encode(bs)
- rgb = b'RGB ' + r + b' ' + g + b' ' + b + b'\n'
- self.serial.write(rgb)
- else:
- print("not connected")
-
- def setStaticColor(self, action):
- self.animOff()
- for color in self.staticColors:
- if color[4] is action:
- self.printRGBStrings(color[1], color[2], color[3])
- return True
- print("color not found")
- return False
-
- def hsvToRgb(self, h, s, v):
- (r, g, b) = colorsys.hsv_to_rgb(h, s, v)
- return (round(r * 255), round(g * 255), round(b * 255))
-
- def getCurrentCpuUsage(self):
- # https://stackoverflow.com/a/9229692
- # "top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}'"
- # https://stackoverflow.com/a/4760517
- cmd = ["top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk '{print 100 - $1}'"]
- result = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)
- num = result.stdout.decode('utf-8')
- return float(num)
-
- def cpuUsageRunner(self):
- while self.animationRunning is True:
- cpu = self.getCurrentCpuUsage()
- color = cpu / 100.0 * 120.0
- (r, g, b) = self.hsvToRgb((120.0 - color) / 360.0, self.fadeSaturation, self.fadeValue)
- self.printRGBStrings(str(r), str(g), str(b))
- time.sleep(1.0 / self.cpuUsageUpdateFreq)
-
- def cpuUsageOn(self):
- self.animOff()
- self.animationRunning = True
- self.animation = threading.Thread(target=self.cpuUsageRunner)
- self.animation.start()
-
- def fadeRunner(self, freq):
- while self.animationRunning is True:
- self.fadeHueCounter += 1
- if self.fadeHueCounter >= 360:
- self.fadeHueCounter = 0
- (r, g, b) = self.hsvToRgb(self.fadeHueCounter / 360.0, self.fadeSaturation, self.fadeValue)
- self.printRGBStrings(str(r), str(g), str(b))
- time.sleep(1.0 / freq)
-
- def slowFadeOn(self):
- self.animOff()
- self.animationRunning = True
- self.animation = threading.Thread(target=self.fadeRunner, args=[self.slowFadeUpdateFreq])
- self.animation.start()
-
- def fastFadeOn(self):
- self.animOff()
- self.animationRunning = True
- self.animation = threading.Thread(target=self.fadeRunner, args=[self.fastFadeUpdateFreq])
- self.animation.start()
-
- def animOff(self):
- self.animationRunning = False
- if self.animation != None:
- self.animation.join()
- self.animation = None
- self.printRGBStrings("0", "0", "0")
- time.sleep(0.1)
-
- def lightsOn(self):
- if self.serial.is_open:
- self.serial.write(b'UV 1\n')
- else:
- print("not connected")
-
- def lightsOff(self):
- if self.serial.is_open:
- self.serial.write(b'UV 0\n')
- else:
- print("not connected")
-
- if __name__ == "__main__":
- tray = CaseLights()
|