Skip to content

Commit

Permalink
GUI-Funktion hinzugefügt
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Schiller committed Dec 19, 2022
1 parent ec7e69d commit 55744f3
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ Pro Schüler wird 1 "Rennspiel-Calliope" benötigt, der direkt per USB an den Re

![Einzelspielmodus Basisversion](./einzelspiel-basis.png)

#### GUI (Grafische Nutzeroberfläche)

Seit Release "Speedy" (19.12.2022) können die Schritte 1 bis 5A statt manuell bzw. via Kommandozeile
auch via einer komfortablen grafischen Nutzeroberfläche (GUI) durchgeführt werden (Stand 19.12.2022 nur für Windows verfügbar)

Dazu im Unterverzeichnis `/ki-in-schulen-master/Calliope-Rennspiel/Python/` folgenden Befehl ausführen: `python ki-gui-win.py`

Die unten beschriebenen Schritte sind dann entsprechend über die Menüpunkte des erscheinenden Fenster nutzbar.

#### Schritt 1 - Rennspiel installieren

Diesen Schritt muss jeder Schüler durchführen.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ Pro Schülergruppe wird 1 "Datensammler-Calliope" benötigt, zu dem mehrere "Ren

![Gruppenspielmodus](./gruppenspiel.png)

#### GUI (Grafische Nutzeroberfläche)

Seit Release "Speedy" (19.12.2022) können die Schritte 1 bis 6A statt manuell bzw. via Kommandozeile auch via einer komfortablen grafischen Nutzeroberfläche (GUI) durchgeführt werden (Stand 19.12.2022 nur für Windows verfügbar).

Dazu im Unterverzeichnis `/ki-in-schulen-master/Calliope-Rennspiel/Python/` folgenden Befehl ausführen: `python ki-gui-win.py`

Die unten beschriebenen Schritte sind dann entsprechend über die Menüpunkte des erscheinenden Fenster nutzbar.

#### Schritt 1 - Rennspiel auf den Calliope Minis der Schüler installieren

Diesen Schritt mit angepasster Funkgruppe (__funkgruppe1__, __funkgruppe2__, ...) wiederholen, bis alle "Rennspiel-Calliopes" für alle Schüler aller Schülergruppen installiert sind.
Expand Down
11 changes: 9 additions & 2 deletions Calliope-Rennspiel/Python/ki-datenlogger.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#
# ki-datenlogger.py$
#
# (C) 2020, Christian A. Schiller, Deutsche Telekom AG
# (C) 2020-2022, Christian A. Schiller, Deutsche Telekom AG
# Diese Version: V2 vom 19.12.2022 auf Basis Workshoperfahrungen 2022
#
# Deutsche Telekom AG and all other contributors /
# copyright owners license this file to you under the
Expand Down Expand Up @@ -121,6 +122,12 @@ def on_press(key):

# Speichern der gesammelten Daten in CSV-Datei
stamp = strftime("%Y%m%d%H%M%S", gmtime())
file = './csv-rohdaten/ki-rennspiel-log-'+stamp+'.csv'

# Festlegen der Ausgabedatei (ohne Endung)
try:
file = sys.argv[2]
except:
file = './csv-rohdaten/ki-rennspiel-log-'+stamp+'.csv'

print("Trainingsdaten gespeichert in Datei: "+file)
collect.to_csv(file,index=False)
304 changes: 304 additions & 0 deletions Calliope-Rennspiel/Python/ki-gui-win.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
#
# ki-gui.py$
#
# (C) 2022, Arndt Baars, Christian A. Schiller, Deutsche Telekom AG
#
# Deutsche Telekom AG and all other contributors /
# copyright owners license this file to you under the
# MIT License (the "License"); you may not use this
# file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
#
# GUI fuer den KI-Workshop mit den Calliope
#
# Diese GUI ermoeglicht einen einfachen und schnellen Umgang mit den KI-Ressourcen.
#
# Folgende Features sind bereits enthalten:
# - Kopieren der Datensammler-Software auf den Calliope. Hier stehen zwei Optionen zur Auswahl.
# Zum einen auf Basis von OpenRoberta (Wahl des Kanals/Gruppe auf dem Calliope).
# Zum anderen auf Basis von Makecode (Direkte Auswahl von vier Punktgruppen).
# - Kopieren der Rennspieldateien auf den Calliope. Hier stehen zwei Optionen zur Auswahl.
# Zum einen auf Basis von OpenRoberta (Wahl des Kanals/Gruppe auf dem Calliope).
# Zum anderen auf Basis von Makecode (Direkte Auswahl von vier Punktgruppen).
# - Einfacher Start des Datensammlers auf dem lokalen Rechner.
# - Einfacher Start des KI-Trainings inkl. Layer-Konfig-Abfrage.
# - Einfacher Start des lokalen Rennspiels mit dem aktuell trainierten Modell.
# - Automatische USB-Port-Ermittlung
# - Automatische Calliope-Erkennung
# - Moeglichkeit, eigene Dateinamen vorzugeben
# - Sicherung der Einstellungen/Konfigurationen
#
# Geplante Features:
# - Einfaches Kopieren von trainierten Modellen auf den Calliope.
# - Einfuegen einer Hilfe mit Verweisen auf die vorhandenen Präsentationen und Materialien.
# - Portierung für Linux und MacOS
#
# Version 0.21 (Stand: 19.12.2022)
#
##########

