diff --git a/.gitignore b/.gitignore index 9d22d3c..50f6a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +/files/Thumbs.db diff --git a/config.yaml b/config.yaml index 4309d04..05709c1 100755 --- a/config.yaml +++ b/config.yaml @@ -7,8 +7,45 @@ station: calibration_weight: 0 terminal_weight: 0 cameraRotation: 180 - weightResetInMinutes: 20 - audio_gain: +20 #audio_gain is to lift the volume of the microphone.To high values generate white noice, to low doesn't change anything + weightResetInMinutes: 20 + audio_gain: +20 +environment_values: + temperature : true + humidity: true + #If a Distancesensor is added set this to true + silolevel: false +sensors: + temperature: + #0- None 1-DHT22 + type: 1 + humidity: + #0- None 1-DHT22 + type: 1 + silolevel: + #0-None 5-vl53l0x + type: 5 +hardware: + # This section defines GPIO Ports, and other hardware dependent values for each Sensor + DHT22: + #Default DATA_PIN:16 + DATA_PIN: 16 + HX711: + #Default SCK_PIN: 23,DT_PIN:17 + SCK_PIN: 23 + DT_PIN: 17 + SPH0645KM4H: + #Default LRCL_PIN: 19,OUT_PIN:20, CLK_PIN:18 + LRCL_PIN: 19 + OUT_PIN: 20 + CLK_PIN: 18 + vl53l0x: + #Default GPIO1_PIN: 4, XSHUT_PIN: 26, i2cAddress: 0x29 + GPIO1_PIN: 4 + XSHUT_PIN: 26 + empty_value: 0 + full_value: 0 + i2cAddress: "0x29" misc: loglevel: 20 # loglevel can be either 0-NOTSET, 10-DEBUG, 20-INFO, 30-WARNING, 40-ERROR, 50-CRITICAL - dev_mode: No # Yes - for testing, nothing is send to the server, No - normal usage, sync to server is enabled + dev_mode: No # Yes - for testing, nothing is sent to the server, No - normal usage, sync to server is enabled + diff --git a/files/SilolevelSensor_Pinout.png b/files/SilolevelSensor_Pinout.png new file mode 100644 index 0000000..31e5317 Binary files /dev/null and b/files/SilolevelSensor_Pinout.png differ diff --git a/files/birdSilo.png b/files/birdSilo.png new file mode 100644 index 0000000..59d4416 Binary files /dev/null and b/files/birdSilo.png differ diff --git a/getStarted.md b/getStarted.md index 402ad09..96ccbb1 100755 --- a/getStarted.md +++ b/getStarted.md @@ -1,18 +1,18 @@ # Vorbereitung Pi - Installiere den Raspberry Pi Imager auf deinem lokalen Computer (Den Imager findest du hier: https://www.raspberrypi.com/software/) -- Verbinde die SD Karte, die später einmal im Raspberry Pi genutzt wird, mit deinem Computer (in der Regel kannst du die Micro SD Karte mit einem beigelegten Adapter in deinen PC stecken) - - Da alle Daten auf der SD Karte gelöscht werden, solltest du eventuelle Daten vor den folgenden Schritten abgespeichert haben +- Verbinde die SD-Karte, die später einmal im Raspberry Pi genutzt wird, mit deinem Computer (in der Regel kannst du die Micro SD-Karte mit einem beigelegten Adapter in deinen PC stecken) + - Da alle Daten auf der SD-Karte gelöscht werden, solltest du eventuelle Daten vor den folgenden Schritten abgespeichert haben - Starte den Raspberry Pi Imager und wähle dann im User Interface "Raspberry PI OS (32-BIT)" als Betriebssystem aus - - Wähle im User Interface des Raspberry Pi Imager, die SD Karte aus, die du eben mit deinem Computer verbunden hast + - Wähle im User Interface des Raspberry Pi Imager, die SD-Karte aus, die du eben mit deinem Computer verbunden hast - Um den Prozess, der Betriebssystemübertragung zu beginnen, wähle entsprechend "SCHREIBEN" aus - - Bestätige, dass alle Daten auf der SD Karte gelöscht werden können - - Sobald die Bestätigung angezeigt wird, dass "Raspberry Pi OS (32-bit)" auf der eingelegten SD Karte gespeichert wurde, kann diese vom Computer entfernt werden und in den Raspberry Pi eingelegt werden (der Slot dafür ist auf der Unterseite des RaspberryPi, gegenüber der USB-Anschlüsse) + - Bestätige, dass alle Daten auf der SD-Karte gelöscht werden können + - Sobald die Bestätigung angezeigt wird, dass "Raspberry Pi OS (32-bit)" auf der eingelegten SD-Karte gespeichert wurde, kann diese vom Computer entfernt werden und in den Raspberry Pi eingelegt werden (der Slot dafür ist auf der Unterseite des RaspberryPi, gegenüber der USB-Anschlüsse) - Solange das Überspielen auf die SD-Karte läuft, können schon die nächsten Schritte bis zum Stromanschluss durchgeführt werden -- Nutze zwei der vier USB Ports am Raspberry Pi um sowohl Tastatur, als auch eine Maus an den Raspberry Pi anzuschließen -- Nutze den Mini-HDMI Port mit der Aufschrift "HDMI0" um einen Bildschrim mit dem Raspberry Pi zu verbinden +- Nutze zwei der vier USB-Ports am Raspberry Pi um sowohl Tastatur, als auch eine Maus an den Raspberry Pi anzuschließen +- Nutze den Mini-HDMI Port mit der Aufschrift "HDMI0" um einen Bildschirm mit dem Raspberry Pi zu verbinden - Optional wäre über "HDMI1" ein weiter Bildschirm verbindbar - Schließe das RaspberryPi Netzteil an der Buchse mit der Bezeichnung "POWER IN" an, um den Raspberry Pi mit dem Strom zu verbinden -- Sofern dein Bildschirm eingeschaltet ist, sollte der Raspberry Pi nun starten und eine graphische Nutzerberfläche angezeigt werden, dies kann ein paar Minuten dauern. +- Sofern dein Bildschirm eingeschaltet ist, sollte der Raspberry Pi nun starten und eine grafische Nutzeroberfläche angezeigt werden, dies kann ein paar Minuten dauern. # initiales Starten Raspberry Pi - Es kann sein, dass es zu der Meldung kommt "Resized root file system. Rebooting in 5 seconds". Dann einfach abwarten und der Raspberry Pi startet selbständig erneut @@ -61,13 +61,16 @@ Zur Info: Eingaben im Terminal können getätigt werden, wenn die (grüne) Zeile - Für die vorherigen drei Schritte ist eine detaillierte Anleitung unter folgendem Link zu finden: https://github.com/Infineon/i2s-microphone/wiki/Raspberry-Pi-Audio-Processing-with-Python - Eingabe im Terminal: `pip3 install SoundFile` +## Futtersilo Füllstand Sensor +- Eingabe im Terminal: sudo pip3 install adafruit-circuitpython-vl53l0x + # Hardware anschließen Die Belegung und Nummerierung der Pins / Ports des Raspberry Pi kann unter anderem hier nachgeguckt werden: https://www.raspberrypi-spy.co.uk/wp-content/uploads/2012/06/Raspberry-Pi-GPIO-Header-with-Photo.png ## Kamera -- Das breite weiße Kamerakabel an einem Ende an die Kamera anschließen und am anderen Ende an den RasperryPi (an den Anschluss der mit Kamera betitelt ist), so, dass das blaue Ende zu den USB Anschlüssen zeigt -- Das Kabel lässt sich sowohl an Pi als auch an Kamera durch das hochziehen, der grauen Abdeckung befestigen. Dieses anschließend wieder runterdrücken um das Kabel zu fixieren +- Das breite weiße Kamerakabel an einem Ende an die Kamera anschließen und am anderen Ende an den RaspberryPi (an den Anschluss der mit Kamera betitelt ist), so, dass das blaue Ende zu den USB-Anschlüssen zeigt +- Das Kabel lässt sich sowohl an Pi als auch an Kamera durch das hochziehen, der grauen Abdeckung befestigen. Dieses anschließend wieder herunterdrücken um das Kabel zu fixieren ## Luftfeuchte und Temperatursensor - "+" -> Port 1 (3,3 V) @@ -94,20 +97,28 @@ Die Belegung und Nummerierung der Pins / Ports des Raspberry Pi kann unter ander - GND -> Port 20 (GND) - 3V -> Port 17 (3,3 V) +## Futtersilo Füllstand Sensor +- VIN -> Port 4 (5 V) +- GND -> Port 14 (GND) +- SCL -> Port 5 (GPIO 3/SCL) +- SDA -> Port 3 (GPIO 2/SDA) +- GPIO1 -> Port 7 (GPIO 4) +- XSHUT -> Port 37 (GPIO 26) + # Setup Birdiary (Je nachdem mit welchen Benutzer man sich am RaspberryPi anmeldet, können die Verzeichnisse abweichen. Solltest du dich nicht mit dem Benutzer 'pi' anmelden, musst du den Verzeichnisnamen gegen Deinen austauschen. Aus /home/pi wird dann /home/) - Eingabe im Terminal: `cd /home/pi/` - Eingabe im Terminal: `git clone https://github.com/CountYourBirds/station.git` - Erstelle unter https://wiediversistmeingarten.org/react/createbox deine eigene Station - Kopiere die Box-Id in die config.yaml-Datei im Ordner `/home/pi/station` -- Navigiere in den Ordner station und öffne die `config.yaml`-Datei mit geany (Rechtsclick auf die Datei -> Geany) +- Navigiere in den Ordner station und öffne die `config.yaml`-Datei mit geany (Rechtsklick auf die Datei -> Geany) - Kopiere die Box-id an die entsprechende Stelle in der Datei - Speichere die Datei (Shortcut: Str + S) # Ermöglichung des Zugriffs auf den Raspberry Pi via VNC - Eingabe im Terminal: `sudo nano /boot/config.txt` (detaillierte Anleitung für die Veränderung der Datei hier: https://www.shellhacks.com/raspberry-pi-force-hdmi-hotplug/) - - Navigiere mit den Pfeiltasten der Tastatur zu den den folgenden Zeilen, entferne die Rauten am Anfang der Zeilen und nimm die entsprechenden Anpassungen vor: - - Setze den Paramter hdmi_force_hotplug auf 1: `hdmi_force_hotplug=1` + - Navigiere mit den Pfeiltasten der Tastatur zu den folgenden Zeilen, entferne die Rauten am Anfang der Zeilen und nimm die entsprechenden Anpassungen vor: + - Setze den Parameter hdmi_force_hotplug auf 1: `hdmi_force_hotplug=1` - Setze den Parameter hdmi_group auf 1: `hdmi_group=1` - Setze den Parameter hdmi_mode auf 16: `hdmi_mode=16` - Zum Speichern: @@ -125,9 +136,9 @@ Die Belegung und Nummerierung der Pins / Ports des Raspberry Pi kann unter ander - Auswahl von: Finish - Auswahl von: Ja (Reboot) - Kurz abwarten und das `Va` Icon anklicken oben rechts neben dem WLAN Zeichen -- IP Adresse die unter Konnektivität gelistet ist auf Rechner der auf PI zugreifen soll im VNC Viewer eintragen (vorheriger Download der VNC Viewer App für entsprechendes Betriebssystem: https://www.realvnc.com/de/connect/download/viewer/) und mit dem Raspberry Pi verbinden +- IP-Adresse die unter Konnektivität gelistet ist auf Rechner der auf PI zugreifen soll im VNC Viewer eintragen (vorheriger Download der VNC Viewer App für entsprechendes Betriebssystem: https://www.realvnc.com/de/connect/download/viewer/) und mit dem Raspberry Pi verbinden - Passwort und Benutzername des pis entsprechend eintragen -- Verbindung sollte funktioneren +- Verbindung sollte funktionieren # Automatische Ausführung ermöglichen - Eingabe im Terminal: `sudo apt-get install xterm` diff --git a/get_pi_requirements.sh b/get_pi_requirements.sh index da2fe3f..f7d7a89 100755 --- a/get_pi_requirements.sh +++ b/get_pi_requirements.sh @@ -26,13 +26,13 @@ else fi printf "\033[0;35m####### Install general python packages#######\033[0m\n" # General -pip3 install pyyaml +pip3 install pyyaml ruamel.yaml pip3 install schedule pip3 install --upgrade numpy printf "\033[0;35m####### Install Senors #######\033[0m\n" # Sensors -sudo pip3 install adafruit-circuitpython-dht +sudo pip3 install adafruit-circuitpython-dht adafruit-circuitpython-vl53l0x sudo apt-get install libgpiod2 # Microphone @@ -55,8 +55,10 @@ xrdb -merge ~/.Xresources #add Desktop Starter ##change ~/station to absolute Path, because ~ is not working for Starter. sed -i "s|~/station|$HOME/station|g" birdiary.desktop +sed -i "s|~/station|$HOME/station|g" silolevel_calibrate.desktop ## copy modified starter to Desktop cp ~/station/birdiary.desktop ~/Desktop +cp ~/station/silolevel_calibrate.desktop ~/Desktop # activate the HDMI output, even if no monitor is detected sudo sed -i '3ahdmi_force_hotplug=1' /boot/config.txt diff --git a/main.py b/main.py index 02b79fa..28d26c7 100755 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ import requests dev_mode = False +silolevel_active: bool = False if not dev_mode: import requests @@ -118,6 +119,36 @@ SENSOR_PIN = D16 # use not board but GPIO number dht22 = adafruit_dht.DHT22(SENSOR_PIN, use_pulseio=False) +# Set Silolevel Sensor +logging.info("Setup Silolevel Sensor!") +if yamlData["environment_values"]["silolevel"]: + if yamlData["hardware"]["vl53l0x"]["empty_value"] == 0 and yamlData["hardware"]["vl53l0x"]["empty_value"] == 0: + silolevel_active = False + logging.warning("Silolevel Sensor not calibrated yet. Please calibrate the sensor first") + print("\033[91mSilolevel Sensor not calibrated yet. Please calibrate the sensor first. Will start without the " + "Sensor\033[0m") + time.sleep(1) + else: + try: + import silolevel + try: + silo = silolevel.SiloLevelSensor(int(yamlData["hardware"]["vl53l0x"]["GPIO1_PIN"]), + int(yamlData["hardware"]["vl53l0x"]["XSHUT_PIN"]), + yamlData["hardware"]["vl53l0x"]["i2cAddress"]) + logging.debug("Silolevel Sensor initiated") + silolevel_active = True + except (ValueError, IOError): + silolevel_active = False + logging.error("Sensor not found! Check the connections") + except ModuleNotFoundError: + silolevel_active = False + logging.error("WARNING! Silolevel Sensor Library not installed, start Script without Sensor\n Please " + "restart get_pi_requirements.sh") + time.sleep(1) +else: + silolevel_active = False + logging.debug("Silolevel Sensor deactivated in configuration") + print("Silolevel Sensor deactivated in configuration") # Setup Balance logging.info("Setup balance!") EMULATE_HX711 = False @@ -147,8 +178,10 @@ from rec_unlimited import record from multiprocessing import Process from pydub import AudioSegment # for converting the raw to mp3 + audio_gain = yamlData["station"]["audio_gain"] -logging.info("Setup finished!") +logging.info("Setup finished!") + def write_environment(environment_data): filename = 'environments/' + environment_data['date'] + '.json' @@ -198,8 +231,29 @@ def send_realtime_environment(environmentData): write_environment(environmentData) else: send_data() - -# Function to track a environment + + +def send_realtime_feed(box_id, feedData: dict = {}): + if dev_mode: + logging.warning('send_feed deactivated') + logging.warning('received: ' + str(feedData)) + else: + try: + r = requests.post(serverUrl + "feed/" + str(box_id), json=feedData) + print("Silolevel Data send with the corresponding feed_id") + print(r.content) + logger.info('Silolevel send to Server') + logger.debug('Feed Post Request Data: URL: %s ; status_code: %s; Text: %s ;json: %s' % + (r.url, r.status_code, r.text, feedData)) + except (requests.ConnectionError, requests.Timeout) as exception: + logging.warning('No internet connection. ' + str(exception)) + logging.warning("Saving Silolevel data to send later") + # write_environment(environmentData) + else: + send_data() + # Function to track a environment + + def track_environment(): try: logging.info("Collect Environment Data") @@ -215,7 +269,7 @@ def track_environment(): except Exception as e: logging.error(e) - + # predefined variables environmentData = None audio_filename = None @@ -225,6 +279,7 @@ def track_environment(): temp_audio_filename = 'temp/audio.raw' # the filename extension has to be .raw to convert it later to mp3 data_filename = None recorder = None +feedData: dict = {} def tare(): @@ -250,13 +305,17 @@ def set_filenames(movementStartDate): global data_filename data_filename = 'savedMovements/' + str(movementStartDate) + '.json' - + # Function to track a movement def track_movement(): values = [] # schedule an environment track for every x minutes schedule.every(environmentTimeDeltaInMinutes).minutes.do(track_environment) + track_environment() + if silolevel_active: + schedule.every(environmentTimeDeltaInMinutes).minutes.do(track_feed) + track_feed() schedule.every(weightResetInMinutes).minutes.do(tare) while True: @@ -313,9 +372,9 @@ def track_movement(): # stop audio recording and move temporary file to output directory terminate_recorder() tags_json = dict(title=(movementStartDate.strftime("%Y-%M-%d %H:%m:%S") + '_' + boxId), - track=1, artist=boxId, - copyright='www.wiediversistmeingarten.org', - genre="Noise", date=movementStartDate.strftime("%Y-%M-%d")) + track=1, artist=boxId, + copyright='www.wiediversistmeingarten.org', + genre="Noise", date=movementStartDate.strftime("%Y-%M-%d")) RawAudio = AudioSegment.from_raw(temp_audio_filename, sample_width=2, frame_rate=48000, channels=1) RawAudioVolumeLifted = RawAudio.apply_gain(audio_gain) # lift up the volume RawAudioVolumeLifted.export(audio_filename, format='mp3', tags=tags_json, id3v2_version="3") @@ -340,7 +399,26 @@ def track_movement(): except (KeyboardInterrupt, SystemExit): cleanAndExit() - + +def track_feed(): + try: + print("Collect Feed Data") + logger.info('Collect Feed Data') + feed: dict = {"date": str(datetime.now()), + "silolevel": silo.read_Silolevel(int(yamlData["hardware"]["vl53l0x"]["empty_value"]), + int(yamlData["hardware"]["vl53l0x"]["full_value"])) + } + + print("Feed Data: ", feed) + logger.info('Feed Data: %s' % feed) + send_realtime_feed(boxId, feed) + global feedData + feedData = feed + except Exception as e: + print(e) + logger.exception(e, exc_info=True) + + def cleanAndExit(): camera.close() terminate_recorder() diff --git a/silolevel.py b/silolevel.py new file mode 100644 index 0000000..9529439 --- /dev/null +++ b/silolevel.py @@ -0,0 +1,74 @@ +import logging +import board +import busio +import time +import RPi.GPIO as GPIO + +try: + import adafruit_vl53l0x +except ModuleNotFoundError: + print("Library adafruit-circuitpython-vl53l0x not found. Try to install via 'sudo apt install " + "adafruit-circuitpython-vl53l0x' ") + raise ModuleNotFoundError + + +class SiloLevelSensor: + + def __init__(self, gpio_gpio1: int = 4, gpio_xshut: int = 26, i2c_Address: hex = 0x29): + """ initialize Sensor """ + self.logger = logging.getLogger('main.silolevel') + self.gpio_gpio1: int = gpio_gpio1 + self.gpio_xshut: int = gpio_xshut + + # Initialize GPIO + GPIO.setmode(GPIO.BCM) + GPIO.setup(self.gpio_gpio1, GPIO.IN) + GPIO.setup(self.gpio_xshut, GPIO.OUT) + GPIO.output(self.gpio_xshut, True) # enable Sensor for 1st initialisation + + # Initialize I2C and sensor + i2c = busio.I2C(board.SCL, board.SDA) + try: + self.sensor = adafruit_vl53l0x.VL53L0X(i2c=i2c, address=int(i2c_Address, 16)) + # GPIO.output(self.gpio_xshut, False) # disable Sensor for reduce Power consumption + except ValueError: + print("Sensor not found under given address (normal: 0x29)") + raise ValueError + + def read_Silolevel(self, empty_value: int = 0, full_value: int = 0): + GPIO.output(self.gpio_xshut, True) + time.sleep(0.0000000001) + + self.sensor = adafruit_vl53l0x.VL53L0X(busio.I2C(board.SCL, board.SDA)) + self.sensor.measurement_timing_budget = 200000 + + silolevel = self.sensor.range + print('Measured Value: ', silolevel) + self.logger.debug('Measured Silolevel %s' % silolevel) + GPIO.output(self.gpio_xshut, False) + silolevel: float = 100 * (silolevel - empty_value) / (full_value - empty_value) + + if silolevel < 0.0: + silolevel = 0 + else: + silolevel = int(round(silolevel, 0)) + return silolevel # value is in % + + def read_plain_value(self): + GPIO.output(self.gpio_xshut, True) + time.sleep(0.0000000001) + self.sensor = adafruit_vl53l0x.VL53L0X(busio.I2C(board.SCL, board.SDA)) + self.sensor.measurement_timing_budget = 200000 + silolevel = self.sensor.range + print('Measured Value: ', silolevel) + self.logger.debug('Measured Silolevel %s' % silolevel) + GPIO.output(self.gpio_xshut, False) + return silolevel # value is in % + + +if __name__ == '__main__': + # Example usage + gpio_gpio1_value = 4 + gpio_xshut_value = 26 + silo_sensor = SiloLevelSensor(gpio_gpio1_value, gpio_xshut_value) + print('Silolevel %s Percent' % (silo_sensor.read_Silolevel(empty_value=210, full_value=56))) diff --git a/silolevel_calibrate.desktop b/silolevel_calibrate.desktop new file mode 100644 index 0000000..c3e4e58 --- /dev/null +++ b/silolevel_calibrate.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=SiloLevel Calibrate +Type=Application +Terminal=false +Comment=Start Silolevel calibration tool +Icon=~/station/files/birdSilo.png +Exec=~/station/silolevel_calibrate.py +Categories=Utility +X-KeepTerminal=true +Path=~/station diff --git a/silolevel_calibrate.py b/silolevel_calibrate.py new file mode 100644 index 0000000..c85a4c5 --- /dev/null +++ b/silolevel_calibrate.py @@ -0,0 +1,271 @@ +#!/usr/bin/python3 +import tkinter as tk +from tkinter import messagebox, font, StringVar + +import ruamel.yaml + +import silolevel + +GPIO1_PIN: int = 4 +XSHUT_PIN: int = 26 +i2cAddress: str = '0x29' + + +def read_configuration(): + print("Reading configuration file!") + yaml = ruamel.yaml.YAML() + with open("config.yaml", 'r') as stream: + yaml_data = yaml.load(stream) + stream.close() + return yaml_data + + +def read_silolevel(): + silo = silolevel.SiloLevelSensor(gpio_gpio1=GPIO1_PIN, gpio_xshut=XSHUT_PIN, i2c_Address=i2cAddress) + silolev = silo.read_plain_value() + return silolev + + +class FutterSiloCheckerApp: + option_var: StringVar + languages: tuple[str, str] + + def __init__(self, mroot, empty_value: int = 0, full_value: int = 0, sensor_status: bool = True): + self.chk_activate_Sensor = None + self.opt_language = None + self.lbl_activate_sensor = None + self.btn_saveValue = None + self.lbl_FullValue = None + self.lbl_Step2 = None + self.lbl_EmptyValue = None + self.lbl_Step1 = None + self.lbl_language = None + self.lbl_titel = None + self.btn_saveValues = None + self.btn_capture_full = None + self.btn_capture_empty = None + self.root = mroot + self.root.title("Futtersilo Sensor Abgleich") + self.txt_EmptyValue = None + self.txt_FullValue = None + self.EmptyValue = empty_value + self.FullValue = full_value + self.sensor_status = sensor_status + self.languages = ('Deutsch', 'English') + self.option_var = tk.StringVar() + self.chk_var = tk.BooleanVar() + self.option_var.set(self.languages[0]) + self.langIndex = 0 + self.lbl_titel_text: tuple[str, str] = ("Mit diesem Tool wird der Futtersilosensor abgeglichen. Es werden die " + "Werte erfasst wenn das Silo leer ist, und wenn das Silo komplett " + "gefüllt ist.", + "This tool is used to calibrate the feed silo sensor. The values are " + "recorded " + " when the silo is empty and when the silo is completely full.") + self.lbl_language_text: tuple[str, str] = ("Sprache :", "Language :") + self.lbl_Step1_text: tuple[str, str] = ("Schritt 1:", "Step 1:") + self.lbl_Step2_text: tuple[str, str] = ("Schritt 2:", "Step 2:") + self.btn_capture_text: tuple[str, str] = ("Erfassen", "Capture") + self.lbl_EmptyValue_text: tuple[str, str] = ( + "Leeren Sie das Futtersilo komplett und achten Sie darauf, das der Sensor " + "in Richtung boden zeigt und klicken Sie auf 'Erfassen'", + "Empty the feed silo completely and make sure that the sensor " + "is pointing towards the ground and click on 'Capture'") + self.lbl_FullValue_text: tuple[str, str] = ("Legen Sie ein Stück Pappe o.ä. an der Oberkante des Silos " + "um ein volles Silo zu simulieren und klicken Sie auf 'Erfassen'", + "Place a piece of cardboard or similar on the top edge of the silo " + "to simulate a full silo and click on 'Capture'") + self.btn_saveValues_text: tuple[str, str] = ("Werte speichern", "Save values") + self.lbl_activate_sensor_text: tuple[str, str] = ("Futtersilosensor hier ein oder ausschalten", + "Silolevel Sensor activate or deactivate") + self.chk_activate_sensor_text: tuple[str, str] = ("einschalten/ausschalten", "activate/deactivate") + self.create_widgets() + + def create_widgets(self): + + bold_font = font.Font(weight=font.BOLD, size=11) + + self.lbl_titel = tk.Label(self.root, + compound="left", justify="left", + wraplength=500, + text=self.lbl_titel_text[self.langIndex], + font=bold_font + ) + self.lbl_titel.grid(row=0, column=0, columnspan=2) + + self.lbl_language = tk.Label(self.root, + compound="left", justify="right", + wraplength=500, + text=self.lbl_language_text[self.langIndex], + ) + self.lbl_language.grid(row=0, column=2) + + self.opt_language = tk.OptionMenu(self.root, + self.option_var, + self.languages[0], + *self.languages, + command=self.language_changed + ) + self.opt_language.grid(row=0, column=3) + self.lbl_Step1 = tk.Label(self.root, + justify="left", + compound="left", + wraplength=300, + text=self.lbl_Step1_text[self.langIndex]) + self.lbl_Step1.grid(row=1, column=0, padx=5, pady=5) + + self.lbl_EmptyValue = tk.Label(self.root, + justify="left", + compound="left", + wraplength=300, + text=self.lbl_EmptyValue_text[self.langIndex]) + self.lbl_EmptyValue.grid(row=1, column=1, padx=5, pady=5) + + self.txt_EmptyValue = tk.Text(self.root, + font=("Noto Mono", 14), + height=1, + width=3, + relief=tk.RAISED + ) + self.txt_EmptyValue.insert('1.0', str(self.EmptyValue)) + self.txt_EmptyValue.grid(row=1, column=3, padx=1, pady=5, ) + + self.btn_capture_empty = tk.Button(self.root, text=self.btn_capture_text[self.langIndex], + command=self.capture_empty) + self.btn_capture_empty.grid(row=1, column=2, padx=1, pady=5) + + self.lbl_Step2 = tk.Label(self.root, + justify="left", + compound="left", + wraplength=300, + text=self.lbl_Step2_text[self.langIndex]) + self.lbl_Step2.grid(row=2, column=0, padx=5, pady=5) + + self.lbl_FullValue = tk.Label(self.root, + justify="left", + compound="left", + wraplength=300, + text=self.lbl_FullValue_text[self.langIndex]) + self.lbl_FullValue.grid(row=2, column=1, padx=5, pady=5) + + self.txt_FullValue = tk.Text(self.root, + font=("Noto Mono", 14), + height=1, + width=3, + relief=tk.RAISED) + self.txt_FullValue.insert('1.0', str(self.FullValue)) + self.txt_FullValue.grid(row=2, column=3, padx=1, pady=5) + + self.btn_capture_full = tk.Button(self.root, text=self.btn_capture_text[self.langIndex], + command=self.capture_full) + self.btn_capture_full.grid(row=2, column=2, padx=1, pady=5) + + self.lbl_activate_sensor = tk.Label(self.root, text=self.lbl_activate_sensor_text[self.langIndex], + justify=tk.LEFT, + compound=tk.LEFT, + wraplength=300) + self.lbl_activate_sensor.grid(row=3, column=0, pady=5) + + if self.sensor_status: + self.chk_var.set(True) + # self.chk_activate_Sensor.config(state=tk.ACTIVE) + + else: + self.chk_var.set(False) + # self.chk_activate_Sensor.config(state=tk.DISABLED) + + self.chk_activate_Sensor = tk.Checkbutton(self.root, + text=self.chk_activate_sensor_text[self.langIndex], + onvalue=True, + offvalue=False, + command=self.activate_changed, + variable=self.chk_var) + + + self.chk_activate_Sensor.grid(row=3, column=1, pady=5) + + self.btn_saveValues = tk.Button(self.root, text=self.btn_saveValues_text[self.langIndex], + command=self.save_values) + self.btn_saveValues.grid(row=4, column=3) + + def capture_empty(self): + self.EmptyValue = read_silolevel() + self.txt_EmptyValue.delete('1.0', tk.END) + self.txt_EmptyValue.insert(tk.END, self.EmptyValue) + + def capture_full(self): + self.FullValue = read_silolevel() + self.txt_FullValue.delete('1.0', tk.END) + self.txt_FullValue.insert(tk.END, self.FullValue) + + def language_changed(self, choice=None): + choice = self.option_var.get() + if choice == "Deutsch": + self.langIndex = 0 + elif choice == "English": + self.langIndex = 1 + self.lbl_titel.config(text=self.lbl_titel_text[self.langIndex]) + self.lbl_language.config(text=self.lbl_language_text[self.langIndex]) + self.lbl_Step1.config(text=self.lbl_Step1_text[self.langIndex]) + self.lbl_EmptyValue.config(text=self.lbl_EmptyValue_text[self.langIndex]) + self.lbl_Step2.config(text=self.lbl_Step2_text[self.langIndex]) + self.lbl_FullValue.config(text=self.lbl_FullValue_text[self.langIndex]) + self.btn_saveValues.config(text=self.btn_saveValues_text[self.langIndex]) + self.btn_capture_full.config(text=self.btn_capture_text[self.langIndex]) + self.btn_capture_empty.config(text=self.btn_capture_text[self.langIndex]) + self.chk_activate_Sensor.config(text=self.chk_activate_sensor_text[self.langIndex]) + self.lbl_activate_sensor.config(text=self.lbl_activate_sensor_text[self.langIndex]) + root.update() + + def activate_changed(self): + choice = self.chk_var.get() + if choice: + self.sensor_status = True + else: + self.sensor_status = False + + def save_values(self): + print("EmptyValue:", repr(self.EmptyValue)) + print("FullValue:", repr(self.FullValue)) + print("Sensor Status:", repr(self.sensor_status)) + e_value: int = self.EmptyValue + f_value: int = self.FullValue + if e_value > f_value: + yaml = ruamel.yaml.YAML() + with open("config.yaml", 'r') as stream: + config_data = yaml.load(stream) + config_data['hardware']['vl53l0x']['empty_value'] = self.EmptyValue + config_data['hardware']['vl53l0x']['full_value'] = self.FullValue + config_data['environment_values']['silolevel'] = self.sensor_status + # write new values back to configuration file. + with open("config.yaml", 'w') as stream: + yaml.dump(config_data, stream) + if self.langIndex == 0: + messagebox.showinfo(title="Speichern erfolgreich", message="Werte in config.yaml gespeichert") + else: + messagebox.showinfo(title="Saving successful", message="Values save into config.yaml") + else: + if self.langIndex == 0: + messagebox.showerror(title="Falsche Werte", + message="Der Wert für das leere Silo muss größer Sein als für " + "das volle Silo.\nBitte die Werte erneut ermitteln!") + else: + messagebox.showerror(title="Wrong Values", + message="The value for the empty silo must be greater than for " + "the full silo.\nPlease determine the values again!") + + +if __name__ == "__main__": + root = tk.Tk() + root.geometry('800x300+50+50') + root.resizable(width=tk.FALSE, height=tk.FALSE) + root.columnconfigure(4, weight=1) + root.rowconfigure(5, weight=1) + config_yaml = read_configuration() + GPIO1_PIN = config_yaml['hardware']['vl53l0x']['GPIO1_PIN'] + XSHUT_PIN = config_yaml['hardware']['vl53l0x']['XSHUT_PIN'] + i2cAddress = config_yaml['hardware']['vl53l0x']['i2cAddress'] + app = FutterSiloCheckerApp(root, empty_value=config_yaml['hardware']['vl53l0x']['empty_value'], + full_value=config_yaml['hardware']['vl53l0x']['full_value'], + sensor_status=config_yaml['environment_values']['silolevel']) + root.mainloop()