Compare commits

...

18 Commits

Author SHA1 Message Date
Your Name
9b724fe7ec update: Anleitung Training 2024-12-04 11:50:40 +01:00
Your Name
b452a3df74 update: delete unnecessary files 2024-12-02 08:17:02 +01:00
Your Name
d89ae03a64 update 2024-11-26 11:37:31 +01:00
Your Name
3139e10ecb update 2024-11-26 10:48:31 +01:00
Your Name
c3e5f0313b update: led control working 2024-11-26 08:33:45 +01:00
Your Name
cde8a1bd15 update 2024-11-25 19:13:28 +01:00
3d7d777b8f update 2024-11-25 18:39:06 +01:00
6d5f08bc12 update 2024-11-25 18:37:11 +01:00
9ba16027b7 update 2024-11-24 18:18:27 +01:00
5cce0a071c update: changed name 2024-11-24 17:22:21 +01:00
8f7fc69e69 Kommentare 2024-11-24 15:11:18 +01:00
Your Name
e80e38acd7 update btn pos 2024-11-23 23:15:20 +01:00
Your Name
df3ac9119c update: led light control ui 2024-11-23 23:11:06 +01:00
Your Name
0de0791acb update 2024-11-23 22:50:56 +01:00
Your Name
ff7e1614b7 update: readme 2024-11-23 22:08:11 +01:00
Your Name
b831fb319e update 2024-11-23 19:46:22 +01:00
Your Name
7e28cfc200 Merge branch 'cleanup' 2024-11-23 19:45:12 +01:00
Your Name
e0a357b78b update 2024-11-23 19:33:29 +01:00
6 changed files with 458 additions and 248 deletions

View File

@ -61,10 +61,42 @@ class WLEDController:
"""Turn off all lights."""
self.send_command('{"ps":"4"}')
def change_effect(self):
"""Change the color effect."""
def blink_yellow(self):
"""Blink in yellow."""
self.send_command('{"ps":"5"}')
def blink_red(self):
"""blink in red."""
self.send_command('{"ps":"7"}')
def blink_green(self):
"""blink in green."""
self.send_command('{"ps":"6"}')
def map_color_to_led(self, color):
"""
Map an RGB color to the corresponding WLED action based on predefined ranges.
:param color: A tuple of (R, G, B) values, where each component is an integer between 0 and 255.
"""
red, green, blue = color
# Check if the color is red (high R, low G and B)
if red > 200 and green < 100 and blue < 100:
self.switch_to_red()
# Check if the color is green (high G, low R and B)
elif red < 100 and green > 200 and blue < 100:
self.switch_to_green()
# Check if the color is yellow (high R and G, low B)
elif red > 200 and green > 200 and blue < 100:
self.switch_to_yellow()
# If none of the above, turn off the LEDs
else:
self.turn_off_all()
# Example usage:
if __name__ == "__main__":
controller = WLEDController(port="/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:1:1.0-port0")

BIN
pics/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

304
readme.md
View File