from tkinter import *
from tkinter import simpledialog
import sys
import subprocess
import serial.tools.list_ports
import os
from ctypes import windll, create_unicode_buffer, c_wchar_p, sizeof
from string import ascii_uppercase
import pickle
import locale

os_umgebung = sys.platform
os_locale = locale.getdefaultlocale()[0]
print ("OS-Umgebung:")
print (os_umgebung)
print (os_locale)
print ("")

# donothing
def donothing():
win = Toplevel(fenster)
button = Button(win, text="Do nothing button")
button.pack()

def configEinlesen():
try:
f = open("ki-gui-win_conf.cfg","rb")
dict = pickle.load(f)
f.close()
return dict
except:
return {}

def configSpeichern(dict):
f = open("ki-gui-win_conf.cfg","wb")
pickle.dump(dict,f)
f.close()

global dictConfig
dictConfig = configEinlesen()
if (not dictConfig):
# Default-Konfig
dictConfig = {"PythonEXE":".", "CalliKIDir":".", "COMPort":"COM7", "Dateiname":"ki-rennspiel-mein-spiel"}
print("Einstellungen:")
print (dictConfig["Dateiname"])
print (dictConfig["PythonEXE"])
print (dictConfig["CalliKIDir"])
print (dictConfig["COMPort"])
print ("")

# Kopiert die notwendigen Daten fuer einen Datensammler auf den Calliope.
# Wenn keine Gruppe angegeben wird, dann wird der Datensammler von OpenRoberta genutzt.
# Sonst wird der entsprechende Datensammler auf Basis von Makecode kopiert.
def copyDatensammler(gruppe=0):
src = dictConfig["CalliKIDir"] + '\\OpenRoberta\\datensammler-openroberta.hex'
if (gruppe != 0):
if (gruppe == 1):
src = dictConfig["CalliKIDir"] + '\Makecode\\datensammler-funkgruppe1-makecode.hex'
elif (gruppe == 2):
src = dictConfig["CalliKIDir"] + '\Makecode\\datensammler-funkgruppe2-makecode.hex'
elif (gruppe == 3):
src = dictConfig["CalliKIDir"] + '\Makecode\\datensammler-funkgruppe3-makecode.hex'
else:
src = dictConfig["CalliKIDir"] + '\Makecode\\datensammler-funkgruppe4-makecode.hex'
dst = get_calliope_drive()
if dst:
dst = dst + '\\'
cmd = 'copy "%s" "%s"' % (src, dst)
os.system(cmd)
else:
print ("Leider konnte kein Ziellaufwerk ermittelt werden!")

# Kopiert die notwendigen Daten fuer das Rennspiel auf den Calliope.
# Wenn keine Gruppe angegeben wird, dann wird das Rennspiel von OpenRoberta genutzt.
# Sonst wird das entsprechende Rennspiel auf Basis von Makecode kopiert.
def copyRennspiel(gruppe=0):
src = dictConfig["CalliKIDir"] + '\\OpenRoberta\\rennspiel-openroberta.hex'
if (gruppe != 0):
if (gruppe == 1):
src = dictConfig["CalliKIDir"] + '\Makecode\\rennspiel-funkgruppe1-makecode.hex'
elif (gruppe == 2):
src = dictConfig["CalliKIDir"] + '\Makecode\\rennspiel-funkgruppe2-makecode.hex'
elif (gruppe == 3):
src = dictConfig["CalliKIDir"] + '\Makecode\\rennspiel-funkgruppe3-makecode.hex'
else:
src = dictConfig["CalliKIDir"] + '\Makecode\\rennspiel-funkgruppe4-makecode.hex'
dst = get_calliope_drive()
if dst:
dst = dst + '\\'
cmd = 'copy "%s" "%s"' % (src, dst)
os.system(cmd)
else:
print ("Leider konnte kein Ziellaufwerk ermittelt werden!")

def portErmitteln():
for port, desc, hwid in serial.tools.list_ports.grep("USB Serial Device"):
return port
else:
for port, desc, hwid in serial.tools.list_ports.grep("Serielles USB"):
return port
else:
return False

"""
ports = serial.tools.list_ports.comports()
for port, desc, hwid in sorted(ports):
print(desc)
if (desc.startswith('USB Serial Device')):
# desc.startswith('Serielles USB') für Deutsch
#'print("{}: {} [{}]".format(port, desc, hwid))
print(f"Erkannter Port: {port}")
return port
"""

# Unabhängig von der Konfigurationsdatei (Einstellungsseite) den Port für diesen Rechner automatisch ermitteln.
# Falls das nicht funktioniert, wird der Port aus der Konfiguration verwendet.
usbPort = portErmitteln()
if usbPort:
dictConfig["COMPort"] = usbPort
print(f"Erkannter Port: {usbPort}")

def get_calliope_drive():
volumeNameBuffer = create_unicode_buffer(1024)
fileSystemNameBuffer = create_unicode_buffer(1024)
serial_number = None
max_component_length = None
file_system_flags = None
erg = ""
drive_names = []
# Get the drive letters, then use the letters to get the drive names
bitmask = (bin(windll.kernel32.GetLogicalDrives())[2:])[::-1] # strip off leading 0b and reverse
drive_letters = [ascii_uppercase[i] + ':/' for i, v in enumerate(bitmask) if v == '1']
for d in drive_letters:
rc = windll.kernel32.GetVolumeInformationW(c_wchar_p(d), volumeNameBuffer, sizeof(volumeNameBuffer),
serial_number, max_component_length, file_system_flags,
fileSystemNameBuffer, sizeof(fileSystemNameBuffer))
if rc:
#drive_names.append(f'{volumeNameBuffer.value}({d[:2]})') # disk_name(C:)
if (volumeNameBuffer.value == "MINI"):
erg = d.rstrip(d[-1])
return erg

# ki-datenlogger.py
def ki_datenlogger():
# Hier wird als erster Prozessschritt ein Dateiname abgefragt und auch für die weiteren Schritte genutzt.
# Falls hier kein Dateiname angegeben wird, wird der Dateiname aus der Konfig weiter verwendet.
dateiname = simpledialog.askstring("Input", "Bitte einen Dateinamen angeben (ohne Leerzeichen!)", initialvalue=dictConfig["Dateiname"], parent=fenster)
if dateiname is not None:
dictConfig["Dateiname"] = dateiname
cmd = dictConfig["CalliKIDir"] + '\\Python\\ki-datenlogger.py'
outputfile = dictConfig["CalliKIDir"] + '\\Python\\csv-rohdaten\\' + dictConfig["Dateiname"] + '.csv'
subprocess.run([sys.executable, cmd, dictConfig["COMPort"], outputfile], check=True)

# ki-trainieren-sklearn.py
def ki_trainieren():
cmd = dictConfig["CalliKIDir"] + '\\Python\\ki-trainieren-sklearn.py'
layer = simpledialog.askstring("Input", "Bitte Hidden Layer definieren - A,B oder A,B,C", initialvalue='7,7', parent=fenster)
param = dictConfig["CalliKIDir"] + '\\Python\\csv-rohdaten\\' + dictConfig["Dateiname"] + '.csv'
outputbase = dictConfig["CalliKIDir"] + '\\Python\\modelle\\' + dictConfig["Dateiname"]
subprocess.run([sys.executable, cmd, param, layer, outputbase], check=True)

# ki-rennspiel.py
def ki_rennspiel():
cmd = dictConfig["CalliKIDir"] + '\\Python\\ki-rennspiel.py'
param = dictConfig["CalliKIDir"] + '\\Python\\modelle\\' + dictConfig["Dateiname"] + '.pkcls'
subprocess.run([sys.executable, cmd, 'sklearn', param], check=True)

# Einstellungen-Win definieren
def einstellungen():
einstellungenWin = Toplevel(fenster)
einstellungenWin.geometry('700x250')

def exit_btn():
dictConfig["Dateiname"] = dateiname_feld.get()
dictConfig["PythonEXE"] = pythonVerz_feld.get()
dictConfig["CalliKIDir"] = kiVerz_feld.get()
configSpeichern(dictConfig)
einstellungenWin.destroy()
einstellungenWin.update()