@ -0,0 +1,304 @@
# CPS Geislinger PackPal
Dieses Repository enthält die Software und Dokumentation für den **CPS Geislinger PackPal**, ein System, das Mitarbeiter beim Einpacken von Aufträgen unterstützt. Es kombiniert eine Waage und eine Kamera, um Bauteile automatisch zu erkennen, die Stückzahl zu prüfen und den Verpackungsprozess effizienter zu gestalten.
---
## Inhaltsverzeichnis
1. [Ordnerstruktur](#ordnerstruktur)
2. [Systemanforderungen](#systemanforderungen)
3. [Funktionsumfang](#funktionsumfang)
4. [Installation](#installation)
5. [Anwendung](#anwendung)
- [Programmstart](#programmstart)
- [Arbeitsablauf](#arbeitsablauf)
6. [Fehlerbehebung](#fehlerbehebung)
8. [Übersicht Datenaufnahme und Training](#training)
7. [Kontakt](#kontakt)
---
## Ordnerstruktur
Die Dateien und Ordner sind wie folgt strukturiert:
- **`workflow.py`**: Hauptprogramm zur Steuerung des Systems.
- **`readme.md`**: Diese Anleitung.
- **`environment.yaml`**: Conda-Umgebungskonfigurationsdatei.
- **`compose.yml`**: Docker Compose Datei für lokale Tests mit MariaDB.
- **`.gitignore`**: Einstellungen für Git, um unnötige Dateien auszuschließen.
- **`doc/`**: Zusätzliche Dokumentation.
- **`ESP32/`**: Firmwaredateien für den ESP32-Hilfscontroller.
- **`lib/`**: Lokale Python-Bibliotheken.
- **`models/`**: Machine Learning-Modelle für die Bauteilerkennung.
- **`pyqt_project/`**: Projektdateien für die Benutzeroberfläche (erstellt mit Qt Designer).
---
## Systemanforderungen
- **Betriebssystem**: Kubuntu 24.04 oder kompatibel.
- **Python-Version**: Python 3.10 oder höher.
- **Abhängigkeiten**:
- Conda oder Miniforge zur Verwaltung von Python-Bibliotheken.
- Docker und Docker Compose (optional für lokale Tests mit MariaDB).
- **Hardware**:
- Mitgelieferte Waage mit USB-/serieller Verbindung.
- Mitgelieferte Kamera für die Bauteilerkennung.
- Mitgelieferte 2x ESP32-Mikrocontroller als Hilfsgeräte.
---
## Funktionsumfang
Der **CPS Geislinger PackPal** bietet eine Vielzahl an Funktionen, um den Verpackungsprozess effizient zu unterstützen.
### Lichtsteuerung
- **Flexible Steuerung**:
- LEDs und Scheinwerfer können unabhängig voneinander per Software gesteuert werden, um optimale Arbeitsbedingungen zu schaffen.
### Datenerfassung
- **Integrierte Geräteansteuerung**:
- Waage und Kamera werden über den mitgelieferten Mini-PC angesteuert und ausgelesen, sodass die Datenerfassung nahtlos in den Workflow integriert ist.
### Vortrainierte Modelle
- **Effiziente Bauteilerkennung**:
- Das System enthält vortrainierte Modelle, die mit knapp **5.000 annotierten Datenframes** erstellt wurden.
- **Modelloptionen**:
- Standardmäßig wird **YOLOv8n** (kleinste Modellvariante) verwendet, um die begrenzte Rechenleistung des Mini-PCs zu berücksichtigen.
- Zusätzlich werden vortrainierte **YOLOv8m**- und **YOLOv10n**-Modelle bereitgestellt, um erweiterte Tests durchzuführen oder zukünftige Hardware-Upgrades zu berücksichtigen.
### Vollständig vorinstallierter Mini-PC
- **Leistungsstarker Mini-PC**:
- **Prozessor**: 12th Gen Intel Prozessor, optimiert für Effizienz.
- **Betriebssystem**: Ubuntu 24.04 ist vorinstalliert und einsatzbereit.
- **Grafik**: Keine dedizierte Grafikkarte, um Energieverbrauch und Kosten zu minimieren, bei gleichzeitiger Eignung für die enthaltenen leichten Modelle.
### Softwareumgebung
- **Umsetzung in Python**:
Die gesamte Softwareumgebung wurde in **Python** entwickelt, um eine einfache Erweiterbarkeit, Wartung und Integration mit modernen Machine-Learning-Frameworks zu gewährleisten.
- **Quellcode verfügbar**:
Der komplette Quellcode ist in diesem Repository enthalten und modular aufgebaut, sodass spezifische Funktionen leicht angepasst oder erweitert werden können.
- **Vorteile der Python-Umgebung**:
- Breite Unterstützung durch Bibliotheken wie **OpenCV**, **PyTorch**, und **PyQt**.
- Leichte Integration von Machine-Learning-Modellen und Hardwaresteuerung.
- Hohe Lesbarkeit und einfache Anpassung durch gut dokumentierten Code.
## Installation
Falls das System nicht vorinstalliert ist, folgen Sie diesen Schritten:
### 1. Conda-Umgebung erstellen
Installieren Sie die Conda-Abhängigkeiten, indem Sie die `environment.yaml` Datei nutzen:
```bash
conda env create -n geislinger -f environment.yaml
```
### 2. Conda-Umgebung aktivieren
Setzen Sie die erstellte Umgebung als Standardumgebung, indem Sie in der Datei ~/.bashrc folgendes hinzufügen:
> conda activate geislinger
Alternativ können Sie die Umgebung vor jedem Programmstart manuell aktivieren:
```bash
conda activate geislinger
```
### 3. Optional: MariaDB-Testumgebung starten
Falls Sie die Datenbank lokal testen möchten, starten Sie Docker Compose im Projektordner:
```bash
docker-compose up -d
```
### 4. Software für USB Relay Board installieren
https://github.com/scheiber-sa/sainsmartUsbRelay
## Anwendung
### Programmstart
Starten Sie das Hauptprogramm aus dem Projektordner mit:
```bash
python3 workflow.py
```
Die Benutzeroberfläche wird gestartet und alle Geräte (Waage, Kamera, ESP32) werden automatisch initialisiert.
![Workspace Overview](pics/image-1.png)
---
### Arbeitsablauf
Der **CPS Geislinger PackPal** unterstützt den Verpackungsprozess mit einer intuitiven Benutzeroberfläche. Es stehen zwei Arbeitsmodi zur Verfügung: **statisch** und **dynamisch**, die über die Checkbox **"static workflow"** ausgewählt werden können.
- **Statischer Workflow**:
Der Benutzer wählt die Positionsnummer (Artikel) manuell aus, und die Waage addiert die Gewichte der eingelegten Bauteile, bis die gewünschte Stückzahl erreicht ist.
- **Dynamischer Workflow** (Standardmodus):
Die Kamera und die Waage arbeiten zusammen, um das eingelegte Bauteil automatisch zu erkennen und die korrekte Positionsnummer auszuwählen. Die Waage überwacht die eingelegten Teile, bis die erforderliche Stückzahl erreicht ist.
#### Schritt-für-Schritt-Anleitung
1. **Auftrag laden**
- Geben Sie die Auftragsnummer in das Feld **"Auftragsnummer"** ein.
- Klicken Sie auf **"load Auftrag"**, um die Auftragsdaten zu laden.
- Die Liste der Bauteile (mit Positionsnummer, Beschreibung, Soll-Menge, Ist-Menge und Lagerort) wird im linken Tabellenbereich angezeigt.
2. **Bauteilerkennung**
- **Manuelle Auswahl (statisch)**: Wählen Sie die gewünschte Positionsnummer direkt aus der Tabelle. Klicken Sie auf **"check Waage"**, um die Gewichtserfassung zu starten.
- **Automatische Erkennung (dynamisch)**:
- Legen Sie ein Bauteil auf die Waage und klicken Sie auf **"check Waage"**.
- Die automatisch erkannte Positionsnummer wird im Feld **"Pos.-Nr."** markiert.
3. **Bestücken**
- Legen Sie die Bauteile auf die Waage, bis die angezeigte **Ist-Menge** die **Soll-Menge** erreicht.
- Das System zeigt die aktuelle Stückzahl an.
- Falls die Waage nicht korrekt eingestellt ist, können Sie diese mit **"Waage tarieren"** neu kalibrieren.
4. **Weiterverpacken**
- Wiederholen Sie den Vorgang für alle Bauteile im Auftrag.
- Die Benutzeroberfläche führt Sie Schritt für Schritt durch die einzelnen Arbeitsvorgänge. Der aktuelle Arbeitsfortschritt wird in der **ToDo-Liste** rechts angezeigt.
5. **Auftragsabschluss**
- Sobald alle Bauteile verpackt sind, zeigt das System an, dass der Auftrag abgeschlossen ist.
- Überprüfen Sie die Daten und schließen Sie den Auftrag ab.
---
### Zusätzliche Funktionen
- **Lichtsteuerung**:
Die LEDs und der Scheinwerfer können über die Schaltflächen gesteuert werden:
- **Turn on light / Turn off light**: Hauptbeleuchtung.
- **Turn on red/yellow/green LED**: Status-LEDs für visuelle Hinweise.
- **Blink Yellow LED**: Blinkt gelb zur Hervorhebung.
- **Turn off all LEDs**: Schaltet alle LEDs aus.
- **Kamera-Steuerung**:
- Aktivieren oder deaktivieren Sie den Kamera-Workflow über die Checkbox **"Camera Workflow"**.
- Starten und stoppen Sie die Kamera mit den Schaltflächen **"Start Camera"** und **"Stop Camera"**.
- Wählen Sie ein Modell für die automatische Bauteilerkennung aus der Dropdown-Liste aus (z. B. **"best_8n.pt"**).
- **Automatisches Tarieren**:
Aktivieren Sie die Checkbox **"automatisches Tarieren"**, um die Waage automatisch zu kalibrieren, sobald ein Arbeitsablauf gestartet wird.
---
### Hinweise
- **ToDo-Liste**:
Die **ToDo-Liste** in der Benutzeroberfläche bietet eine klare Übersicht über die nächsten Schritte und den aktuellen Status. Befolgen Sie die Anweisungen, um den Workflow effizient abzuschließen.
- **Überwachung des Vorgangs**:
Verfolgen Sie die Statusanzeigen in der Benutzeroberfläche. Fehler oder Warnungen werden dort direkt angezeigt.
- **Stückzahlkontrolle**:
Die Waage erkennt automatisch Über- oder Unterfüllungen und zeigt entsprechende Hinweise an.
- **Korrektur**:
Sollten Fehler auftreten, können Sie jederzeit den aktuellen Artikel neu auswählen oder den Auftrag zurücksetzen.
## Fehlerbehebung
- Geräte werden nicht erkannt:
Stellen Sie sicher, dass alle Geräte korrekt verbunden sind und überprüfen Sie die Konfiguration.
- Waage zeigt falsche Werte:
Kalibrieren Sie die Waage erneut gemäß der Dokumentation. Die Waage muss auf Kg eingestellt werden, damit die Daten korrekt übermittelt werden.
- Fehlerhafte Bauteilerkennung:
Stellen Sie sicher, dass die Kamera sauber ist und die Beleuchtung ausreichend ist. Prüfen Sie auch, ob das richtige Modell in models/ geladen wurde.
- Datenbankfehler:
Vergewissern Sie sich, dass die Datenbank MariaDB läuft und die Zugangsdaten korrekt in der Konfiguration hinterlegt sind.
- Konsolenausgaben kontrollieren:
Sollte es Probleme mit der Bauteilerkennung oder Waage geben, überprüfen Sie die Konsolenausgabe des Programms. Diese zeigt hilfreiche Diagnosen an.
## Übersicht Datenaufnahme und Training
Die Erstellung eines robusten Datensatzes und die anschließende Modelltrainierung erfolgten nach einem klar strukturierten Ablauf, um eine möglichst hohe Präzision der Bauteilerkennung zu gewährleisten. Dieser Prozess lässt sich in mehrere Schritte unterteilen:
---
#### **Datenaufnahme**
1. **Videoaufzeichnung**
- Die Videos wurden mit **OBS Studio** direkt vom Kamerastream aufgenommen.
- Jedes Bauteil wurde einzeln unter standardisierten Bedingungen vermessen und gefilmt.
- Zusätzlich wurden Bauteile in Kombination mit anderen Bauteilen und vor unterschiedlichen Hintergründen aufgenommen, um die Datendiversität zu erhöhen.
2. **Bearbeitung und Extraktion**
- Die aufgenommenen Videos wurden auf die Plattform **Roboflow** hochgeladen.
- Einzelne Frames (ca. **500 Frames pro Bauteil**) wurden aus den Videos extrahiert. Dabei wurde darauf geachtet, eine **repräsentative Stückzahl** zu wählen, die typische Variationen wie Winkel, Beleuchtung und Positionen abdeckt.
---
#### **Annotation und Datensatzvorbereitung**
1. **Annotation**
- Die extrahierten Frames wurden in Roboflow manuell annotiert, d. h., die Bauteile wurden mit präzisen Bounding-Boxen und Labels versehen.
- Für jeden Bauteiltyp wurde eine eigene Klasse definiert.
2. **Export und Augmentierung**
- Der annotierte Datensatz wurde im **YOLOv8-Format** exportiert.
- Während des Exports erfolgte eine automatische **Datenaugmentierung** durch Roboflow:
- **Transformationen**: Die Bilder wurden gespiegelt, gedreht, beschnitten und auf ein **quadratisches 1:1-Bildformat** skaliert.
- Ziel der Augmentierung war es, die Robustheit des Modells gegenüber unterschiedlichen Perspektiven und Hintergründen zu erhöhen.
---
#### **Modelltraining**
1. **Trainingsumgebung**
- Die Trainingsläufe wurden mithilfe der integrierten YOLOv8-Trainingsmethoden durchgeführt (Details siehe [Ultralytics Dokumentation](https://docs.ultralytics.com/modes/train/)).
- Hardware: Eine leistungsstarke **Nvidia Tesla P100 Grafikkarte** wurde für das Training genutzt.
- Framework: YOLOv8 implementiert in Python mit der Ultralytics-Bibliothek.
2. **Trainingsdetails**
- **Train/Test-Split**:
- Der Datensatz wurde in **80% Trainingsdaten** und **20% Testdaten** unterteilt.
- Für die Validierung wurden zusätzliche **10% der Trainingsdaten** als Validierungsdatensatz verwendet.
- **Hyperparameter**:
- Anzahl der **Epochen**: Variationen mit 50, 100 und 200 Epochen, um die optimale Konvergenz zu ermitteln.
- **Mixed Precision Training** wurde aktiviert, um die Speichereffizienz auf der GPU zu erhöhen und die Trainingsgeschwindigkeit zu verbessern.
3. **Trainierte Modelle**
- Es wurden mehrere Modelle basierend auf unterschiedlichen **YOLOv8-Architekturen** erstellt:
- **YOLOv8n** (Nano): Besonders ressourcensparend, ideal für den Einsatz auf Geräten mit begrenzter Rechenleistung.
- **YOLOv8m** (Medium): Liefert eine höhere Genauigkeit, benötigt jedoch mehr Rechenleistung.
- **YOLOv10n**: Als experimenteller Vergleich zur Validierung des Trainingsprozesses.
---
#### **Ergebnisse und Validierung**
1. **Modellbewertung**
- Die Modelle wurden anhand der Metriken **mAP (mean Average Precision)**, **Präzision**, und **Recall** bewertet.
- **Cross-Validation** wurde durchgeführt, um sicherzustellen, dass die Modelle generalisieren und nicht überangepasst sind.
2. **Einfluss von Epochs und Augmentierung**
- Eine höhere Anzahl von Epochen führte in vielen Fällen zu einer Verbesserung der Genauigkeit, allerdings war der Effekt nach etwa 100 Epochen minimal.
- Die Datenaugmentierung zeigte signifikanten Einfluss auf die Robustheit der Modelle, insbesondere bei ungewöhnlichen Blickwinkeln und Hintergründen.
---
## Kontakt
Für weitere Unterstützung oder Fragen wenden Sie sich bitte an:
- E-Mail: clemens.fritze@unileoben.ac.at
- Telefon: +43 3842 402 1904
- Dokumentation: Weitere Informationen finden Sie im Ordner doc/.
Dieses Projekt wurde entwickelt, um die Effizienz und Genauigkeit im Verpackungsprozess zu steigern und die Arbeitsbelastung der Mitarbeiter zu reduzieren. Vielen Dank für die Nutzung des CPS Geislinger PackPal!

BIN
readme.pdf Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,14 +1,3 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
########## beim Static workflow funktionieren die Threads ab dem zweiten nicht mehr
#from PyQt5 import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QProgressBar
from PyQt5 import QtCore, QtGui, QtWidgets
@ -28,29 +17,41 @@ import cv2
import os
import lib.sainsmartrelay
import lib.sainsmartrelay as sainsmartrelay
import lib.wledControl as wledControl
# db_config = {
# 'user': 'dbUser',
# 'password': 'dbPassword',
# 'host': '127.0.0.1', # 'host': 'localhost',
# 'database': 'projectGeislinger',
# 'port': 3306 # Standard port for MariaDB
# }
# ## in der Ui_MainWindow-Klasse wird die GUI erstellt - der MainThread läuft in dieser -> Farben Drop Down
# ## Aufsetzen der Datenbank und Waagenverbindung
# # Definieren der Datenbankverbindung
db_config = {
'user': 'dbUser',
'password': 'dbPassword',
'host': '127.0.0.1', # 'host': 'localhost',
'host': '127.0.0.1', # 'host': 'localhost', changed because more compatible
'database': 'projectGeislinger',
'port': 3306 # Standard port for MariaDB
}
# # Establishing the connection
# conn = mariadb.connect(**db_config)
# # Create a cursor to execute queries
# cursor = conn.cursor()
try:
# Attempt to establish the connection
conn = mariadb.connect(**db_config)
print("Database connection established successfully.")
# Create a cursor to execute queries
cursor = conn.cursor()
except mariadb.Error as e:
# Handle connection errors
print(f"Error connecting to the database: {e}")
conn = None
cursor = None
# Establishing the connection
conn = mariadb.connect(**db_config)
# Create a cursor to execute queries
cursor = conn.cursor()
# Configuration of the serial port
try:
@ -59,32 +60,8 @@ try:
except serial.SerialException:
ser = None
print("Warning: Serial port not found. Continuing without serial connection. Only working for demo-purposes.")
#ser = serial.Serial('/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0', 9600) #dadurch garantiert immer die gleiche Schnittstelle verwendet
# und nicht die Schnittstelle, welche die Bezeichnung ttyUSB0 verwendet (welche sich ändern könnte)
# um die ID der USB-Schnittstelle heraus zu finden im Terminal folgendes eingeben: ls -l /dev/serial/by-id/
waageEingeschwungen = False
def wahrscheinlichkeitsDichte(x,mue, var):
# in der Funktion wird der Wahrscheinlichkeitsdichtenwert der Variable x für eine bestimmte Normalverteilung berechnet
standardabweichung = var**0.5
result = 1/(standardabweichung * (2*math.pi)**0.5 ) * math.exp(-0.5 * ((x-mue)/standardabweichung)**2)
return result
def calcWahrscheinlichkeitFromDichte(x,mue, var):
# in der Funktion wird die Wahrscheinlichkeit via der Wahrscheinlichkeitsdichte berechnet, indem das Verhältnis aus der dem Bauteil zugehörigen Wahrscheinlichkeitsdichte zu der maximalen Wahrscheinlichkeitsdichte berechnet wird
p1 = wahrscheinlichkeitsDichte(x,mue, var)
p_max = wahrscheinlichkeitsDichte(mue,mue, var)
return p1/p_max
#class Worker(QThread):
# die Workerklasse ist dazu da die Schleifen - in welchen der Bauteiltyp erkannt wird und die Anzahl der Bauteile, welche auf der Waage liegen - in einem seperaten Thread auszuführen, um ein Blockieren des Mainthreads zu verhindern
class Worker(QObject):
#progress = pyqtSignal(int)
objectDetectionStartSignal = pyqtSignal(str)
@ -97,6 +74,7 @@ class Worker(QObject):
waageStoppedSignal = pyqtSignal(int)
stopLoopSignal = pyqtSignal(bool) #das Signal wird verwendet um direkt den stopLoop-Wert zu ändern (also kein Funktionsaufruf)
waageEingeschwungen = False
stopLoop = False
btTypeIsSet = False
correctBtNr = False
@ -108,7 +86,7 @@ class Worker(QObject):
def getDataOfArticleType(self, allArticles, articleType):
# die Funktion geht die Liste mit allen Artikeln durch und gibt jenen Eintrag, welcher mit dem "articleType" übereinstimmt zurück
''' die Funktion geht die Liste mit allen Artikeln durch und gibt jenen Eintrag, welcher mit dem "articleType" übereinstimmt zurück '''
for i in allArticles:
if i[1] == articleType:
return i
@ -118,26 +96,27 @@ class Worker(QObject):
def waageNichtEingeschwungenOutput(self):
print("Die Waage ist noch nicht eingeschwungen - Ergebnisse sind dadurch noch fehlerhaft.")
def wahrscheinlichkeitsDichte(self, x,mue, var):
''' in der Funktion wird der Wahrscheinlichkeitsdichtenwert der Variable x für eine bestimmte Normalverteilung berechnet '''
standardabweichung = var**0.5
result = 1/(standardabweichung * (2*math.pi)**0.5 ) * math.exp(-0.5 * ((x-mue)/standardabweichung)**2)
return result
def readWaage(self):
# in folgender Funktion wird die Waage ausgelesen
#print("connection is open: ", ser.is_open) #Debuggingausgabe
#print("port to which it is connected: ", ser.portstr) #Debuggingausgabe
''' in folgender Funktion wird die Waage ausgelesen '''
if ser.is_open == False:
ser.open()
#an die Waage den Befehl senden, dass sie ausgelesen werden soll
ser.write(b'getWeight\n')
#ser.write(b'tare\n')
serialString = ser.readline().decode('utf-8').rstrip() #Auslesen des Serial-Strings/der Messung der Waage
# wenn am Ende des Strings kg steht, dann ist die Waage eingeschwungen - das wird hiermit überprüft
lenString = len(serialString)-1
if serialString[lenString] == "g" and serialString[lenString-1] == "k":
#print("ist eingeschwungen") #Debuggingausgabe
waageEingeschwungen = True
else:
print("die Waage ist noch nicht eingeschwungen")
@ -149,17 +128,13 @@ class Worker(QObject):
if i=="-" or i=="0" or i=="." or i=="1" or i=="2" or i=="3" or i=="4" or i=="5" or i=="6" or i=="7" or i=="8" or i=="9":
intString = intString + i
print("Wert, welcher von der Waage ausgelesen wurde: " + intString + "kg")
## Waage auslesen - ENDE
ser.close()
#print("connection is open: ", ser.is_open) #Debuggingausgabe
return waageEingeschwungen, intString
def objectTypeDetectionThread(self, auftragsnummer):
# in dieser Funktion wird der Typ des Bauteils automatisch erkannt
#print("objectTypeDetectionThread - Running in thread:", threading.current_thread().name) #Debuggausgabe
''' in dieser Funktion wird der Typ des Bauteils automatisch erkannt '''
# Parameterdefinition
propDensVect = []
@ -179,16 +154,9 @@ class Worker(QObject):
cursor.execute(sql_query)
auftragEinzelteilDaten = cursor.fetchall()
'''
# Display data #Debugausgabe
print("Ausgabe der Auftragsdetails des obigen Auftrags, inklusive Einzelteildetails:")
for row in auftragEinzelteilDaten:
print(row)
'''
# in der Folge werden alle Wahrscheinlichkeitsdichten der Auftragsbauteile berechnet und in dem Vektor gesammelt
for row in auftragEinzelteilDaten:
propDensVect.append([wahrscheinlichkeitsDichte(float(intString),float(row[6]), float(row[7])), row[1], row[5]])
propDensVect.append([self.wahrscheinlichkeitsDichte(float(intString),float(row[6]), float(row[7])), row[1], row[5]])
# Jenen Eintrag des propDensVect raussuchen, welcher die größte Wahrscheinlichkeitsdichte beinhaltet
maxpropDens = 0
@ -206,14 +174,6 @@ class Worker(QObject):
else:
print("Bei dem Bauteil" , einzelteilID , "wurde die höchste Wahrscheinlichkeitsdichte berechnet.")
# Wahrscheinlichkeit berechnen, dass das angegebene Bauteil auch wirklich diesem entspricht
prop = 0
for row in auftragEinzelteilDaten:
if row[1] == einzelteilID:
prop = calcWahrscheinlichkeitFromDichte(float(intString),float(row[6]), float(row[7]))
break
print("Die Wahrscheinlichkeit, dass es das Bauteil ist, beträgt: ", prop)
# den Bool auf true setzen, damit die Schleife beendet wird - dieser wird auf True gesetzt, wenn ein Bauteiltyp erkannt wird
self.btTypeIsSet = True
@ -224,6 +184,7 @@ class Worker(QObject):
self.waageStoppedSignal.emit(einzelteilID)
def checkWaageThread(self, einzelteilID, teileZuViel, auftragsnummer):
''' in dieser Funktion wird die Stückzahl der Bauteile, welche auf der Waage liegen, berechnet '''
self.correctBtNr = False
prevAnzahl = 0
@ -243,14 +204,6 @@ class Worker(QObject):
cursor.execute(sql_query)
auftragDaten = cursor.fetchall()
'''
# Display data - zum Debuggen
print("Ausgabe der Auftragsdetails:")
print("id|EinzelteilID|Auftragsnummer|Anzahl")
for row in auftragDaten:
print(row)
'''
#auslesen, wie viele Bauteile des Types laut Auftrag vorhanden sein sollen
anzBauteile_soll = 0
idVorhanden = False
@ -272,7 +225,7 @@ class Worker(QObject):
# seien X1,..., Xn unabhängige Zufallsvariablen die N(mue_i, sigma_i^2) verteilt sind, dann ist X = X1+...+Xn - N(mue, sigma^2) verteilt mit mue=mue1+...+mue_n, sigma^2 = sigma_1^2+...+sigma_n^2
mueGes = float(articleData[6])*i # Berechnen des äquivalenten Mittelwert
varGes = float(articleData[7])*i # Berechnen der äquivalenten Varianz
propDensVect.append([wahrscheinlichkeitsDichte(float(intString),mueGes, varGes), i])
propDensVect.append([self.wahrscheinlichkeitsDichte(float(intString),mueGes, varGes), i])
# durch den propDensVect iterieren und jenen Eintrag mit der höchsten Wahrscheinlichkeitsdichte raussuchen
maxpropDens = 0
@ -285,14 +238,6 @@ class Worker(QObject):
if prevAnzahl != anzahl:
prevAnzahl = anzahl
# Die Wahrscheinlichkeit berechnen, dass das obige Ergebnis auch dem Bauteil entspricht
if anzahl > 0:
for row in auftragDaten:
if row[1] == einzelteilID:
prop = calcWahrscheinlichkeitFromDichte(float(intString),float(row[6])*anzahl, float(row[7])*anzahl)
break
print("Die Wahrscheinlichkeit, dass es das Bauteil ist, beträgt: ", prop)
# wenn genug Bauteile vorhanden sind, dann soll die Schleife beendet werden
if (anzahl == anzBauteile_soll):
self.correctBtNr = True
@ -312,8 +257,7 @@ class Worker(QObject):
self.waageStoppedSignal.emit(einzelteilID)
## in der Ui_MainWindow-Klasse wird die GUI erstellt - der MainThread läuft in dieser
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
self.auftragsnummer = ""
@ -321,7 +265,6 @@ class Ui_MainWindow(object):
self.correctBtNr = False
self.btTypeIsSet = False
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1400, 675)
self.centralwidget = QtWidgets.QWidget(MainWindow)
@ -461,7 +404,7 @@ class Ui_MainWindow(object):
self.graphicsView.setGeometry(QtCore.QRect(10, int(1080/2), 661, int(480*1.05))) # position and size of camera frame # int(640*1.05)
self.graphicsView.setObjectName("graphicsView")
# relay control
# relay control buttons
self.startSpotlightBtn = QtWidgets.QPushButton(self.centralwidget)
self.startSpotlightBtn.setGeometry(QtCore.QRect(700+200, int(1080/2)+100, 161, 25)) #int(1080/2)+100
self.startSpotlightBtn.setObjectName("startSpotlightBtn")
@ -469,15 +412,17 @@ class Ui_MainWindow(object):
self.stopSpotlightBtn.setGeometry(QtCore.QRect(700+200, int(1080/2)+150, 161, 25))
self.stopSpotlightBtn.setObjectName("stopSpotlightBtn")
#self.myTestLambda = lambda: self.worker.checkWaageStartSignal.emit(einzelteilID, self.teileZuViel, self.auftragsnummer)
'''
item = QtWidgets.QTableWidgetItem()
font = QtGui.QFont()
font.setPointSize(8)
item.setFont(font)
self.AuftragsdetailsTable.setVerticalHeaderItem(0,item)
'''
# led control buttons
self.redLightBtn = QtWidgets.QPushButton(self.centralwidget)
self.redLightBtn.setGeometry(QtCore.QRect(700+200+200, int(1080/2)+100, 161, 25))
self.yellowLightBtn = QtWidgets.QPushButton(self.centralwidget)
self.yellowLightBtn.setGeometry(QtCore.QRect(700+200+200, int(1080/2)+150, 161, 25))
self.greenLightBtn = QtWidgets.QPushButton(self.centralwidget)
self.greenLightBtn.setGeometry(QtCore.QRect(700+200+200, int(1080/2)+200, 161, 25))
self.offLightBtn = QtWidgets.QPushButton(self.centralwidget)
self.offLightBtn.setGeometry(QtCore.QRect(700+200+200, int(1080/2)+250, 161, 25))
self.blinkLightBtn = QtWidgets.QPushButton(self.centralwidget)
self.blinkLightBtn.setGeometry(QtCore.QRect(700+200+200, int(1080/2)+300, 161, 25))
# Maximize the window on startup
MainWindow.showMaximized()
@ -513,8 +458,6 @@ class Ui_MainWindow(object):
item = self.ArbeitsschrittTable.horizontalHeaderItem(1)
item.setText(_translate("MainWindow", "ToDo:"))
# Befüllen der Arbeisschritttabelle mit Text
#item = self.ArbeitsschrittTable.
# Ein neues QTableWidgetItem erstellen und den Text setzen
item = QtWidgets.QTableWidgetItem("Auftrag laden")
self.ArbeitsschrittTable.setItem(0, 0, item)
@ -532,19 +475,10 @@ class Ui_MainWindow(object):
self.ArbeitsschrittTable.setItem(3, 0, item)
self.ArbeitsschrittTable.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers)
#self.ArbeitsschrittTable.item(0,0).setText("test")
#einzelteilID = int(self.AuftragsdetailsTable.item(i,0).text())
#self.PosNrTxtFeld.setText(str(self.AuftragsdetailsTable.item(i,0).text()))
#self.bauteiltypTextbox.setText(str(self.AuftragsdetailsTable.item(i,2).text()))
self.label.setText(_translate("MainWindow", "Auftragsnummer:"))
self.bauteilTypBtn.setText(_translate("MainWindow", "Bauteiltyp erkennen"))
self.bauteilTypBtn.clicked.connect(self.objectTypeDetection)
self.AuftragsdetailsTable.cellClicked.connect(self.onTableCellClick)
#self.AuftragsdetailsTable.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers)
#item = self.AuftragsdetailsTable.verticalHeaderItem(0)
#item.setText(_translate("MainWindow", "test"))
self.BezeichnungLabel.setText(_translate("MainWindow", "Bezeichnung"))
self.PosNrLabel.setText(_translate("MainWindow", "Pos.-Nr:"))
self.teileZuVielLabel.setText(_translate("MainWindow", "Zu prüfende Teileanzahl über Auftragsanzahl:"))
@ -566,32 +500,33 @@ class Ui_MainWindow(object):
# new camera workflow
self.startCamBtn.setText(_translate("MainWindow", "Start Camera"))
# self.startCamBtn.clicked.connect(self.startCamBtnClicked)
self.stopCamBtn.setText(_translate("MainWindow", "Stop Camera"))
self.camWorkFlowcheckBox.setText(_translate("MainWindow", "Camera Workflow"))
# self.camWorkFlowcheckBox.clicked.connect(self.onCheckboxCheck)
self.modelComboBox
# relay control
# relay control buttons
self.startSpotlightBtn.setText(_translate("MainWindow", "Turn on light"))
self.stopSpotlightBtn.setText(_translate("MainWindow", "Turn off light"))
# led control buttons
self.redLightBtn.setText(_translate("MainWindow", "Turn on red LED"))
self.yellowLightBtn.setText(_translate("MainWindow", "Turn on yellow LED"))
self.greenLightBtn.setText(_translate("MainWindow", "Turn on green LED"))
self.offLightBtn.setText(_translate("MainWindow", "Turn off all LEDs"))
self.blinkLightBtn.setText(_translate("MainWindow", "Blink Yellow LED"))
def mousePressEvent(self, event):
print("Das MainWindow wurde angeklickt.")
self.setFocus()
super().mousePressEvent(event)
def onTareClick(self):
#Tarieren der Waage
''' Tarieren der Waage '''
if ser.is_open == False:
ser.open()
ser.write(b'tare\n')
ser.close()
def onCheckboxCheck(self):
if self.checkBox.isChecked() == True:
print("static workflow activated")
@ -608,9 +543,6 @@ class Ui_MainWindow(object):
for i in range(0,self.AuftragsdetailsTable.rowCount()):
if(self.AuftragsdetailsTable.item(i,0).text() == str(posNr)):
return i
# brauche ich hier vermutlich nicht mehr - ist in den Worker Thread kopiert worden
def waageNichtEingeschwungenOutput(self):
print("Die Waage ist noch nicht eingeschwungen - Ergebnisse sind dadurch noch fehlerhaft.")
def onTableCellClick(self):
self.PosNrTxtFeld.setText(self.AuftragsdetailsTable.item(self.AuftragsdetailsTable.currentRow(),0).text())
@ -635,71 +567,21 @@ class Ui_MainWindow(object):
else:
return False
# eventuell benötigt man die Funktion hier nicht mehr, da sie zu den Threads kopiert wurde
def getDataOfArticleType(self, allArticles, articleType):
# die Funktion geht die Liste mit allen Artikeln durch und gibt jenen Eintrag, welcher mit dem "articleType" übereinstimmt zurück
for i in allArticles:
if i[1] == articleType:
return i
return -1
def updateGUI(self):
self.PosNrLabel.repaint() #GUI aktualisieren
QApplication.processEvents() #GUI aktualisieren
def setRowColor(self, tableObject, rowID,r,g,b):
for col in range(tableObject.columnCount()-1):
tableObject.item(rowID, col).setBackground(QtGui.QColor(r,g,b))
'''
die alte Version der Programmierung
tableObject.item(rowID, 0).setBackground(QtGui.QColor(r,g,b))
tableObject.item(rowID, 1).setBackground(QtGui.QColor(r,g,b))
tableObject.item(rowID, 2).setBackground(QtGui.QColor(r,g,b))
tableObject.item(rowID, 3).setBackground(QtGui.QColor(r,g,b))
tableObject.item(rowID, 4).setBackground(QtGui.QColor(r,g,b))
tableObject.item(rowID, 5).setBackground(QtGui.QColor(r,g,b))
'''
#wird hier vermutlich nicht mehr benötigt - wurde in die Workerklasse kopiert
def readWaage(self):
# in folgender Funktion wird die Waage ausgelesen
#print("connection is open: ", ser.is_open) #Debuggingausgabe
#print("port to which it is connected: ", ser.portstr) #Debuggingausgabe
self.checkPosNrEmpty()
if ser.is_open == False:
ser.open()
#an die Waage den Befehl senden, dass sie ausgelesen werden soll
ser.write(b'getWeight\n')
#ser.write(b'tare\n')
serialString = ser.readline().decode('utf-8').rstrip() #Auslesen des Serial-Strings/der Messung der Waage
# wenn am Ende des Strings kg steht, dann ist die Waage eingeschwungen - das wird hiermit überprüft
lenString = len(serialString)-1
if serialString[lenString] == "g" and serialString[lenString-1] == "k":
#print("ist eingeschwungen") #Debuggingausgabe
waageEingeschwungen = True
else:
print("die Waage ist noch nicht eingeschwungen")
waageEingeschwungen = False
#aus dem String werden alle Zeichen, welche nicht zur Darstellung der Zahl benötigt werden entfernt
intString = ""
for i in serialString:
if i=="-" or i=="0" or i=="." or i=="1" or i=="2" or i=="3" or i=="4" or i=="5" or i=="6" or i=="7" or i=="8" or i=="9":
intString = intString + i
print("Wert, welcher von der Waage ausgelesen wurde: " + intString + "kg")
## Waage auslesen - ENDE
ser.close()
#print("connection is open: ", ser.is_open) #Debuggingausgabe
return waageEingeschwungen, intString
# # call change led color
# Check if the 'w' object already exists
# hier könnte noch nachgearbeitet werden
if not hasattr(self, 'w'):
w = wledControl.WLEDController(port="/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:1:1.0-port0")
w.connect()
w.map_color_to_led([r,g,b])
def auftragsBtnClicked(self):
databaseQueryWorking = False #wird für die Überprüfung, ob die Datenbankabfrage fehlerhaft ist, verwendet
@ -718,16 +600,7 @@ class Ui_MainWindow(object):
print("Fehler in der Datenbankabfrage.")
if databaseQueryWorking==True and len(auftragEinzelteilDaten)>0:
'''
# Display data
print("Ausgabe der Auftragsdetails des obigen Auftrags, inklusive Einzelteildetails:")
for row in auftragEinzelteilDaten:
if str(row[2]) == self.auftragsnummer:
print("passt")
print(row)
'''
### die Auftragsdaten in die Tabelle laden
self.AuftragsdetailsTable.setRowCount(len(auftragEinzelteilDaten))
@ -774,7 +647,6 @@ class Ui_MainWindow(object):
self.bauteiltypTextbox.setText("")
self.PosNrTxtFeld.setText("")
elif(databaseQueryWorking==True and len(auftragEinzelteilDaten)==0):
print("Es wurde in der Datenbank kein Auftrag mit dieser Auftragsnummer gefunden.")
@ -787,7 +659,7 @@ class Ui_MainWindow(object):
self.setRowColor(self.AuftragsdetailsTable, row,0,255,0)
def stopLoopClicked(self):
# damit wird beim Klick auf den Stopbutton der stopLoop-boolWert in der Workerklasse auf true gesetzt -> der Stop des Threads wird initiiert
''' damit wird beim Klick auf den Stopbutton der stopLoop-boolWert in der Workerklasse auf true gesetzt -> der Stop des Threads wird initiiert '''
if hasattr(self, 'objectDetectionWorker'):
self.objectDetectionWorker.stopLoopSignal.emit(True)
@ -795,20 +667,15 @@ class Ui_MainWindow(object):
self.checkWaageWorker.stopLoopSignal.emit(True)
def checkFinished(self):
#die Funktion geht alle Zeilen der Auftragsliste durch und schaut, ob die richtige Anzahl an Teilen vorhanden sind
''' die Funktion geht alle Zeilen der Auftragsliste durch und schaut, ob die richtige Anzahl an Teilen vorhanden sind '''
for i in range(0,self.AuftragsdetailsTable.rowCount()):
if self.AuftragsdetailsTable.item(i,3).text() != self.AuftragsdetailsTable.item(i,4).text():
return False
return True
def checkWaage(self):
#print("Running in thread:", threading.current_thread().name) # Debuggingausgabe
QApplication.processEvents()
# die Loopvariable des Workers auf False setzten, damit die Schleife durchgelaufen wird (diese wird zum Abbruch der Schleife benötigt -> siehe stopLoop)
#self.checkWaageWorker.stopLoopSignal.emit(False)
# überprüfen, ob der Auftrag geladen wurde
if(self.AuftragsdetailsTable.item(0,0) == None):
print("Der Auftrag muss zuerst geladen werden.")
@ -897,22 +764,13 @@ class Ui_MainWindow(object):
#self.updateGUI()
def objectTypeDetection(self):
# in dieser Funktion wird der Typ des Bauteils automatisch erkannt
''' in dieser Funktion wird der Typ des Bauteils automatisch erkannt '''
# wenn der statische Workflow ausgewählt wurde, dann soll die checkWaage Funktion aufgerufen werden, auch wenn die detectBauteiltyp-Funkion aufgerufen wurde
if self.checkBox.isChecked() == True:
self.checkWaage()
return
'''
if(self.checkPosNrEmpty()==True):
print("Das Pos.-Nr.-Feld ist leer.")
return
'''
# die Loopvariable des Workers auf False setzten, damit die Schleife durchgelaufen wird (diese wird zum Abbruch der Schleife benötigt -> siehe stopLoop)
#self.objectDetectionWorker.stopLoopSignal.emit(False)
# überprüfen, ob der Auftrag in die Tabelle geladen wurde
if(self.AuftragsdetailsTable.item(0,0) == None):
print("Der Auftrag muss zuerst geladen werden.")
@ -936,7 +794,7 @@ class Ui_MainWindow(object):
self.objectDetectionThread.start()
def objectTypeDetectionFinished(self, einzelteilID, rowData):
# wenn der Typ des Objektes erkannt wurde, dann soll die Funktion aufgerufen werden
''' wenn der Typ des Objektes erkannt wurde, dann soll die Funktion aufgerufen werden '''
# Schreiben der Bauteiltype und Pos.-Nr in die jeweiligen Felder
self.PosNrTxtFeld.setText(str(rowData[1]))
@ -952,20 +810,12 @@ class Ui_MainWindow(object):
self.objectDetectionThread.quit()
self.objectDetectionThread.wait()
'''
try:
self.objectDetectionThread.started.disconnect() # Trenne das Signal, damit es beim nächsten Start keine Konflikte gibt
except:
print(f"Error while disconnecting: {e}")
print("Disconnecting the thread did not work.")
'''
# wenn der Bauteiltyp erkannt wurde, dann soll die GUI aktualisiert werden und anschließend die CheckWaage-Funktion aufgerufen werden
self.updateGUI()
self.checkWaage()
def threadStopped(self, einzelteilID):
# wenn kein Bauteil erkannt wurde, dann ist die EinzenteilID = 0
''' wenn kein Bauteil erkannt wurde, dann ist die EinzenteilID = 0 '''
if self.checkFinished() == False:
if(einzelteilID > 0):
self.setRowColor(self.AuftragsdetailsTable, self.getRowNr(einzelteilID),255,255,255) #zum setzen den Farbe der gesamten Reihe auf Weiß
@ -983,7 +833,6 @@ class Ui_MainWindow(object):
self.checkWaageThread.wait()
print("Der CheckWaage-Thread wurde beendet.")
# new class for Camera Object detection with YOLOv8
class CameraStreamApp(QtWidgets.QMainWindow):
def __init__(self, ui):
@ -1035,7 +884,7 @@ class CameraStreamApp(QtWidgets.QMainWindow):
# Convert the frame from BGR (OpenCV format) to RGB
# frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frame = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB) # might change nomenclature later?
frame = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB)
# Convert the frame to QImage
h, w, ch = frame.shape
@ -1059,16 +908,37 @@ class CameraStreamApp(QtWidgets.QMainWindow):
self.yolo_stream.cap.release()
event.accept()
# new class for relay control
class RelayControl(QtWidgets.QMainWindow):
# new class for light control
class LightControl(QtWidgets.QMainWindow):
def __init__(self, ui):
super().__init__()
self.ui = ui
# init relay control
self.ui.startSpotlightBtn.clicked.connect(self.spot_on)
self.ui.stopSpotlightBtn.clicked.connect(self.spot_off)
self.r = sainsmartrelay.SainsmartRelay()
# init led control
self.ui.redLightBtn.clicked.connect(self.red_on)
self.ui.yellowLightBtn.clicked.connect(self.yellow_on)
self.ui.greenLightBtn.clicked.connect(self.green_on)
self.ui.offLightBtn.clicked.connect(self.leds_off)
self.ui.blinkLightBtn.clicked.connect(self.blink_yellow)
self.w = wledControl.WLEDController(port="/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:1:1.0-port0")
self.w.connect()
#self.w.switch_to_green() # at appstart the light is green
self.w.blink_green() # and switches to green blinking until the workflow gets started successfully#
# Connect app exit signal to the cleanup method
app.aboutToQuit.connect(self.cleanup)
def cleanup(self):
"""Clean up resources and switch off LEDs."""
print("Cleaning up resources and switching off LEDs")
self.leds_off() # Turn off LEDs
self.w.disconnect() # Disconnect WLED controller
def spot_on(self):
print("Turn on light clicked")
self.r.turn_on(1)
@ -1079,27 +949,31 @@ class RelayControl(QtWidgets.QMainWindow):
def red_on(self):
print("Red light is on")
self.r.turn_on(2)
def red_off(self):
print("Red light is off")
self.r.turn_off(2)
self.w.switch_to_red()
def yellow_on(self):
print("Yellow light is on")
self.r.turn_on(3)
def yellow_off(self):
print("Yellow light is off")
self.r.turn_off(3)
self.w.switch_to_yellow()
def green_on(self):
print("Green light is on")
self.r.turn_on(4)
self.w.switch_to_green()
def green_off(self):
print("Green light is off")
self.r.turn_off(4)
def leds_off(self):
print("LEDs are off")
self.w.turn_off_all()
def blink_yellow(self):
print("Yellow color is blinking")
self.w.blink_yellow()
def blink_red(self):
print("Red color is blinking")
self.w.blink_red()
def blink_green(self):
print("Green color is blinking")
self.w.blink_green()
if __name__ == "__main__":
import sys
@ -1110,8 +984,8 @@ if __name__ == "__main__":
# Initialize the CameraStreamApp with the UI
camera_app = CameraStreamApp(ui)
# initialize relay app
relay_app = RelayControl(ui)
# initialize light control app
light_app = LightControl(ui)
MainWindow.show()
sys.exit(app.exec_())