dateiname_label = Label(einstellungenWin, text="Dateiname")
dateiname_feld = Entry(einstellungenWin, justify=LEFT, bd=5, width=40)
dateiname_feld.insert(END, dictConfig["Dateiname"])
pythonVerz_label = Label(einstellungenWin, text="Python-Verzeichnis")
pythonVerz_feld = Entry(einstellungenWin, justify=LEFT, bd=5, width=80)
pythonVerz_feld.insert(END, dictConfig["PythonEXE"])
kiVerz_label = Label(einstellungenWin, text="KI-Verzeichnis")
kiVerz_feld = Entry(einstellungenWin, justify=LEFT, bd=5, width=80)
kiVerz_feld.insert(END, dictConfig["CalliKIDir"])
usbPort_label = Label(einstellungenWin, text="USB-Port")
usbPort_feld = Entry(einstellungenWin, justify=LEFT, bd=5, width=20)
usbPort_feld.insert(END, dictConfig["COMPort"])
#uebernehmen_button = Button(einstellungenWin, text="Einstellungen übernehmen", command=einstellungenWin.destroy)
uebernehmen_button = Button(einstellungenWin, text="Einstellungen übernehmen", command=exit_btn)

dateiname_label.grid(row = 0, column = 0, pady = 15, padx = 20)
dateiname_feld.grid(row = 0, column = 1, sticky=W)
pythonVerz_label.grid(row = 1, column = 0, pady = 15, padx = 20)
pythonVerz_feld.grid(row = 1, column = 1, sticky=W)
kiVerz_label.grid(row = 2, column = 0, pady = 15, padx = 20)
kiVerz_feld.grid(row = 2, column = 1, sticky=W)
usbPort_label.grid(row = 3, column = 0, pady = 15, padx = 20)
usbPort_feld.grid(row = 3, column = 1, sticky=W)
uebernehmen_button.grid(row = 4, column = 0, columnspan = 2, pady = 15)

# Ein Fenster erstellen
fenster = Tk()
# Den Fenstertitle erstellen
fenster.title("KI mit dem Calliope")
# Fenstergroesse vorgeben
fenster.geometry('400x200')

# Ein Menu anlegen
menubar = Menu(fenster)
# Datei-Menu
dateimenu = Menu(menubar, tearoff=0)
dateimenu.add_command(label="Einstellungen", command=einstellungen)
dateimenu.add_separator()
dateimenu.add_command(label="Beenden", command=fenster.quit)
menubar.add_cascade(label="Datei", menu=dateimenu)

# Calli-Menu
callimenu = Menu(menubar, tearoff=0)
callimenu.add_command(label="Datensammler kopieren", command=copyDatensammler)
callimenu.add_command(label="Rennspiel kopieren", command=copyRennspiel)
callimenu.add_separator()
callimenu.add_command(label="Makecode Datensammler kopieren Gruppe 1", command=lambda: copyDatensammler(1))
callimenu.add_command(label="Makecode Datensammler kopieren Gruppe 2", command=lambda: copyDatensammler(2))
callimenu.add_command(label="Makecode Datensammler kopieren Gruppe 3", command=lambda: copyDatensammler(3))
callimenu.add_command(label="Makecode Datensammler kopieren Gruppe 4", command=lambda: copyDatensammler(4))
callimenu.add_command(label="Makecode Rennspiel kopieren Gruppe 1", command=lambda: copyRennspiel(1))
callimenu.add_command(label="Makecode Rennspiel kopieren Gruppe 2", command=lambda: copyRennspiel(2))
callimenu.add_command(label="Makecode Rennspiel kopieren Gruppe 3", command=lambda: copyRennspiel(3))
callimenu.add_command(label="Makecode Rennspiel kopieren Gruppe 4", command=lambda: copyRennspiel(4))
menubar.add_cascade(label="Calli", menu=callimenu)

# KI-Menu
kimenu = Menu(menubar, tearoff=0)
kimenu.add_command(label="Datensammler starten", command=ki_datenlogger)
kimenu.add_separator()
kimenu.add_command(label="KI anlernen", command=ki_trainieren)
kimenu.add_command(label="KI testen", command=ki_rennspiel)
#kimenu.add_separator()
#kimenu.add_command(label="KI auf Calli kopieren", command=donothing)
menubar.add_cascade(label="KI", menu=kimenu)

# Hilfe-Menu
#hilfemenu = Menu(menubar, tearoff=0)
#hilfemenu.add_command(label="Hilfe", command=donothing)
#menubar.add_cascade(label="Hilfe", menu=hilfemenu)

fenster.config(menu=menubar)
# In der Ereignisschleife auf Eingabe des Benutzers warten.
fenster.mainloop()
Loading

0 comments on commit 55744f3

Please sign in to comment.