diff --git a/.arduino-ci.yml b/.arduino-ci.yml
new file mode 100644
index 0000000..7a55fb2
--- /dev/null
+++ b/.arduino-ci.yml
@@ -0,0 +1,32 @@
+platforms:
+ rpipico:
+ board: rp2040:rp2040:rpipico
+ package: rp2040:rp2040
+ gcc:
+ features:
+ defines:
+ - ARDUINO_ARCH_RP2040
+ warnings:
+ flags:
+
+packages:
+ rp2040:rp2040:
+ url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
+
+compile:
+ # Choosing to run compilation tests on 2 different Arduino platforms
+ platforms:
+ # - uno
+ # - due
+ # - zero
+ # - leonardo
+ # - m4
+ - esp32
+ # - esp8266
+ # - mega2560
+ # - rpipico
+ libraries:
+ - U8g2
+ - HX711
+ - ESP32Servo
+
diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml
new file mode 100644
index 0000000..7f8f4ef
--- /dev/null
+++ b/.github/workflows/arduino-lint.yml
@@ -0,0 +1,13 @@
+name: Arduino-lint
+
+on: [push, pull_request]
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - uses: arduino/arduino-lint-action@v1
+ with:
+ library-manager: update
+ compliance: strict
\ No newline at end of file
diff --git a/.github/workflows/arduino_test_runner.yml b/.github/workflows/arduino_test_runner.yml
new file mode 100644
index 0000000..dbd0ce7
--- /dev/null
+++ b/.github/workflows/arduino_test_runner.yml
@@ -0,0 +1,17 @@
+name: Arduino CI
+
+on: [push, pull_request]
+
+jobs:
+ runTest:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 2.6
+ - run: |
+ gem install arduino_ci
+ arduino_ci.rb
diff --git a/.github/workflows/jsoncheck.yml b/.github/workflows/jsoncheck.yml
new file mode 100644
index 0000000..1cbb5e2
--- /dev/null
+++ b/.github/workflows/jsoncheck.yml
@@ -0,0 +1,18 @@
+name: JSON check
+
+on:
+ push:
+ paths:
+ - '**.json'
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - name: json-syntax-check
+ uses: limitusus/json-syntax-check@v2
+ with:
+ pattern: "\\.json$"
\ No newline at end of file
diff --git a/README.md b/README.md
index 385afef..ba5621c 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@
![Documentation](https://readthedocs.org/projects/hanimandl/badge/)
![Version](https://img.shields.io/github/v/tag/hiveeyes/hanimandl.svg)
+[![Arduino CI](https://github.com/hiveeyes/hanimandl/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)
+
[de] Ein halbautomatischer Honig-Abfüll-Roboter.
[en] A semi-automatic honey filling robot.
diff --git a/changelog.md b/changelog.md
index 38ac91a..6772c16 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,5 @@
# Change Log
+
## 2020-08-13, (v0.2.4 bis) v0.2.6
Display:
diff --git a/docs/handbook-de.md b/docs/handbook-de.md
index 5a2752a..673fe23 100644
--- a/docs/handbook-de.md
+++ b/docs/handbook-de.md
@@ -195,6 +195,7 @@ Die weiteren defines und Variablen müssen bei einer Standard-Schaltung nicht an
### Bauen
Just type:
+
```
make
```
@@ -222,11 +223,15 @@ Board Heltec ESP32 Arduino > Wifi Kit 32 kompiliert:
```cpp
#define HARDWARE_LEVEL 2 // 1 = originales Layout mit Schalter auf Pin 19/22/21
// 2 = Layout für V2 mit Schalter auf Pin 23/19/22
+
#define SERVO_ERWEITERT // definieren, falls die Hardware mit dem alten Programmcode mit Poti aufgebaut wurde oder der Servo zu wenig fährt
// Sonst bleibt der Servo in Stop-Position einige Grad offen! Nach dem Update erst prüfen!
+
#define ROTARY_SCALE 2 // in welchen Schritten springt unser Rotary Encoder.
// Beispiele: KY-040 = 2, HW-040 = 1, für Poti-Betrieb auf 1 setzen
+
#define USE_ROTARY // Rotary benutzen
+
#define USE_ROTARY_SW // Taster des Rotary benutzen
```
diff --git a/docs/handbook-en.md b/docs/handbook-en.md
index 842c46d..7b5f790 100644
--- a/docs/handbook-en.md
+++ b/docs/handbook-en.md
@@ -1,3 +1,274 @@
# Handbook [en]
-Todo. In the meanwhile, see the [](handbook-de.md).
+Todo. verify translation.
+
+
+The HaniMandl manual provides an overview of the basic configuration
+of the HaniMandl firmware and its operation.
+
+
+## Hardware structure
+
+It is recommended that you only connect the servo to the pinch valve
+after you have switched on the electronics for the first time.
+The servo automatically moves to the zero position.
+
+The rods can then be connected and the servo positions can be finely
+adjusted using the servo setup.
+
+
+### Rotary control variants
+
+Either a potentiometer or a rotary encoder is used as a rotary control.
+When using a rotary encoder with a button, the button functions as a selection button.
+
+If a potentiometer or rotary encoder without a button is used, the green button
+in the setup is used as a selection. The "tare" function in manual mode and the
+direct adjustment of the correction value and the filling quantity in automatic
+mode are only available with a rotary encoder with an integrated button.
+
+
+## Setup
+
+In switch position II (or I depending on how the 3-way switch was installed)
+various basic settings can be made:
+
+1. **Tare**
+
+An empty weight ("tare") can be defined for each filling quantity or the
+corresponding glass. The stored tare values are displayed in the menu.
+
+Setting: Select the filling quantity, place the empty glass and save
+using the selection button.
+
+2. **Calibrate**
+
+Menu-guided calibration of the load cell.
+
+3. **Correction**
+
+Setting the correction value. Depending on the temperature and consistency
+of the honey or the filling quantity in the filling container, the set value
+is exceeded due to the inertia of the system. The correction value is used
+to adjust this or to add a few grams more in order to meet the guidelines.
+
+Setting: Set the value using the rotary control and confirm with selection.
+
+4. **Fill quantity**
+
+Selection of the filling quantity
+
+5. **Automatic**
+
+Setting the two automatic functions "Auto-start" and "Auto-correction" as well
+as the (margin) value for the auto-correction.
+
+- Auto-start starts the filling process when a suitable, empty glass is placed on top.
+
+- Auto-correction determines an automatic correction value in order to fill
+the glasses up to the filling quantity (+ margin) when the pressure in the container drops.
+
+6. **Servo angle**
+
+Definition of the minimum and maximum opening angle of the servo as well as
+the angle for the fine dosing.
+
+- The minimum opening angle should close the pinch valve with minimal margin.
+
+- The maximum opening angle limits the stroke of the servo in order to adapt
+the servo to the mechanics.
+
+The servo remains open at least up to the fine dosing angle until the target
+weight (fill quantity + correction) is reached and then closes completely.
+Should be adjusted depending on the consistency. A larger value here leads
+to faster filling, a smaller value to more precise quantities.
+
+Setting: In manual mode, slowly move the servo to the desired opening.
+Then set the value under "W=" here. The angles can also be set directly
+from the setup via the "Live setup" option.
+Be careful when using a potentiometer or when the filling container is full.
+
+7. **Clear Preferences**
+
+Resets all presets (after confirmation).
+
+The scale calibration must then be repeated and all values need to be set again.
+
+
+## Operation
+
+### Manual operation
+
+In manual operation, the opening angle of the servo is determined directly using the rotary control.
+The absolute and relative servo angles are displayed in the top line.
+
+The servo is activated using the green button and deactivated using the red button.
+The current status is displayed using the play/pause symbol on the left.
+
+The weight is permanently displayed. The current weight can be set as tare using
+the selection button, i.e. the scale jumps to zero and counts from the
+current weight. It is reset by pressing a button when the scale is empty.
+
+The mode is used for manual filling and for determining the values for the maximum
+and minimum angle of the servo for the setup.
+
+
+### Automatic operation
+
+The automatic mode has two operating modes, which are selected using the "Auto-start" setup.
+The active auto-start is displayed in the top line with "AS".
+The absolute and relative opening angle can also be found there.
+
+The bottom line shows the values for the correction and the filling quantity.
+An active auto-correction can be recognized there because the correction value
+is given as "ak=" instead of "k=".
+
+The automatic mode must be activated using the green button.
+Red stops the automatic and closes the servo.
+The opening angle of the servo can be limited using the rotary control.
+
+
+#### Autostart not active
+
+If the auto-start is not active, the servo only starts when an empty or partially filled
+glass is on the scale.
+This glass is filled up to the set fill quantity, then the program stops.
+
+A new filling must be activated again using the green button (semi-automatic).
+
+**Attention:** Since partially filled glasses are also filled, a different weight of
+the empty glass cannot be taken into account.
+A half-full 125g glass can trigger the filling with 500g.
+
+
+#### Auto-start active
+
+The auto-start mode is also activated using the green button (play/pause symbol).
+
+The filling process then starts automatically as soon as a suitable, empty glass is placed on top.
+When the filled glass is removed and another glass is placed on top, the next
+filling process starts without further confirmation (fully automatic).
+
+The weight of the glass is used as tare for this filling process to compensate for
+fluctuations in the empty glasses.
+
+A partially filled glass is not filled! Pressing the start button again forces the filling process.
+
+If no filling process is active (i.e. the tap is closed), the correction value and
+the filling quantity for adjustment can be selected directly in both modes using
+the selection button. The value to be adjusted flashes.
+
+
+### Auto-correction
+
+The active auto-correction (display "ak=" bottom left) automatically adjusts the
+filling quantity as the pressure in the filling container drops.
+The target weight is set in the automatic setup via the margin (fill quantity + margin).
+
+## Firmware
+
+### Settings in the Arduino code
+
+The behaviour of the code is controlled by several `#define` variables.
+
+```cpp
+#define HARDWARE_LEVEL 2
+ 1 for original pinout layout with switch on pin 19/22/21
+ 2 for new layout for "New Heltec Wifi Kit 32" (V2) with switch on pin 23/19/22
+
+#define SERVO_EXTENDED
+ activate if a machine is updated with software 0.1.4 and the servo does not close completely
+ or the servo is generally not moved far enough.
+ The new code uses default values for controlling the servo in order to address more servos.
+ The 0.1.4 code had special values where the 0 position was lower.
+ The values of the 0.1.4 code can be activated using this define.
+ Alternative: adjust hardware
+
+#define ROTARY_SCALE 2
+ Different rotary encoders provide different increments per level.
+ This can be adjusted here.
+ Examples: KY-040 = 2, HW-040 = 1
+ If no rotary encoder is used, the define should be set to 1
+
+#define USE_ROTARY
+ activate if a rotary encoder is used to control the interface.
+
+#define USE_ROTARY_SW // Use the rotary button
+ activate if the rotary also has a button function.
+ ATTENTION: if a potentiometer (see below) is used, this variable should be deactivated!
+ Exception: a third button is installed. Not supported!
+
+#define USE_POTI // Use the potentiometer -> ATTENTION, normally also deactivate USE_ROTARY_SW!
+ activate if a potentiometer is used instead of a rotary to control the interface
+ It is strongly recommended to use a rotary encoder!
+
+#define ERROR CORRECTION_WAAGE // if weight jumps occur, these can be intercepted here
+ Attention, can slow down the weighing process.
+ Check load cells / HX711 first!
+
+#define QUETSCHHAHN_LINKS
+ Invert servo if the pinch valve is opened from the left.
+ At least one example of such a bucket is known
+```
+
+The other defines and variables do not need to be adjusted for a standard circuit.
+
+
+### Build
+
+Just type:
+
+```
+make
+```
+
+After successfully building it, you will find firmware images at
+
+- `.pio/build/heltec/firmware.bin`
+- `.pio/build/heltec/firmware.elf`
+
+
+### Notes
+- The project was developed on the [Heltec WiFi Kit 32], an ESP32 with an OLED display.
+ However, it should also run on other ESP32 devices.
+- Please use the latest version of the HX711 library from https://github.com/bogde/HX711,
+ the older version does not run on an ESP32.
+
+### Binary files
+
+- [hani-mandl-heltec-v2]
+- [hani-mandl-heltec-v3]
+
+The file for the Heltec V2 was compiled with the following parameters for the
+board Heltec ESP32 Arduino > Wifi Kit 32:
+
+```cpp
+#define HARDWARE_LEVEL 2 // 1 = original layout with switch on pin 19/22/21
+ // 2 = layout for V2 with switch on pin 23/19/22
+
+#define SERVO_EXTENDED // define if the hardware was built with the old program
+ // code with potentiometer or the servo does not move enough
+ // Otherwise the servo will remain open a few degrees in the stop position!
+ // Check after the update!
+
+#define ROTARY_SCALE 2 // in which steps does our rotary encoder jump.
+ // Examples: KY-040 = 2, HW-040 = 1, set to 1 for potentiometer operation
+
+#define USE_ROTARY // Use rotary
+
+#define USE_ROTARY_SW // Use rotary button
+```
+
+```{tip}
+Instructions for flashing the binary file can be found here:
+
+- [Install HaniMandl 0.2.11]
+- [Install HaniMandl 0.2.13]
+```
+
+
+[Heltec WiFi Kit 32]: https://community.hiveeyes.org/t/heltec-wifi-kit-32-esp32-mit-kleinem-oled/1498
+[hani-mandl-heltec-v2]: https://github.com/hiveeyes/hanimandl/files/14157991/hani-mandl.bin.zip
+[hani-mandl-heltec-v3]: https://github.com/ClemensGruber/hani-mandl/releases/download/v0.2.13/hani-mandl_v0.2.13_heltec-v3-combined.bin
+[HaniMandl 0.2.11 aufspielen]: https://hanimandl.de/2020/12/23/firmware-binary-flashen/
+[HaniMandl 0.2.13 aufspielen]: https://community.hiveeyes.org/t/wie-bekomme-ich-am-einfachsten-die-hanimandl-software-aufs-board-oder-binary-datei-mit-espressif-tool-flashen/3825
diff --git a/examples/hanimandl/hanimandl.ino b/examples/hanimandl/hanimandl.ino
new file mode 100644
index 0000000..fb5e7fe
--- /dev/null
+++ b/examples/hanimandl/hanimandl.ino
@@ -0,0 +1,2374 @@
+/*
+ HaniMandl Version 0.2.13
+ ------------------------
+ Copyright (C) 2018-2023 by Marc Vasterling, Marc Wetzel, Clemens Gruber, Marc Junker, Andreas Holzhammer, Johannes Kuder, Jeremias Bruker
+
+ 2018-05 Marc Vasterling | initiale Version,
+ veröffentlicht in der Facebook-Gruppe "Imkerei und Technik. Eigenbau",
+ Marc Vasterling: "meinen Code kann jeder frei verwenden, ändern und hochladen wo er will, solange er nicht seinen eigenen Namen drüber setzt."
+ 2018-06 Marc Vasterling | verbesserte Version,
+ ebenfalls veröffentlicht in der Facebook-Gruppe
+ 2019-01 Marc Wetzel | Refakturierung und Dokumentation,
+ ebenfalls veröffentlicht in der Facebook-Gruppe
+ 2019-02 Clemens Gruber | code beautifying mit kleineren Umbenennungen bei Funktionen und Variablen
+ Anpassung für Heltec WiFi Kit 32 (ESP32 onboard OLED)
+ - pins bei OLED-Initialisierung geändert
+ - pins geändert, um Konflikte mit hard wired pins des OLEDs zu vermeiden
+ 2019-02 Clemens Gruber | Aktivierung der internen pull downs für alle digitalen Eingaenge
+ 2019-02 Clemens Gruber | "normale" pins zu Vcc / GND geaendert um die Verkabelung etwas einfacher und angenehmer zu machen
+ 2020-05 Andreas Holzhammer | Anpassungen an das veränderte ;-( pin-Layout der Version 2 des Heltec
+ wird verkauft als "New Wifi Kit 32" oder "Wifi Kit 32 V2"
+ 2020-05 Marc Junker | - Erweiterung von Poti auf Rotary Encoder
+ - alle Serial.prints in #ifdef eingeschlossen
+ - "Glas" nun als Array mit individuellem Tara
+ - Korrekturwert und Auswahl der Füllmenge über Drücken & Drehen des Rotary einstellbar
+ 2020-05 Andreas Holzhammer | - Tara pro abzufüllendem Glas automatisch anpassen (Variable tara_glas)
+ - Code läuft auch ohne Waage
+ 2020-06 Andreas Holzhammer | - Code wahlweise mit Heltec V1 oder V2 nutzbar
+ - Code wahlweise mit Poti oder Rotary nutzbar
+ - Tara pro Glas einstellbar
+ - Öffnungswinkel für Maximale Öffnung und Feindosierung im Setup konfigurierbar
+ - Korrektur und Glasgröße im Automatikmodus per Rotary Encoder Button wählbar
+ - Preferences löschbar über Setup
+ - Gewicht blinkt bei Vollautomatik, wenn nicht vollständig gefüllt
+ - Nicht kalibrierte Waage anzeigen, fehlende Waage anzeigen
+ - Tara wird nur bei >20g gesetzt, verhindert den Autostart bei leerer Waage
+ - Tarieren der Waage bei jedem Start bis +-20g. Sonst Warnung
+ 2020-07 Andreas Holzhammer | Version 0.2.4
+ - SCALE_READS auf 2 setzen? ca. 100ms schneller als 3, schwankt aber um +-1g
+ - Reihenfolge der Boot-Meldungen optimiert, damit nur relevante Warnungen ausgegeben werden
+ - Autokorrektur implementiert
+ - LOGO! und Umlaute (Anregung von Johannes Kuder)
+ - Stop-Taste verlässt Setup-Untermenüs (Anregung von Johannes Kuder)
+ - Preferences nur bei Änderung speichern
+ 2020-07 Andreas Holzhammer | Version 0.2.5
+ - Anzeige der vorherigen Werte im Setup
+ - Kulanzwert für Autokorrektur einstellbar
+ - Setup aufgeräumt, minimaler Servowinkel einstellbar
+ 2020-07 Andreas Holzhammer | Version 0.2.6
+ - Kalibrierung der Waage verbessert; Messewerte runden; Waage "aufheizen" vor Bootscreen
+ - Aktiver Piezo-Buzzer (Idee von Johannes Kuder)
+ 2020-07 Johannes Kuder | Version 0.2.7
+ - Zählwerk für abgefüllte Gläser und Gewicht (nur im Automatikbetrieb)
+ 2020-07 Jeremias Bruker | Version 0.2.8
+ - "GlasTyp" in allen Menüs und Automatikmodus integriert
+ - 5 Gläser können vom User im Menüpunkt "Fuellmenge" in Gewicht und GlasTyp konfiguriert werden
+ und werden nichtflüchtig gespeichert. So kann sich jeder User seine eigenen üblichen 5 Gläser anlegen
+ - Stabilisierung des Waagenwerts nach Wunsch (define FEHLERKORREKTUR_WAAGE)
+ - das Kalibriergewicht kann beim Kalibrierungsvorgang vom User verändert
+ werden (nicht jeder hat 500g als Eichgewicht) und wird nichtflüchtig gespeichert
+ - rotierendes Hauptmenü
+ - Umkehrbarer Servo für linksseitige Quetschhähne :-)
+ 2020-10 Andreas Holzhammer | Version 0.2.8.1
+ - Bugfix: Servo konnte im Manuellen Modus unter Minimum bewegt werden
+ - Glastoleranz über Variable steuerbar auf +-20g angepasst
+ 2020-12 Andreas Holzhammer | Version 0.2.9.1
+ - Fortschrittsanzeige eingebaut
+ - angepasst an ESP32Servo aus dem Bibliotheksverwalter
+ 2021-01 Andreas Motl | Version 0.2.9.1
+ - PlatformIO-Support an neue Servo-Bibliothek angepasst
+ 2021-02 Andreas Holzhammer | Version 0.2.10
+ - Korrektur zwischen -90 und +20 anpassbar
+ - Autokorrektur auch ohne Autostart
+ - Preferences Flash-schonender implementiert
+ 2021-07 Andreas Holzhammer | Version 0.2.11
+ - Credits-Seite
+ - Fix für Rotary mit Schrittweite > 1
+ 2021-11 Andreas Holzhammer | Version 0.2.12
+ - Glastoleranz einstellbar
+ - Komfortverstellung für Füllmengen (1g/5g/25g Schritte)
+ 2023-01 Clemens Gruber | Version 0.2.13
+ - pin-Anpassungen für Hardware-Version V3 des Heltec "WiFi Kit 32 V3" mit wieder mal geändertem pin-Layout
+ - default HARDWARE_LEVEL ist nun 3 / Heltec V3
+ - Anpassungen für den ESP32 Arduino core Version ≥ 2.x
+ - Display, U8g2: HW statt SW im constructor (ggf. Probleme mit älteren Heltec-Versionen)
+ - Rotary: de-bouncing code im isr2 auskommentiert, da sie zu Abstürzen führte
+
+
+ This code is in the public domain.
+
+ Hinweise zur Hardware
+ ---------------------
+ - bei allen digitalen Eingängen sind interne pull downs aktiviert, keine externen-Widerstände nötig!
+*/
+
+#include
+#include
+#include /* aus dem Bibliotheksverwalter */
+#include /* aus dem Bibliotheksverwalter: "HX711 Arduino Library" by Bogdan Necula, Andreas Motl */
+#include /* aus dem Bibliotheksverwalter */
+#include /* aus dem BSP von expressif, wird verfügbar wenn das richtige Board ausgewählt ist */
+
+//
+// Hier den Code auf die verwendete Hardware einstellen
+//
+#define HARDWARE_LEVEL 3 // 1 = originales Layout mit Schalter auf Pin 19/22/21
+ // 2 = Layout für Heltec V2 mit Schalter auf Pin 23/19/22
+ // 3 = Layout für Heltec V3 mit komplett anderer Pinbelegung
+#define SERVO_ERWEITERT // definieren, falls die Hardware mit dem alten Programmcode mit Poti aufgebaut wurde oder der Servo zu wenig fährt
+ // Sonst bleibt der Servo in Stop-Position einige Grad offen! Nach dem Update erst prüfen!
+#define ROTARY_SCALE 2 // in welchen Schritten springt unser Rotary Encoder.
+ // Beispiele: KY-040 = 2, HW-040 = 1, für Poti-Betrieb auf 1 setzen
+#define USE_ROTARY // Rotary benutzen
+#define USE_ROTARY_SW // Taster des Rotary benutzen
+//#define USE_POTI // Poti benutzen -> ACHTUNG, im Normalfall auch USE_ROTARY_SW deaktivieren!
+//#define FEHLERKORREKTUR_WAAGE // falls Gewichtssprünge auftreten, können diese hier abgefangen werden
+ // Achtung, kann den Wägeprozess verlangsamen. Vorher Hardware prüfen.
+//#define QUETSCHHAHN_LINKS // Servo invertieren, falls der Quetschhahn von links geöffnet wird. Mindestens ein Exemplar bekannt
+//
+// Ende Benutzereinstellungen!
+//
+
+//
+// Ab hier nur verstellen wenn Du genau weisst, was Du tust!
+//
+//#define isDebug 4 // serielle debug-Ausgabe aktivieren. Mit > 3 wird jeder Messdurchlauf ausgegeben
+ // mit 4 zusätzlich u.a. Durchlaufzeiten
+ // mit 5 zusätzlich rotary debug-Infos
+ // ACHTUNG: zu viel Serieller Output kann einen ISR-Watchdog Reset auslösen!
+//#define POTISCALE // Poti simuliert eine Wägezelle, nur für Testbetrieb!
+#define MAXIMALGEWICHT 1000 // Maximales Gewicht
+
+// Ansteuerung der Waage
+#define SCALE_READS 2 // Parameter für hx711 Library. Messwert wird aus der Anzahl gemittelt
+#define SCALE_GETUNITS(n) (waage_vorhanden ? round(scale.get_units(n)) : simulate_scale(n) )
+
+// Ansteuerung Servo
+#ifdef QUETSCHHAHN_LINKS
+#define SERVO_WRITE(n) servo.write(180-n)
+#else
+#define SERVO_WRITE(n) servo.write(n)
+#endif
+
+// Rotary Encoder Taster zieht Pegel auf Low, Start/Stop auf High!
+#ifdef USE_ROTARY_SW
+#define SELECT_SW outputSW
+#define SELECT_PEGEL LOW
+#else
+#define SELECT_SW button_start_pin
+#define SELECT_PEGEL HIGH
+#endif
+
+// Betriebsmodi
+#define MODE_SETUP 0
+#define MODE_AUTOMATIK 1
+#define MODE_HANDBETRIEB 2
+
+// Buzzer Sounds
+#define BUZZER_SHORT 1
+#define BUZZER_LONG 2
+#define BUZZER_SUCCESS 3
+#define BUZZER_ERROR 4
+
+// ** Definition der pins
+// ----------------------
+
+// Heltec Version 3
+//
+#if HARDWARE_LEVEL == 3
+// OLED fuer Heltec WiFi Kit 32 (ESP32 onboard OLED)
+//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
+// für den ESP32 Arduino core Version ≥ 2.x brauchen wir HW I2C, mit SW I2C läuft der code zu langsam
+U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 21, /* clock=*/ 18, /* data=*/ 17);
+
+// Rotary Encoder
+const int outputA = 47; // Clk
+const int outputB = 48; // DT
+const int outputSW = 26;
+
+// Servo
+const int servo_pin = 33;
+
+// 3x Schalter Ein 1 - Aus - Ein 2
+const int switch_betrieb_pin = 40;
+const int switch_vcc_pin = 41; // <- Vcc
+const int switch_setup_pin = 42;
+const int vext_ctrl_pin = 36; // Vext control pin
+
+// Taster
+const int button_start_vcc_pin = 7; // <- Vcc
+const int button_start_pin = 6;
+const int button_stop_vcc_pin = 5; // <- Vcc
+const int button_stop_pin = 4;
+
+// Poti
+//const int poti_pin = 39;
+
+// Wägezelle-IC
+const int hx711_sck_pin = 38;
+const int hx711_dt_pin = 39;
+
+// Buzzer - aktiver Piezo
+static int buzzer_pin = 2;
+
+
+#elif HARDWARE_LEVEL == 2
+//
+// Heltec Version 2
+
+// OLED fuer Heltec WiFi Kit 32 (ESP32 onboard OLED)
+//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
+// für den ESP32 Arduino core Version ≥ 2.x brauchen wir HW I2C, mit SW I2C läuft der code zu langsam
+U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 16, /* clock=*/ 15, /* data=*/ 4);
+
+// Rotary Encoder
+const int outputA = 33;
+const int outputB = 26;
+const int outputSW = 32;
+
+// Servo
+const int servo_pin = 2;
+
+// 3x Schalter Ein 1 - Aus - Ein 2
+const int switch_betrieb_pin = 23;
+const int switch_vcc_pin = 19; // <- Vcc
+const int switch_setup_pin = 22;
+const int vext_ctrl_pin = 21; // Vext control pin
+
+// Taster
+const int button_start_vcc_pin = 13; // <- Vcc
+const int button_start_pin = 12;
+const int button_stop_vcc_pin = 14; // <- Vcc
+const int button_stop_pin = 27;
+
+// Poti
+const int poti_pin = 39;
+
+// Wägezelle-IC
+const int hx711_sck_pin = 17;
+const int hx711_dt_pin = 5;
+
+// Buzzer - aktiver Piezo
+static int buzzer_pin = 25;
+
+
+#elif HARDWARE_LEVEL == 1
+//
+// Heltec Version 1
+
+// OLED fuer Heltec WiFi Kit 32 (ESP32 onboard OLED)
+U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
+//U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 16, /* clock=*/ 15, /* data=*/ 4); // HW I2C crashed den Code
+
+// Rotary Encoder
+const int outputA = 33;
+const int outputB = 26;
+const int outputSW = 32;
+
+// Servo
+const int servo_pin = 2;
+
+// 3x Schalter Ein 1 - Aus - Ein 2
+const int switch_betrieb_pin = 19;
+const int switch_vcc_pin = 22; // <- Vcc
+const int switch_setup_pin = 21;
+
+// Taster
+const int button_start_vcc_pin = 13; // <- Vcc
+const int button_start_pin = 12;
+const int button_stop_vcc_pin = 14; // <- Vcc
+const int button_stop_pin = 27;
+
+// Poti
+const int poti_pin = 39;
+
+// Wägezelle-IC
+const int hx711_sck_pin = 17;
+const int hx711_dt_pin = 5;
+
+// Buzzer - aktiver Piezo
+static int buzzer_pin = 25;
+#else
+#error Hardware Level nicht definiert! Korrektes #define setzen!
+#endif
+
+
+Servo servo;
+HX711 scale;
+Preferences preferences;
+
+// Datenstrukturen für Rotary Encoder
+struct rotary {
+ int Value;
+ int Minimum;
+ int Maximum;
+ int Step;
+};
+#define SW_WINKEL 0
+#define SW_KORREKTUR 1
+#define SW_MENU 2
+struct rotary rotaries[3]; // Werden in setup() initialisiert
+int rotary_select = SW_WINKEL;
+static boolean rotating = false; // debounce management für Rotary Encoder
+
+// Füllmengen für 5 verschiedene Gläser
+struct glas {
+ int Gewicht;
+ int GlasTyp; //JB
+ int Tara;
+ int TripCount; //Kud
+ int Count; //Kud
+};
+const char *GlasTypArray[3] = { "DIB", "TOF", "DEE"};//DIB = DeutscherImkerBund-Glas DEE= DeepTwist-Glas TOF= TwistOff-Glas //JB
+struct glas glaeser[5] = {
+ { 125, 0, -9999, 0, 0 },
+ { 250, 1, -9999, 0, 0 },
+ { 250, 2, -9999, 0, 0 },
+ { 500, 1, -9999, 0, 0 },
+ { 500, 0, -9999, 0, 0 }
+ };
+
+// Allgemeine Variablen
+int i; // allgemeine Zählvariable
+int pos; // aktuelle Position des Poti bzw. Rotary
+int gewicht; // aktuelles Gewicht
+int tara; // Tara für das ausgewählte Glas, für Automatikmodus
+int tara_glas; // Tara für das aktuelle Glas, falls Glasgewicht abweicht
+long gewicht_leer; // Gewicht der leeren Waage
+float faktor; // Skalierungsfaktor für Werte der Waage
+int fmenge; // ausgewählte Füllmenge
+int fmenge_index; // Index in gläser[]
+int korrektur; // Korrekturwert für Abfüllmenge
+int autostart; // Vollautomatik ein/aus
+int autokorrektur; // Autokorrektur ein/aus
+int kulanz_gr; // gewollte Überfüllung im Autokorrekturmodus in Gramm
+int winkel; // aktueller Servo-Winkel
+int winkel_hard_min = 0; // Hard-Limit für Servo
+int winkel_hard_max = 180; // Hard-Limit für Servo
+int winkel_min = 0; // konfigurierbar im Setup
+int winkel_max = 85; // konfigurierbar im Setup
+int winkel_fein = 35; // konfigurierbar im Setup
+float fein_dosier_gewicht = 60; // float wegen Berechnung des Schliesswinkels
+int servo_aktiv = 0; // Servo aktivieren ja/nein
+int kali_gewicht = 500; // frei wählbares Gewicht zum kalibrieren
+char ausgabe[30]; // Fontsize 12 = 13 Zeichen maximal in einer Zeile
+int modus = -1; // Bei Modus-Wechsel den Servo auf Minimum fahren
+int auto_aktiv = 0; // Für Automatikmodus - System ein/aus?
+int waage_vorhanden = 0; // HX711 nicht ansprechen, wenn keine Waage angeschlossen ist, sonst Crash
+long preferences_chksum; // Checksumme, damit wir nicht sinnlos Prefs schreiben
+int buzzermode = 0; // 0 = aus, 1 = ein. TODO: Tastentöne als buzzermode 2?
+bool gezaehlt = true; // Kud Zähl-Flag
+bool setup_modern = 1; // Setup als rotierendes Menu
+int glastoleranz = 20; // Gewicht für autostart darf um +-20g schwanken, insgesamt also 40g!
+
+// Simuliert die Dauer des Wägeprozess, wenn keine Waage angeschlossen ist. Wirkt sich auf die Blinkfrequenz im Automatikmodus aus.
+long simulate_scale(int n) {
+ long sim_gewicht = 9500;
+ while (n-- >= 1) {
+ delay(10); // empirisch ermittelt. n=2: 10, n=3: 40, n=4: 50
+ }
+#ifdef POTISCALE
+ sim_gewicht = (map(analogRead(poti_pin), 0, 4095, 0, 700));
+#endif
+ return sim_gewicht;
+}
+
+#ifdef USE_ROTARY_SW
+// Rotary Taster. Der Interrupt kommt nur im Automatikmodus zum Tragen und nur wenn der Servo inaktiv ist.
+// Der Taster schaltet in einen von drei Modi, in denen unterschiedliche Werte gezählt werden.
+void IRAM_ATTR isr1() {
+ static unsigned long last_interrupt_time = 0;
+ unsigned long interrupt_time = millis();
+
+ if (interrupt_time - last_interrupt_time > 300) { // If interrupts come faster than 300ms, assume it's a bounce and ignore
+ if ( modus == MODE_AUTOMATIK && servo_aktiv == 0 ) { // nur im Automatik-Modus interessiert uns der Click
+ rotary_select = (rotary_select + 1) % 3;
+#ifdef isDebug
+ Serial.print("Rotary Button changed to ");
+ Serial.println(rotary_select);
+#endif
+ }
+ last_interrupt_time = interrupt_time;
+ }
+}
+#endif
+
+#ifdef USE_ROTARY
+// Rotary Encoder. Zählt in eine von drei Datenstrukturen:
+// SW_WINKEL = Einstellung des Servo-Winkels
+// SW_KORREKTUR = Korrekturfaktor für Füllgewicht
+// SW_MENU = Zähler für Menuauswahlen
+void IRAM_ATTR isr2() {
+ static int aState;
+ static int aLastState = 2; // reale Werte sind 0 und 1
+
+// auskommentiert, da vermutlich das delay in der isr-Funktion ein reset ab ESP32 Arduino core Version ≥ 2.x auslöst
+// beobachten, ob damit das bouncing ein Problem wird, in rudimentären Tests konnte ich (cg) nichts negatives beobachten
+// if ( rotating ) delay (1); // wait a little until the bouncing is done
+
+ aState = digitalRead(outputA); // Reads the "current" state of the outputA
+ if (aState != aLastState) {
+ // If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
+ if (digitalRead(outputB) != aState) {
+ rotaries[rotary_select].Value -= rotaries[rotary_select].Step;
+ } else { // counter-clockwise
+ rotaries[rotary_select].Value += rotaries[rotary_select].Step;
+ }
+ rotaries[rotary_select].Value = constrain( rotaries[rotary_select].Value, rotaries[rotary_select].Minimum, rotaries[rotary_select].Maximum );
+ rotating = false;
+#ifdef isDebug
+#if isDebug >= 5
+ Serial.print(" Rotary Value changed to ");
+ Serial.println(getRotariesValue(rotary_select));
+#endif
+#endif
+ }
+ aLastState = aState; // Updates the previous state of the outputA with the current state
+}
+#endif
+
+//
+// Skalierung des Rotaries für verschiedene Rotary Encoder oder Simulation über Poti
+int getRotariesValue( int rotary_mode ) {
+#ifdef USE_ROTARY
+ return ( (rotaries[rotary_mode].Value - (rotaries[rotary_mode].Value % (rotaries[rotary_mode].Step*ROTARY_SCALE) )) / ROTARY_SCALE );
+#elif defined USE_POTI
+ int poti_min = (rotaries[rotary_mode].Minimum / ROTARY_SCALE);
+ int poti_max = (rotaries[rotary_mode].Maximum / ROTARY_SCALE);
+ if( rotaries[rotary_mode].Step > 0 ) {
+ return (map(analogRead(poti_pin), 0, 4095, poti_min, poti_max));
+ } else {
+ return (map(analogRead(poti_pin), 0, 4095, poti_max, poti_min));
+ }
+#else
+#error Weder Rotary noch Poti aktiviert!
+#endif
+}
+void setRotariesValue( int rotary_mode, int rotary_value ) {
+ rotaries[rotary_mode].Value = rotary_value * ROTARY_SCALE;
+}
+void initRotaries( int rotary_mode, int rotary_value, int rotary_min, int rotary_max, int rotary_step ) {
+ rotaries[rotary_mode].Value = rotary_value * ROTARY_SCALE;
+ rotaries[rotary_mode].Minimum = rotary_min * ROTARY_SCALE;
+ rotaries[rotary_mode].Maximum = rotary_max * ROTARY_SCALE;
+ rotaries[rotary_mode].Step = rotary_step;
+
+#ifdef isDebug
+ Serial.print("initRotaries...");
+ Serial.print(" Rotary Mode: "); Serial.print(rotary_mode);
+ Serial.print(" rotary_value: "); Serial.print(rotary_value);
+ Serial.print(" Value: "); Serial.print(rotaries[rotary_mode].Value);
+ Serial.print(" Min: "); Serial.print(rotaries[rotary_mode].Minimum);
+ Serial.print(" Max: "); Serial.print(rotaries[rotary_mode].Maximum);
+ Serial.print(" Step: "); Serial.print(rotaries[rotary_mode].Step);
+ Serial.print(" Scale: "); Serial.println(ROTARY_SCALE);
+#endif
+}
+// Ende Funktionen für den Rotary Encoder
+//
+
+
+void getPreferences(void) {
+ preferences.begin("EEPROM", false); // Parameter aus dem EEPROM lesen
+
+ faktor = preferences.getFloat("faktor", 0.0); // falls das nicht gesetzt ist -> Waage ist nicht kalibriert
+ pos = preferences.getUInt("pos", 0);
+ gewicht_leer = preferences.getUInt("gewicht_leer", 0);
+ korrektur = preferences.getUInt("korrektur", 0);
+ autostart = preferences.getUInt("autostart", 0);
+ autokorrektur = preferences.getUInt("autokorrektur", 0);
+ kulanz_gr = preferences.getUInt("kulanz_gr", 5);
+ fmenge_index = preferences.getUInt("fmenge_index", 3);
+ winkel_min = preferences.getUInt("winkel_min", winkel_min);
+ winkel_max = preferences.getUInt("winkel_max", winkel_max);
+ winkel_fein = preferences.getUInt("winkel_fein", winkel_fein);
+ buzzermode = preferences.getUInt("buzzermode", buzzermode);
+ kali_gewicht = preferences.getUInt("kali_gewicht", kali_gewicht); //JB
+ setup_modern = preferences.getUInt("setup_modern", setup_modern);
+ glastoleranz = preferences.getUInt("glastoleranz", glastoleranz);
+
+ preferences_chksum = faktor + pos + gewicht_leer + korrektur + autostart + autokorrektur + fmenge_index + winkel_min + winkel_max + winkel_fein + kulanz_gr + buzzermode + kali_gewicht + setup_modern + glastoleranz;
+
+ i = 0;
+ int ResetGewichte[] = {125,250,250,500,500,};
+ int ResetGlasTyp[] = {0,1,2,1,0,};
+ while( i < 5) {
+ sprintf(ausgabe, "Gewicht%d", i); //JB
+ glaeser[i].Gewicht = preferences.getInt(ausgabe, ResetGewichte[i]); //JB
+ preferences_chksum += glaeser[i].Gewicht; //JB
+
+ sprintf(ausgabe, "GlasTyp%d", i); //JB
+ glaeser[i].GlasTyp = preferences.getInt(ausgabe, ResetGlasTyp[i]); //JB
+ preferences_chksum += glaeser[i].GlasTyp; //JB
+
+ sprintf(ausgabe, "Tara%d", i);
+ glaeser[i].Tara= preferences.getInt(ausgabe, -9999);
+ preferences_chksum += glaeser[i].Tara;
+
+ sprintf(ausgabe, "TripCount%d", i); //Kud
+ glaeser[i].TripCount = preferences.getInt(ausgabe, 0);//Kud
+ preferences_chksum += glaeser[i].TripCount;
+
+ sprintf(ausgabe, "Count%d", i); //Kud
+ glaeser[i].Count = preferences.getInt(ausgabe, 0);//Kud
+ preferences_chksum += glaeser[i].Count;
+ i++;
+ }
+
+ preferences.end();
+
+#ifdef isDebug
+ Serial.println("get Preferences:");
+ Serial.print("pos = "); Serial.println(pos);
+ Serial.print("faktor = "); Serial.println(faktor);
+ Serial.print("gewicht_leer = "); Serial.println(gewicht_leer);
+ Serial.print("korrektur = "); Serial.println(korrektur);
+ Serial.print("autostart = "); Serial.println(autostart);
+ Serial.print("autokorrektur = ");Serial.println(autokorrektur);
+ Serial.print("kulanz_gr = "); Serial.println(kulanz_gr);
+ Serial.print("fmenge_index = "); Serial.println(fmenge_index);
+ Serial.print("winkel_min = "); Serial.println(winkel_min);
+ Serial.print("winkel_max = "); Serial.println(winkel_max);
+ Serial.print("winkel_fein = "); Serial.println(winkel_fein);
+ Serial.print("buzzermode = "); Serial.println(buzzermode);
+ Serial.print("kali_gewicht = "); Serial.println(kali_gewicht);//JB
+ Serial.print("setup_modern = "); Serial.println(setup_modern);
+ Serial.print("glastoleranz = "); Serial.println(glastoleranz);
+
+ i = 0;
+ while( i < 5 ) {
+ sprintf(ausgabe, "Gewicht%d = ", i);
+ Serial.print(ausgabe);
+ Serial.println(glaeser[i].Gewicht);
+
+ sprintf(ausgabe, "GlasTyp%d = ", i);
+ Serial.print(ausgabe);
+ Serial.println(GlasTypArray[glaeser[i].GlasTyp]);
+
+ sprintf(ausgabe, "Tara%d = ", i);
+ Serial.print(ausgabe);
+ Serial.println(glaeser[i].Tara);
+
+ i++;
+ }
+ Serial.print("Checksumme:");
+ Serial.println(preferences_chksum);
+#endif
+}
+
+void setPreferences(void) {
+ long preferences_newchksum;
+ int winkel = getRotariesValue(SW_WINKEL);
+ int i;
+
+ preferences.begin("EEPROM", false);
+
+ // Winkel-Einstellung separat behandeln, ändert sich häufig
+ if ( winkel != preferences.getUInt("pos", 0) ) {
+ preferences.putUInt("pos", winkel);
+#ifdef isDebug
+ Serial.print("winkel gespeichert: ");
+ Serial.println(winkel);
+#endif
+ }
+
+ // Counter separat behandeln, ändert sich häufig
+ for ( i=0 ; i < 5; i++ ) {
+ sprintf(ausgabe, "TripCount%d", i);
+ if ( glaeser[i].TripCount != preferences.getInt(ausgabe, 0) )
+ preferences.putInt(ausgabe, glaeser[i].TripCount);
+ sprintf(ausgabe, "Count%d", i);
+ if ( glaeser[i].Count != preferences.getInt(ausgabe, 0) )
+ preferences.putInt(ausgabe, glaeser[i].Count);
+#ifdef isDebug
+ Serial.print("Counter gespeichert: Index ");
+ Serial.print(i);
+ Serial.print(" Trip ");
+ Serial.print(glaeser[fmenge_index].TripCount);
+ Serial.print(" Gesamt ");
+ Serial.println(glaeser[fmenge_index].Count);
+#endif
+ }
+
+ // Den Rest machen wir gesammelt, das ist eher statisch
+ preferences_newchksum = faktor + gewicht_leer + korrektur + autostart + autokorrektur +
+ fmenge_index + winkel_min + winkel_max + winkel_fein + kulanz_gr +
+ buzzermode + kali_gewicht + setup_modern + glastoleranz;
+ i = 0;
+ while( i < 5 ) {
+ preferences_newchksum += glaeser[i].Gewicht;//JB
+ preferences_newchksum += glaeser[i].GlasTyp;//JB
+ preferences_newchksum += glaeser[i].Tara;
+// preferences_newchksum += glaeser[i].TripCount;//Kud
+// preferences_newchksum += glaeser[i].Count;//Kud
+ i++;
+ }
+
+ if( preferences_newchksum == preferences_chksum ) {
+#ifdef isDebug
+// Serial.println("Preferences unverändert");
+#endif
+ return;
+ }
+ preferences_chksum = preferences_newchksum;
+
+// preferences.begin("EEPROM", false);
+ preferences.putFloat("faktor", faktor);
+ preferences.putUInt("gewicht_leer", gewicht_leer);
+// preferences.putUInt("pos", winkel);
+ preferences.putUInt("korrektur", korrektur);
+ preferences.putUInt("autostart", autostart);
+ preferences.putUInt("autokorrektur", autokorrektur);
+ preferences.putUInt("kulanz_gr", kulanz_gr);
+ preferences.putUInt("winkel_min", winkel_min);
+ preferences.putUInt("winkel_max", winkel_max);
+ preferences.putUInt("winkel_fein", winkel_fein);
+ preferences.putUInt("fmenge_index", fmenge_index);
+ preferences.putUInt("buzzermode", buzzermode);
+ preferences.putUInt("kali_gewicht", kali_gewicht);//JB
+ preferences.putUInt("setup_modern", setup_modern);
+ preferences.putUInt("glastoleranz", glastoleranz);
+
+ i = 0;
+ while( i < 5 ) {
+ sprintf(ausgabe, "Gewicht%d", i);
+ preferences.putInt(ausgabe, glaeser[i].Gewicht);
+ sprintf(ausgabe, "GlasTyp%d", i);
+ preferences.putInt(ausgabe, glaeser[i].GlasTyp);
+ sprintf(ausgabe, "Tara%d", i);
+ preferences.putInt(ausgabe, glaeser[i].Tara);
+// sprintf(ausgabe, "TripCount%d", i);
+// preferences.putInt(ausgabe, glaeser[i].TripCount);//Kud
+// sprintf(ausgabe, "Count%d", i);
+// preferences.putInt(ausgabe, glaeser[i].Count);//Kud
+ i++;
+ }
+ preferences.end();
+
+#ifdef isDebug
+ Serial.println("Set Preferences:");
+ Serial.print("pos = "); Serial.println(winkel);
+ Serial.print("faktor = "); Serial.println(faktor);
+ Serial.print("gewicht_leer = "); Serial.println(gewicht_leer);
+ Serial.print("korrektur = "); Serial.println(korrektur);
+ Serial.print("autostart = "); Serial.println(autostart);
+ Serial.print("autokorrektur = ");Serial.println(autokorrektur);
+ Serial.print("kulanz_gr = "); Serial.println(kulanz_gr);
+ Serial.print("fmenge_index = "); Serial.println(fmenge_index);
+ Serial.print("winkel_min = "); Serial.println(winkel_min);
+ Serial.print("winkel_max = "); Serial.println(winkel_max);
+ Serial.print("winkel_fein = "); Serial.println(winkel_fein);
+ Serial.print("buzzermode = "); Serial.println(buzzermode);
+ Serial.print("kali_gewicht = "); Serial.println(kali_gewicht); //JB
+ Serial.print("setup_modern = "); Serial.println(setup_modern);
+ Serial.print("glastoleranz = "); Serial.println(glastoleranz);
+
+ i = 0;
+ while( i < 5 ) {
+ sprintf(ausgabe, "Gewicht%d = ", i);
+ Serial.print(ausgabe); Serial.println(glaeser[i].Gewicht);
+ sprintf(ausgabe, "GlasTyp%d = ", i);
+ Serial.print(ausgabe); Serial.println(GlasTypArray[glaeser[i].GlasTyp]);
+ sprintf(ausgabe, "Tara%d = ", i);
+ Serial.print(ausgabe); Serial.println(glaeser[i].Tara);
+ i++;
+ }
+#endif
+}
+
+void setupTripCounter(void) { //Kud
+ int j;
+ i = 1;
+ float TripAbfuellgewicht = 0;
+
+ while (i > 0) { //Erster Screen: Anzahl pro Glasgröße
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ while ( j < 5 ) {
+ u8g2.setCursor(1, 10 + (j * 13));
+ sprintf(ausgabe, "%4dg%3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(50, 10 + (j * 13));
+ sprintf(ausgabe, "%5d St.", glaeser[j].TripCount);
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Zweiter Screen: Gewicht pro Glasgröße
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ while ( j < 5 ) {
+ u8g2.setCursor(1, 10 + (j * 13));
+ sprintf(ausgabe, "%4dg%3s", glaeser[j].Gewicht,GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(65, 10 + (j * 13));
+ // Serial.println(glaeser[j].Gewicht);
+ // Serial.print("\t");
+ // Serial.print(glaeser[j].TripCount);
+ // Serial.print("\t");
+ // Serial.print(glaeser[j].Gewicht * glaeser[j].TripCount / 1000.0);
+ // Serial.println();
+
+ sprintf(ausgabe, "%5.1fkg", glaeser[j].Gewicht * glaeser[j].TripCount / 1000.0);
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Dritter Screen: Gesamtgewicht
+ TripAbfuellgewicht = 0;
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ while ( j < 5) {
+ TripAbfuellgewicht += glaeser[j].Gewicht * glaeser[j].TripCount / 1000.0;
+ j++;
+ }
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB14_tf);
+ u8g2.setCursor(5, 15);
+ u8g2.print("Summe Trip:");
+ u8g2.setFont(u8g2_font_courB18_tf);
+ u8g2.setCursor(10, 50);
+ sprintf(ausgabe, "%5.1fkg", TripAbfuellgewicht);
+ u8g2.print(ausgabe);
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Vierter Screen: Zurücksetzen
+ initRotaries(SW_MENU, 1, 0, 1, -1);
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ pos = getRotariesValue(SW_MENU);
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 12); u8g2.print("Reset");
+ u8g2.setCursor(10, 28); u8g2.print("Abbrechen");
+
+ u8g2.setCursor(0, 12 + ((pos) * 16));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ u8g2.setCursor(105, 12 + ((pos) * 16));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ if ( pos == 0) {
+ j = 0;
+ while ( j < 5 ) {
+ glaeser[j].TripCount = 0;
+ j++;
+ }
+ setPreferences();
+ }
+ delay(1000);
+ i = 0;
+ }
+ }
+ }
+}
+
+void setupCounter(void) { //Kud
+ int j;
+ i = 1;
+ float Abfuellgewicht = 0;
+
+ while (i > 0) { //Erster Screen: Anzahl pro Glasgröße
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ while ( j < 5 ) {
+ u8g2.setCursor(1, 10 + (j * 13));
+ sprintf(ausgabe, "%4dg%3s", glaeser[j].Gewicht,GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(50, 10 + (j * 13));
+ sprintf(ausgabe, "%5d St.", glaeser[j].Count);
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Zweiter Screen: Gewicht pro Glasgröße
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ while ( j < 5) {
+ u8g2.setCursor(1, 10 + (j * 13));
+ sprintf(ausgabe, "%4dg%3s", glaeser[j].Gewicht,GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(65, 10 + (j * 13));
+ // Serial.println(glaeser[j].Gewicht);
+ // Serial.print("\t");
+ // Serial.print(glaeser[j].Count);
+ // Serial.print("\t");
+ // Serial.print(glaeser[j].Gewicht * glaeser[j].Count / 1000.0);
+ // Serial.println();
+
+ sprintf(ausgabe, "%5.1fkg", glaeser[j].Gewicht * glaeser[j].Count / 1000.0);
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Dritter Screen: Gesamtgewicht
+ Abfuellgewicht = 0;
+ j = 0;
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ //verlasse Screen
+ i = 0;
+ delay(250);
+ }
+
+ while ( j < 5 ) {
+ Abfuellgewicht += glaeser[j].Gewicht * glaeser[j].Count / 1000.0;
+ j++;
+ }
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB14_tf);
+ u8g2.setCursor(1, 15);
+ u8g2.print("Summe:");
+ u8g2.setFont(u8g2_font_courB18_tf);
+ u8g2.setCursor(10, 50);
+ sprintf(ausgabe, "%5.1fkg", Abfuellgewicht);
+ u8g2.print(ausgabe);
+ u8g2.sendBuffer();
+ delay(100);
+ }
+
+ i = 1;
+ while (i > 0) { //Vierter Screen: Zurücksetzen
+ initRotaries(SW_MENU, 1, 0, 1, -1);
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ pos = getRotariesValue(SW_MENU);
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 12); u8g2.print("Reset");
+ u8g2.setCursor(10, 28); u8g2.print("Abbrechen");
+
+ u8g2.setCursor(0, 12 + ((pos) * 16));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ u8g2.setCursor(105, 12 + ((pos) * 16));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ if ( pos == 0) {
+ j = 0;
+ while ( j < 5 ) {
+ glaeser[j].Count = 0;
+ glaeser[j].TripCount = 0;
+ j++;
+ }
+ setPreferences();
+
+ }
+ delay(1000);
+ i = 0;
+ }
+ }
+ }
+}
+
+
+void setupTara(void) {
+ int j;
+ tara = 0;
+
+ initRotaries( SW_MENU, fmenge_index, 0, 4, -1 ); // Set Encoder to Menu Mode, four Selections, inverted count
+
+ i = 0;
+ while ( i == 0 ) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) {
+ tara = (int(SCALE_GETUNITS(10)));
+ if ( tara > 20 ) { // Gläser müssen mindestens 20g haben
+ glaeser[getRotariesValue(SW_MENU)].Tara = tara;
+ }
+ i++;
+ }
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+
+ j = 0;
+ while( j < 5 ) {
+ u8g2.setCursor(3, 10+(j*13));
+ if ( glaeser[j].Gewicht < 1000 ) {
+ sprintf(ausgabe, " %3d-%3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ } else {
+ sprintf(ausgabe, " %3s-%3s", "1kg", GlasTypArray[glaeser[j].GlasTyp]);
+ }
+ u8g2.print(ausgabe);
+ u8g2.setCursor(75, 10+(j*13));
+ if ( glaeser[j].Tara > 0 ) {
+ sprintf(ausgabe, " %4dg", glaeser[j].Tara);
+ u8g2.print(ausgabe);
+ } else {
+ u8g2.print(" fehlt");
+ }
+ j++;
+ }
+ u8g2.setCursor(0, 10+(getRotariesValue(SW_MENU)*13) );
+ u8g2.print("*");
+ u8g2.sendBuffer();
+ }
+ delay(2000);
+}
+
+
+void setupCalibration(void) {
+ float gewicht_raw;
+
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB12_tf);
+ u8g2.setCursor(0, 12); u8g2.print("Bitte Waage");
+ u8g2.setCursor(0, 28); u8g2.print("leeren");
+ u8g2.setCursor(0, 44); u8g2.print("und mit OK");
+ u8g2.setCursor(0, 60); u8g2.print("bestätigen");
+ u8g2.sendBuffer();
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ scale.set_scale();
+ scale.tare(10);
+ delay(500);
+ i = 0;
+ }
+ }
+
+ u8g2.setFont(u8g2_font_courB12_tf);
+ initRotaries( SW_MENU, kali_gewicht, 100, 9999, 1 );
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ kali_gewicht = getRotariesValue(SW_MENU);
+
+ int blinktime = (millis()/10) % 5;
+ u8g2.clearBuffer();
+ u8g2.setCursor(0, 12);u8g2.print("Bitte ");
+
+ if (blinktime < 3) {
+ sprintf(ausgabe, "%dg", kali_gewicht);
+ } else {
+ sprintf(ausgabe, " ");
+ }
+ u8g2.print(ausgabe);
+ u8g2.setCursor(0, 28); u8g2.print("aufstellen");
+ u8g2.setCursor(0, 44); u8g2.print("und mit OK");
+ u8g2.setCursor(0, 60); u8g2.print("bestätigen");
+ u8g2.sendBuffer();
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ u8g2.clearBuffer();
+ u8g2.setCursor(0, 12);u8g2.print("Bitte ");
+ sprintf(ausgabe, "%dg", kali_gewicht);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(0, 28); u8g2.print("aufstellen");
+ u8g2.setCursor(0, 44); u8g2.print("und mit OK");
+ u8g2.setCursor(0, 60); u8g2.print("bestätigen");
+ u8g2.sendBuffer();
+ gewicht_raw = scale.get_units(10);
+ faktor = gewicht_raw / kali_gewicht;
+ scale.set_scale(faktor);
+ gewicht_leer = scale.get_offset(); // Leergewicht der Waage speichern
+#ifdef isDebug
+ Serial.print("kalibrier_gewicht = ");
+ Serial.println(kali_gewicht);
+ Serial.print("gewicht_leer = ");
+ Serial.println(gewicht_leer);
+ Serial.print("gewicht_raw = ");
+ Serial.println(gewicht_raw);
+ Serial.print(" Faktor = ");
+ Serial.println(faktor);
+#endif
+ delay(1000);
+ i = 0;
+ }
+ }
+}
+
+void setupKorrektur(void) {
+ int korrektur_alt = korrektur;
+
+ rotary_select = SW_KORREKTUR;
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ setRotariesValue(SW_KORREKTUR, korrektur_alt);
+ korrektur = korrektur_alt;
+ rotary_select = SW_MENU;
+ return;
+ }
+
+ korrektur = getRotariesValue(SW_KORREKTUR);
+ u8g2.setFont(u8g2_font_courB12_tf);
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 12);
+ u8g2.print("Korrektur");
+ u8g2.setCursor(40, 28);
+ u8g2.print(korrektur);
+
+ u8g2.setCursor(10, 48); // A.P.
+ u8g2.print("alter Wert"); // A.P.
+ u8g2.setCursor(40, 64); // A.P.
+ u8g2.print(korrektur_alt); // A.P.
+
+ u8g2.sendBuffer();
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ u8g2.setCursor(100, 28);
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ delay(1000);
+ i = 0;
+ }
+ }
+ rotary_select = SW_MENU;
+}
+
+void setupServoWinkel(void) {
+ int menuitem;
+ int lastmin = winkel_min;
+ int lastfein = winkel_fein;
+ int lastmax = winkel_max;
+ int wert_alt;
+ bool wert_aendern = false;
+ bool servo_live = false;
+
+ initRotaries(SW_MENU, 0, 0, 4, -1);
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ winkel_min = lastmin;
+ winkel_fein = lastfein;
+ winkel_max = lastmax;
+ if ( servo_live == true ) SERVO_WRITE(winkel_min);
+ return;
+ }
+
+ if ( wert_aendern == false ) {
+ menuitem = getRotariesValue(SW_MENU);
+ } else {
+ switch (menuitem) {
+ case 0: servo_live = getRotariesValue(SW_MENU);
+ break;
+ case 1: winkel_min = getRotariesValue(SW_MENU);
+ if ( servo_live == true ) SERVO_WRITE(winkel_min);
+ break;
+ case 2: winkel_fein = getRotariesValue(SW_MENU);
+ if ( servo_live == true ) SERVO_WRITE(winkel_fein);
+ break;
+ case 3: winkel_max = getRotariesValue(SW_MENU);
+ if ( servo_live == true ) SERVO_WRITE(winkel_max);
+ break;
+ }
+ }
+
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 23); sprintf(ausgabe,"Minimum %3d", winkel_min); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 36); sprintf(ausgabe,"Feindos. %3d", winkel_fein); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 49); sprintf(ausgabe,"Maximum %3d", winkel_max); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 62); u8g2.print( "Speichern");
+
+ if ( wert_aendern == false ) {
+ u8g2.setCursor(10, 10); sprintf(ausgabe,"Livesetup %3s", (servo_live==false?"aus":"ein")); u8g2.print(ausgabe);
+ u8g2.setCursor( 0, 10+(menuitem*13)); u8g2.print("*");
+ } else {
+ if ( menuitem != 0 ) {
+ u8g2.setCursor(10, 10); sprintf(ausgabe," vorher: %3d", wert_alt); u8g2.print(ausgabe);
+ } else {
+ u8g2.setCursor(10, 10); sprintf(ausgabe,"Livesetup %3s", (servo_live==false?"aus":"ein")); u8g2.print(ausgabe);
+ }
+ u8g2.setFont(u8g2_font_open_iconic_arrow_1x_t);
+ u8g2.drawGlyph(0, 10+(menuitem*13), 0x42);
+ u8g2.setFont(u8g2_font_courB10_tf);
+ }
+ u8g2.sendBuffer();
+
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 4 )
+ && (wert_aendern == false) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ switch (menuitem) {
+ case 0: initRotaries(SW_MENU, servo_live, 0, 1, 1);
+ break;
+ case 1: initRotaries(SW_MENU, winkel_min, winkel_hard_min, winkel_fein, 1);
+ wert_alt = lastmin;
+ break;
+ case 2: initRotaries(SW_MENU, winkel_fein, winkel_min, winkel_max, 1);
+ wert_alt = lastfein;
+ break;
+ case 3: initRotaries(SW_MENU, winkel_max, winkel_fein, winkel_hard_max, 1);
+ wert_alt = lastmax;
+ break;
+ }
+ wert_aendern = true;
+ }
+
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 4 )
+ && (wert_aendern == true) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ if ( servo_live == true )
+ SERVO_WRITE(winkel_min);
+ initRotaries(SW_MENU, menuitem, 0, 4, -1);
+ wert_aendern = false;
+ }
+
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL) && (menuitem == 4) ) {
+ u8g2.setCursor(108, 10+(menuitem*13));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ delay(1000);
+ i = 0;
+ }
+ }
+}
+
+void setupAutomatik(void) {
+ int menuitem;
+ int lastautostart = autostart;
+ int lastglastoleranz = glastoleranz;
+ int lastautokorrektur = autokorrektur;
+ int lastkulanz = kulanz_gr;
+ bool wert_aendern = false;
+
+ initRotaries(SW_MENU, 0, 0, 4, -1);
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ autostart = lastautostart;
+ autokorrektur = lastautokorrektur;
+ kulanz_gr = lastkulanz;
+ glastoleranz = lastglastoleranz;
+ return;
+ }
+
+ if ( wert_aendern == false ) {
+ menuitem = getRotariesValue(SW_MENU);
+// if ( menuitem == 3 )
+// menuitem = 4; // Eine Zeile Abstand zu "Speichern"
+ } else {
+ switch (menuitem) {
+ case 0: autostart = getRotariesValue(SW_MENU);
+ break;
+ case 1: glastoleranz = getRotariesValue(SW_MENU);
+ break;
+ case 2: autokorrektur = getRotariesValue(SW_MENU);
+ break;
+ case 3: kulanz_gr = getRotariesValue(SW_MENU);
+ break;
+ }
+ }
+
+ // Menu
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 10); sprintf(ausgabe,"Autostart %3s", (autostart==0?"aus":"ein")); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 23); sprintf(ausgabe,"Glastol. %c%2dg", 177, glastoleranz); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 36); sprintf(ausgabe,"Autokorr. %3s", (autokorrektur==0?"aus":"ein")); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 49); sprintf(ausgabe,"-> Kulanz %2dg", kulanz_gr); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 62); u8g2.print( "Speichern");
+
+ // Positionsanzeige im Menu. "*" wenn nicht ausgewählt, Pfeil wenn ausgewählt
+ if ( wert_aendern == false ) {
+ u8g2.setCursor(0, 10+(menuitem*13)); u8g2.print("*");
+ } else {
+ u8g2.setFont(u8g2_font_open_iconic_arrow_1x_t);
+ u8g2.drawGlyph(0, 10+(menuitem*13), 0x42);
+ u8g2.setFont(u8g2_font_courB10_tf);
+ }
+ u8g2.sendBuffer();
+
+ // Menupunkt zum Ändern ausgewählt
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 4 )
+ && (wert_aendern == false) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ switch (menuitem) {
+ case 0: initRotaries(SW_MENU, autostart, 0, 1, 1);
+ break;
+ case 1: initRotaries(SW_MENU, glastoleranz, 0, 99, 1);
+ break;
+ case 2: initRotaries(SW_MENU, autokorrektur, 0, 1, 1);
+ break;
+ case 3: initRotaries(SW_MENU, kulanz_gr, 0, 99, 1);
+ break;
+ }
+ wert_aendern = true;
+ }
+
+ // Änderung im Menupunkt übernehmen
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 4 )
+ && (wert_aendern == true) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ initRotaries(SW_MENU, menuitem, 0, 4, -1);
+ wert_aendern = false;
+ }
+
+ // Menu verlassen
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL) && (menuitem == 4) ) {
+ u8g2.setCursor(108, 10+(menuitem*13));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ delay(1000);
+ i = 0;
+ }
+ }
+}
+
+void setupFuellmenge(void) {
+ int j,k;
+ int blinktime;
+ initRotaries(SW_MENU, fmenge_index, 0, 4, -1);
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ pos = getRotariesValue(SW_MENU);
+ u8g2.clearBuffer();
+ j = 0;
+ while( j < 5 ) {
+ u8g2.setCursor(10, 10+(j*13));
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.setCursor(0, 10+(getRotariesValue(SW_MENU)*13));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) { // Füllmenge gewählt
+ delay(500);
+
+// initRotaries(SW_MENU, glaeser[pos].Gewicht, 30, 1000, 5);
+ initRotaries(SW_MENU, weight2step( glaeser[pos].Gewicht) , 25, weight2step(MAXIMALGEWICHT), 1);
+ k = 1;
+ while (k > 0){
+
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ blinktime = (millis()/10) % 5;
+// glaeser[pos].Gewicht = getRotariesValue(SW_MENU);
+ glaeser[pos].Gewicht = step2weight( getRotariesValue(SW_MENU) );
+ u8g2.clearBuffer();
+
+ j = 0;
+ while( j < 5 ) {
+ u8g2.setCursor(10, 10+(j*13));
+ if (j == pos){
+ if (blinktime < 3) {
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ } else {
+ sprintf(ausgabe, "%5s %3s"," ",GlasTypArray[glaeser[j].GlasTyp]);
+ }
+ } else {
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ }
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) { // Gewicht bestätigt
+ delay(500);
+
+ initRotaries(SW_MENU, glaeser[pos].GlasTyp, 0, 2, 1);
+ while (k > 0){
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+ blinktime = (millis()/10) % 5;
+ glaeser[pos].GlasTyp = getRotariesValue(SW_MENU);
+ u8g2.clearBuffer();
+
+ j = 0;
+ while( j < 5 ) {
+ u8g2.setCursor(10, 10+(j*13));
+ if (j == pos){
+ if (blinktime < 3) {
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ } else {
+ sprintf(ausgabe, "%4dg %3s",glaeser[pos].Gewicht," ");
+ }
+ } else {
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ }
+ u8g2.print(ausgabe);
+ j++;
+ }
+ u8g2.sendBuffer();
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) { //GlasTyp bestätigt
+ u8g2.clearBuffer();
+ j = 0;
+ while( j < 5 ) {
+ u8g2.setCursor(10, 10+(j*13));
+ sprintf(ausgabe, "%4dg %3s", glaeser[j].Gewicht, GlasTypArray[glaeser[j].GlasTyp]);
+ u8g2.print(ausgabe);
+ j++;
+ }
+
+ u8g2.setCursor(0, 10+(13*pos));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+ delay(1000);
+ k = 0; //raus
+ }
+ }
+ }
+ }
+ fmenge = glaeser[pos].Gewicht;
+ tara = glaeser[pos].Tara;
+ fmenge_index = pos;
+ i = 0;
+ }
+ }
+}
+
+void setupParameter(void) {
+ int menuitem;
+ int lastbuzzer = buzzermode;
+ int lastsetup = setup_modern;
+ bool wert_aendern = false;
+
+ initRotaries(SW_MENU, 0, 0, 2, -1);
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ buzzermode = lastbuzzer;
+ setup_modern = lastsetup;
+ return;
+ }
+
+ if ( wert_aendern == false ) {
+ menuitem = getRotariesValue(SW_MENU);
+ if ( menuitem == 2 )
+ menuitem = 4; // Eine Zeile Abstand zu "Speichern"
+ } else {
+ switch (menuitem) {
+ case 0: buzzermode = getRotariesValue(SW_MENU);
+ break;
+ case 1: setup_modern = getRotariesValue(SW_MENU);
+ break;
+ }
+ }
+
+ // Menu
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ sprintf(ausgabe,"Buzzer %3s", (buzzermode==0?"aus":"ein"));
+ u8g2.setCursor(10, 10); u8g2.print(ausgabe);
+ sprintf(ausgabe,"Menu %6s", (setup_modern==0?" Liste":"Scroll"));
+ u8g2.setCursor(10, 23); u8g2.print(ausgabe);
+ u8g2.setCursor(10, 62); u8g2.print("Speichern");
+
+ // Positionsanzeige im Menu. "*" wenn nicht ausgewählt, Pfeil wenn ausgewählt
+ if ( wert_aendern == false ) {
+ u8g2.setCursor(0, 10+((menuitem)*13)); u8g2.print("*");
+ } else {
+ u8g2.setCursor(0, 10+((menuitem)*13)); u8g2.print("-");
+ }
+ u8g2.sendBuffer();
+
+ // Menupunkt zum Ändern ausgewählt
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 2 )
+ && (wert_aendern == false) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ switch (menuitem) {
+ case 0: initRotaries(SW_MENU, buzzermode, 0, 1, 1);
+ break;
+ case 1: initRotaries(SW_MENU, setup_modern, 0, 1, 1);
+ break;
+ }
+ wert_aendern = true;
+ }
+
+ // Änderung im Menupunkt übernehmen
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL)
+ && (menuitem < 2 )
+ && (wert_aendern == true) ) {
+
+ // debounce
+ delay(10);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL )
+ ;
+ delay(10);
+
+ initRotaries(SW_MENU, menuitem, 0, 2, -1);
+ wert_aendern = false;
+ }
+
+ // Menu verlassen
+ if ( (digitalRead(SELECT_SW) == SELECT_PEGEL) && (menuitem == 4) ) {
+ u8g2.setCursor(108, 10+(menuitem*13));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+
+ delay(1000);
+ i = 0;
+ }
+ }
+}
+
+void setupClearPrefs(void) {
+ initRotaries(SW_MENU, 1, 0, 1, -1);
+
+ i = 1;
+ while (i > 0) {
+ if ((digitalRead(button_stop_pin)) == HIGH)
+ return;
+
+ pos = getRotariesValue(SW_MENU);
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ u8g2.setCursor(10, 12); u8g2.print("Löschen");
+ u8g2.setCursor(10, 28); u8g2.print("Zurück!");
+
+ u8g2.setCursor(0, 12+((pos)*16));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+
+ if ((digitalRead(SELECT_SW)) == SELECT_PEGEL) {
+ u8g2.setCursor(105, 12+((pos)*16));
+ u8g2.print("OK");
+ u8g2.sendBuffer();
+ if ( pos == 0) {
+ preferences.begin("EEPROM", false);
+ preferences.clear();
+ preferences.end();
+ // gelöschte Werte einlesen, sonst bleiben die Variablen erhalten
+ getPreferences();
+ }
+ delay(1000);
+ i = 0;
+ }
+ }
+}
+
+void processSetup(void) {
+ if ( setup_modern == 0 )
+ processSetupList();
+ else
+ processSetupScroll();
+}
+
+void processSetupList(void) {
+ if ( modus != MODE_SETUP ) {
+ modus = MODE_SETUP;
+ winkel = winkel_min; // Hahn schliessen
+ servo_aktiv = 0; // Servo-Betrieb aus
+ SERVO_WRITE(winkel);
+ rotary_select = SW_MENU;
+ initRotaries(SW_MENU, 0, 0, 9, -1);
+ }
+
+ int menuitem = getRotariesValue(SW_MENU);
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.clearBuffer();
+ if( menuitem < 5 ) {
+ u8g2.setCursor(10, 10); u8g2.print("Tara");
+ u8g2.setCursor(10, 23); u8g2.print("Kalibrieren");
+ u8g2.setCursor(10, 36); u8g2.print("Korrektur");
+ u8g2.setCursor(10, 49); u8g2.print("Füllmenge");
+ u8g2.setCursor(10, 62); u8g2.print("Automatik");
+ u8g2.setFont(u8g2_font_open_iconic_arrow_2x_t);
+ u8g2.drawGlyph(112, 64, 0x40);
+ } else {
+ u8g2.setCursor(10, 10); u8g2.print("Servowinkel");
+ u8g2.setCursor(10, 23); u8g2.print("Parameter");
+ u8g2.setCursor(10, 36); u8g2.print("Zähler");//Kud
+ u8g2.setCursor(10, 49); u8g2.print("Zähler Trip");//Kud
+ u8g2.setCursor(10, 62); u8g2.print("Clear Prefs");
+ u8g2.setFont(u8g2_font_open_iconic_arrow_2x_t);
+ u8g2.drawGlyph(112, 16, 0x43);
+ }
+ u8g2.setFont(u8g2_font_courB10_tf);
+ u8g2.setCursor(0, 10 + (((menuitem)%5) * 13));
+ u8g2.print("*");
+ u8g2.sendBuffer();
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) {
+ // sollte verhindern, dass ein Tastendruck gleich einen Unterpunkt wählt
+ delay(250);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL ) {
+ }
+#ifdef isDebug
+ Serial.print("Setup Position: ");
+ Serial.println(menuitem);
+#endif
+
+ int lastpos = menuitem;
+ if (menuitem == 0) setupTara(); // Tara
+ if (menuitem == 1) setupCalibration(); // Kalibrieren
+ if (menuitem == 2) setupKorrektur(); // Korrektur
+ if (menuitem == 3) setupFuellmenge(); // Füllmenge
+ if (menuitem == 4) setupAutomatik(); // Autostart/Autokorrektur konfigurieren
+ if (menuitem == 5) setupServoWinkel(); // Servostellungen Minimum, Maximum und Feindosierung
+ if (menuitem == 6) setupParameter(); // Sonstige Einstellungen
+ if (menuitem == 7) setupCounter(); // Kud Zählwerk Trip
+ if (menuitem == 8) setupTripCounter(); // Kud Zählwerk
+ setPreferences();
+
+ if (menuitem == 9) setupClearPrefs(); // EEPROM löschen
+ initRotaries(SW_MENU, lastpos, 0, 9, -1); // Menu-Parameter könnten verstellt worden sein
+ }
+}
+void processSetupScroll(void) {
+ if ( modus != MODE_SETUP ) {
+ modus = MODE_SETUP;
+ winkel = winkel_min; // Hahn schliessen
+ servo_aktiv = 0; // Servo-Betrieb aus
+ SERVO_WRITE(winkel);
+ rotary_select = SW_MENU;
+ initRotaries(SW_MENU, 124, 0,255, -1);
+ }
+ int MenuepunkteAnzahl = 10;
+ const char *menuepunkte[] = {
+ " Tarawerte","Kalibrieren"," Korrektur"," Füllmenge"," Automatik"," Servoeinst."," Parameter"," Zählwerk","ZählwerkTrip","Clear Prefs"
+ };
+ int menuitem = getRotariesValue(SW_MENU);
+ menuitem = menuitem % MenuepunkteAnzahl;
+
+ u8g2.clearBuffer();
+ //obere Zeile
+ int oberpos = menuitem-1;
+ if (menuitem == 0)
+ oberpos = (MenuepunkteAnzahl-1);
+
+ u8g2.setFont(u8g2_font_courB08_tf);
+ u8g2.setCursor(30,12);
+ u8g2.print(menuepunkte[oberpos]);
+
+ //untere Zeile
+ int unterpos = menuitem+1;
+ if (unterpos == MenuepunkteAnzahl)
+ unterpos=0;
+ u8g2.setCursor(30,62);
+ u8g2.print(menuepunkte[unterpos]);
+
+ //Mittelzeile
+ u8g2.drawLine(1, 20, 120, 20);
+ u8g2.setFont(u8g2_font_courB12_tf);
+ u8g2.setCursor(6, 38);
+ u8g2.print(menuepunkte[menuitem]);
+ u8g2.drawLine(1, 47, 120, 47);
+
+ u8g2.sendBuffer();
+ int lastpos = menuitem;
+
+ if ( digitalRead(SELECT_SW) == SELECT_PEGEL ) {
+ // sollte verhindern, dass ein Tastendruck gleich einen Unterpunkt wählt
+ delay(250);
+ while( digitalRead(SELECT_SW) == SELECT_PEGEL ) {}
+#ifdef isDebug
+ Serial.print("Setup Position: ");
+ Serial.println(menuitem);
+#endif
+
+ int lastpos = menuitem;
+ if (menuitem == 0) setupTara(); // Tara
+ if (menuitem == 1) setupCalibration(); // Kalibrieren
+ if (menuitem == 2) setupKorrektur(); // Korrektur
+ if (menuitem == 3) setupFuellmenge(); // Füllmenge
+ if (menuitem == 4) setupAutomatik(); // Autostart/Autokorrektur konfigurieren
+ if (menuitem == 5) setupServoWinkel(); // Servostellungen Minimum, Maximum und Feindosierung
+ if (menuitem == 6) setupParameter(); // Sonstige Einstellungen
+ if (menuitem == 7) setupCounter(); // Kud Zählwerk
+ if (menuitem == 8) setupTripCounter(); // Kud Zählwerk Trip
+ setPreferences();
+
+ if (menuitem == 9) setupClearPrefs(); // EEPROM löschen
+ initRotaries(SW_MENU,lastpos, 0,255, -1); // Menu-Parameter könnten verstellt worden sein
+ }
+}
+
+void processAutomatik(void)
+{
+ int zielgewicht; // Glas + Korrektur
+ long blinktime;
+ static int autokorrektur_gr = 0;
+ int erzwinge_servo_aktiv = 0;
+ boolean voll = false; //Kud
+
+ static int gewicht_vorher; // Gewicht des vorher gefüllten Glases
+ static int sammler_num = 5; // Anzahl identischer Messungen für Nachtropfen
+
+ if ( modus != MODE_AUTOMATIK ) {
+ modus = MODE_AUTOMATIK;
+ winkel = winkel_min; // Hahn schliessen
+ servo_aktiv = 0; // Servo-Betrieb aus
+ SERVO_WRITE(winkel);
+ auto_aktiv = 0; // automatische Füllung starten
+ tara_glas = 0;
+ rotary_select = SW_WINKEL; // Einstellung für Winkel über Rotary
+ initRotaries(SW_MENU, fmenge_index, 0, 4, 1);
+ gewicht_vorher = glaeser[fmenge_index].Gewicht + korrektur;
+// autokorrektur_gr = 0;
+ }
+
+ pos = getRotariesValue(SW_WINKEL);
+ // nur bis winkel_fein regeln, oder über initRotaries lösen?
+ if ( pos < ((winkel_fein*100)/winkel_max) ) {
+ pos = ((winkel_fein*100)/winkel_max);
+ setRotariesValue(SW_WINKEL, pos);
+ }
+
+#ifdef USE_ROTARY // TODO: kann das Poti hier überhaupt etwas ändern?
+ korrektur = getRotariesValue(SW_KORREKTUR);
+ fmenge_index = getRotariesValue(SW_MENU);
+#endif
+ fmenge = glaeser[fmenge_index].Gewicht;
+ tara = glaeser[fmenge_index].Tara;
+ if ( tara <= 0 )
+ auto_aktiv = 0;
+
+ // wir starten nur, wenn das Tara für die Füllmenge gesetzt ist!
+ // Ein erneuter Druck auf Start erzwingt die Aktivierung des Servo
+ if (((digitalRead(button_start_pin)) == HIGH) && (tara > 0)) {
+ // debounce
+ delay(10);
+ while( digitalRead(button_start_pin) == HIGH )
+ ;
+ delay(10);
+
+ if ( auto_aktiv == 1 ) {
+ erzwinge_servo_aktiv = 1;
+#ifdef isDebug
+ Serial.println("erzwinge Servo aktiv");
+#endif
+ }
+ auto_aktiv = 1; // automatisches Füllen aktivieren
+ rotary_select = SW_WINKEL; // falls während der Parameter-Änderung auf Start gedrückt wurde
+ setPreferences(); // falls Parameter über den Rotary verändert wurden
+ }
+
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ winkel = winkel_min;
+ servo_aktiv = 0;
+ auto_aktiv = 0;
+ tara_glas = 0;
+// autokorrektur_gr = 0;
+ }
+
+// Fehlerkorrektur der Waage, falls Gewicht zu sehr schwankt
+#ifdef FEHLERKORREKTUR_WAAGE
+ int Vergleichsgewicht = (int(SCALE_GETUNITS(SCALE_READS))) - tara;
+ for (byte j = 0 ; j < 3; j++) { // Anzahl der Wiederholungen, wenn Abweichung zu hoch
+ gewicht = (int(SCALE_GETUNITS(SCALE_READS))) - tara;
+ if (abs(gewicht - Vergleichsgewicht) < 50) // Abweichung für Fehlererkennung
+ break;
+ delay(100);
+ }
+#else
+ gewicht = (int(SCALE_GETUNITS(SCALE_READS))) - tara;
+#endif
+
+ // Glas entfernt -> Servo schliessen
+ if (gewicht < -20) {
+ winkel = winkel_min;
+ servo_aktiv = 0;
+ tara_glas = 0;
+ if ( autostart != 1 ) { // Autostart nicht aktiv
+ auto_aktiv = 0;
+ }
+ }
+
+ // Automatik ein, leeres Glas aufgesetzt, Servo aus -> Glas füllen
+ if ((auto_aktiv == 1) && (abs(gewicht) <= glastoleranz) && (servo_aktiv == 0)) {
+ rotary_select = SW_WINKEL; // falls während der Parameter-Änderung ein Glas aufgesetzt wird
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB24_tf);
+ u8g2.setCursor(15, 43);
+ u8g2.print("START");
+ u8g2.sendBuffer();
+ // kurz warten und prüfen ob das Gewicht nicht nur eine zufällige Schwankung war
+ delay(1500);
+ gewicht = (int(SCALE_GETUNITS(SCALE_READS))) - tara;
+
+ if ( abs(gewicht) <= glastoleranz ) {
+ tara_glas = gewicht;
+#ifdef isDebug
+ Serial.print("gewicht: "); Serial.print(gewicht);
+ Serial.print(" gewicht_vorher: "); Serial.print(gewicht_vorher);
+ Serial.print(" zielgewicht: "); Serial.print(fmenge + korrektur + tara_glas + autokorrektur_gr);
+ Serial.print(" kulanz: "); Serial.print(kulanz_gr);
+ Serial.print(" Autokorrektur: "); Serial.println(autokorrektur_gr);
+#endif
+ servo_aktiv = 1;
+ sammler_num = 0;
+ voll = false; //Kud
+ gezaehlt = false; //Kud
+ buzzer(BUZZER_SHORT);
+ }
+ }
+ zielgewicht = fmenge + korrektur + tara_glas + autokorrektur_gr;
+
+ // Anpassung des Autokorrektur-Werts
+ if ( autokorrektur == 1 )
+ {
+ if ( (auto_aktiv == 1) // Automatik ist aktiviert
+ && (servo_aktiv == 0 ) && (winkel == winkel_min) // Hahn ist geschlossen
+ && (gewicht >= zielgewicht ) // Glas ist voll
+ && (sammler_num <= 5) // tropfmenge noch nicht erfasst
+ ) {
+ voll = true;//Kud
+ if ( (gewicht == gewicht_vorher) && (sammler_num < 5) ) { // wir wollen 5x das identische Gewicht sehen
+ sammler_num++;
+ } else if ( gewicht != gewicht_vorher ) { // sonst gewichtsänderung nachführen
+ gewicht_vorher = gewicht;
+ sammler_num = 0;
+ } else if ( sammler_num == 5 ) { // gewicht ist 5x identisch, autokorrektur bestimmen
+ autokorrektur_gr = (fmenge + kulanz_gr + tara_glas) - (gewicht - autokorrektur_gr);
+ if ( korrektur + autokorrektur_gr > kulanz_gr ) { // Autokorrektur darf nicht überkorrigieren, max Füllmenge plus Kulanz
+ autokorrektur_gr = kulanz_gr - korrektur;
+#ifdef isDebug
+ Serial.print("Autokorrektur begrenzt auf ");
+ Serial.println(autokorrektur_gr);
+#endif
+ }
+ buzzer(BUZZER_SUCCESS);
+ sammler_num++; // Korrekturwert für diesen Durchlauf erreicht
+ }
+
+ if ((voll == true) && (gezaehlt == false)) { //Kud
+ glaeser[fmenge_index].TripCount++;
+ glaeser[fmenge_index].Count++;
+// setPreferences(); // machen wir am Ende
+ gezaehlt = true;
+ }
+#ifdef isDebug
+ Serial.print("Nachtropfen:");
+ Serial.print(" gewicht: "); Serial.print(gewicht);
+ Serial.print(" gewicht_vorher: "); Serial.print(gewicht_vorher);
+ Serial.print(" sammler_num: "); Serial.print(sammler_num);
+ Serial.print(" Korrektur: "); Serial.println(autokorrektur_gr);
+ Serial.print(" Zähler Trip: "); Serial.print(glaeser[fmenge_index].TripCount); //Kud
+ Serial.print(" Zähler: "); Serial.println(glaeser[fmenge_index].Count); //Kud
+#endif
+ }
+ }
+
+ // Glas ist teilweise gefüllt. Start wird über Start-Taster erzwungen
+ if ((auto_aktiv == 1) && (gewicht > 5) && (erzwinge_servo_aktiv == 1) ) {
+ servo_aktiv = 1;
+ voll = false; //Kud
+ gezaehlt = false;//Kud
+ buzzer(BUZZER_SHORT);
+ }
+
+ if (servo_aktiv == 1) {
+ winkel = ((winkel_max * pos) / 100);
+ }
+
+ if ((servo_aktiv == 1) && (( zielgewicht - gewicht ) <= fein_dosier_gewicht)) {
+ winkel = ( ((winkel_max*pos) / 100) * ((zielgewicht-gewicht) / fein_dosier_gewicht) );
+ }
+
+ if ((servo_aktiv == 1) && (winkel <= winkel_fein)) {
+ winkel = winkel_fein;
+ }
+
+ // Glas ist voll
+ if ((servo_aktiv == 1) && (gewicht >= zielgewicht)) {
+ winkel = winkel_min;
+ servo_aktiv = 0;
+
+ if (gezaehlt == false) { //Kud
+ glaeser[fmenge_index].TripCount++;
+ glaeser[fmenge_index].Count++;
+// setPreferences(); machen wir am Ende
+ gezaehlt = true;
+ }
+ if ( autostart != 1 ) // autostart ist nicht aktiv, kein weiterer Start
+ auto_aktiv = 0;
+ if ( autokorrektur == 1 ) // autokorrektur, gewicht merken
+ gewicht_vorher = gewicht;
+ buzzer(BUZZER_SHORT);
+ }
+
+ SERVO_WRITE(winkel);
+
+#ifdef isDebug
+#if isDebug >= 4
+ Serial.print("Automatik:");
+ Serial.print(" Gewicht: "); Serial.print(gewicht);
+ Serial.print(" Winkel: "); Serial.print(winkel);
+// Serial.print(" Dauer "); Serial.print(millis() - scaletime);
+// Serial.print(" Füllmenge: "); Serial.print(fmenge);
+// Serial.print(" Korrektur: "); Serial.print(korrektur);
+// Serial.print(" Tara_glas:"); Serial.print(tara_glas);
+ Serial.print(" Autokorrektur: "); Serial.print(autokorrektur_gr);
+ Serial.print(" Zielgewicht "); Serial.print(zielgewicht);
+// Serial.print(" Erzwinge Servo: "); Serial.print(erzwinge_servo_aktiv);
+// Serial.print(" servo_aktiv "); Serial.print(servo_aktiv);
+ Serial.print(" auto_aktiv "); Serial.println(auto_aktiv);
+#endif
+#endif
+ u8g2.clearBuffer();
+
+ // Gewicht blinkt, falls unter der definierten Füllmenge
+ // Korrekturfaktor und Füllmenge blinken, wenn sie über den Rotary verstellt werden
+ blinktime = (millis()/10) % 5;
+
+ // wenn kein Tara für unser Glas definiert ist, wird kein Gewicht sondern eine Warnung ausgegeben
+ if ( tara > 0 ) {
+ // kein Glas aufgestellt
+ if ( gewicht < -20 ) {
+ u8g2.setFont(u8g2_font_courB12_tf);
+ u8g2.setCursor(28, 30); u8g2.print("Bitte Glas");
+ u8g2.setCursor(28, 44); u8g2.print("aufstellen");
+ } else {
+ u8g2.setCursor(10, 42);
+ u8g2.setFont(u8g2_font_courB24_tf);
+
+ if( (autostart == 1) && (auto_aktiv == 1 ) && (servo_aktiv == 0) && (gewicht >= -5) && (gewicht - tara_glas < fmenge) && (blinktime < 2) ) {
+ sprintf(ausgabe,"%5s", " ");
+ } else {
+ sprintf(ausgabe,"%5dg", gewicht - tara_glas);
+ }
+ u8g2.print(ausgabe);
+ }
+ } else {
+ u8g2.setCursor(42, 38);
+ u8g2.setFont(u8g2_font_courB14_tf);
+ sprintf(ausgabe,"%6s", "no tara!");
+ u8g2.print(ausgabe);
+ }
+
+ // Play/Pause Icon, ob die Automatik aktiv ist
+ u8g2.setFont(u8g2_font_open_iconic_play_2x_t);
+ u8g2.drawGlyph(0, 40, (auto_aktiv==1)?0x45:0x44 );
+
+ u8g2.setFont(u8g2_font_courB12_tf);
+ // Zeile oben, Öffnungswinkel absolut und Prozent, Anzeige Autostart
+ u8g2.setCursor(0, 11);
+ sprintf(ausgabe,"W=%-3d %2s %3d%%", winkel, (autostart==1)?"AS":" ", pos);
+ u8g2.print(ausgabe);
+
+ u8g2.setFont(u8g2_font_courB10_tf);
+ // Zeile unten, aktuell zu verstellende Werte blinken.
+ // Verstellung nur wenn Automatik inaktiv, gesteuert über Interrupt-Funktion
+
+ if(servo_aktiv == 1) {
+ int progressbar = 128.0*((float)gewicht/(float)zielgewicht);
+ progressbar = constrain(progressbar,0,128);
+
+ u8g2.drawFrame(0, 50, 128, 14 );
+ u8g2.drawBox (0, 50, progressbar, 14 );
+ }
+ else
+ {
+ if( autokorrektur == 1 ){
+ u8g2.setCursor( 0, 64);
+ u8g2.print("a");
+ u8g2.setCursor(10, 64);
+ } else {
+ u8g2.setCursor( 0, 64);
+ }
+
+ if( rotary_select == SW_KORREKTUR && blinktime < 2 ) {
+ if (glaeser[fmenge_index].Gewicht > 999){
+ sprintf(ausgabe,"k= %s %3s-%3s",(autokorrektur==1)?"":" ", "1kg", GlasTypArray[glaeser[fmenge_index].GlasTyp] );
+ } else {
+ sprintf(ausgabe,"k= %s %3d-%3s",(autokorrektur==1)?"":" ", glaeser[fmenge_index].Gewicht, GlasTypArray[glaeser[fmenge_index].GlasTyp] );
+ }
+ } else if ( rotary_select == SW_MENU && blinktime < 2 ) {
+ sprintf(ausgabe,"k=%-3d" , korrektur + autokorrektur_gr);
+ } else {
+ if (glaeser[fmenge_index].Gewicht > 999){
+ sprintf(ausgabe,"k=%-3d%s %3s-%3s", korrektur + autokorrektur_gr, (autokorrektur==1)?"":" ", "1kg", GlasTypArray[glaeser[fmenge_index].GlasTyp] );
+ } else {
+ sprintf(ausgabe,"k=%-3d%s %3d-%3s", korrektur + autokorrektur_gr, (autokorrektur==1)?"":" ", glaeser[fmenge_index].Gewicht, GlasTypArray[glaeser[fmenge_index].GlasTyp] );
+ }
+ }
+ u8g2.print(ausgabe);
+ }
+
+ u8g2.sendBuffer();
+
+ setPreferences();
+}
+
+void processHandbetrieb(void)
+{
+ static unsigned long scaletime;
+ static unsigned long dauer;
+
+ if ( modus != MODE_HANDBETRIEB ) {
+ modus = MODE_HANDBETRIEB;
+ winkel = winkel_min; // Hahn schliessen
+ servo_aktiv = 0; // Servo-Betrieb aus
+ SERVO_WRITE(winkel);
+ rotary_select = SW_WINKEL;
+ tara = 0;
+ }
+
+ pos = getRotariesValue(SW_WINKEL);
+ gewicht = SCALE_GETUNITS(SCALE_READS) - tara;
+
+ if ((digitalRead(button_start_pin)) == HIGH) {
+ servo_aktiv = 1;
+ }
+
+ if ((digitalRead(button_stop_pin)) == HIGH) {
+ servo_aktiv = 0;
+ }
+
+#ifdef USE_ROTARY_SW
+ if ( ((digitalRead(outputSW)) == LOW) /*&& (tara == 0) */ ) { // sonst muss der Taster entprellt werden!
+ tara = SCALE_GETUNITS(SCALE_READS);
+ }
+#endif
+
+ if (servo_aktiv == 1) {
+ winkel = ((winkel_max * pos) / 100);
+ } else {
+ winkel = winkel_min;
+ }
+ winkel = constrain(winkel, winkel_min, winkel_max);
+ SERVO_WRITE(winkel);
+
+#ifdef isDebug
+#if isDebug >= 4
+ Serial.print("Handbetrieb:");
+ Serial.print(" Gewicht "); Serial.print(gewicht);
+ Serial.print(" Winkel "); Serial.print(winkel);
+ Serial.print(" Dauer "); Serial.print(millis() - scaletime);
+ Serial.print(" servo_aktiv "); Serial.println(servo_aktiv);
+#endif
+#endif
+ scaletime = millis();
+ // Ausgabe OLED. Dauert ca. 170ms
+ u8g2.clearBuffer();
+
+ u8g2.setFont(u8g2_font_courB24_tf);
+ u8g2.setCursor(10, 42);
+ sprintf(ausgabe,"%5dg", gewicht);
+// sprintf(ausgabe,"%5dg", dauer);
+ u8g2.print(ausgabe);
+
+ u8g2.setFont(u8g2_font_open_iconic_play_2x_t);
+ u8g2.drawGlyph(0, 40, (servo_aktiv==1)?0x45:0x44 );
+
+ u8g2.setFont(u8g2_font_courB12_tf);
+ u8g2.setCursor(0, 11);
+ sprintf(ausgabe,"W=%-3d %3d%%", winkel, pos);
+ u8g2.print(ausgabe);
+ u8g2.setCursor(0, 64);
+ sprintf(ausgabe, "Manuell %s", (tara>0?"Tara":" "));
+ u8g2.print(ausgabe);
+
+ u8g2.sendBuffer();
+
+ setPreferences();
+// u8g2.updateDisplayArea(4,2,12,6); // schneller aber ungenaue Displayausgabe.
+ dauer = millis() - scaletime;
+}
+
+void setup()
+{
+ // enable internal pull downs for digital inputs
+ pinMode(button_start_pin, INPUT_PULLDOWN);
+ pinMode(button_stop_pin, INPUT_PULLDOWN);
+ pinMode(switch_betrieb_pin, INPUT_PULLDOWN);
+ pinMode(switch_setup_pin, INPUT_PULLDOWN);
+#if HARDWARE_LEVEL == 2
+ pinMode(vext_ctrl_pin, INPUT_PULLDOWN);
+#endif
+
+ Serial.begin(115200);
+ while (!Serial) {
+ }
+#ifdef isDebug
+ Serial.println("HaniMandl Start");
+#endif
+
+// Rotary
+#ifdef USE_ROTARY_SW
+ pinMode(outputSW, INPUT_PULLUP);
+ attachInterrupt(outputSW, isr1, FALLING);
+#endif
+#ifdef USE_ROTARY
+ pinMode(outputA,INPUT);
+ pinMode(outputB,INPUT);
+ attachInterrupt(outputA, isr2, CHANGE);
+#endif
+
+// switch Vcc / GND on normal pins for convenient wiring
+// output is 3.3V for VCC
+ digitalWrite (switch_vcc_pin, HIGH);
+ digitalWrite (button_start_vcc_pin, HIGH);
+ digitalWrite (button_stop_vcc_pin, HIGH);
+
+// pinMode (_GND, OUTPUT); // turn on GND pin first (important!)
+// turn on VCC power
+ pinMode (switch_vcc_pin, OUTPUT);
+ pinMode (button_start_vcc_pin, OUTPUT);
+ pinMode (button_stop_vcc_pin, OUTPUT);
+
+// Buzzer
+ pinMode(buzzer_pin, OUTPUT);
+
+// short delay to let chip power up
+ delay (100);
+
+// Preferences aus dem EEPROM lesen
+ getPreferences();
+
+// Servo initialisieren und schliessen
+#ifdef SERVO_ERWEITERT
+ servo.attach(servo_pin, 750, 2500); // erweiterte Initialisierung, steuert nicht jeden Servo an
+#else
+ servo.attach(servo_pin, 1000, 2000); // default Werte. Achtung, steuert den Nullpunkt weniger weit aus!
+#endif
+ SERVO_WRITE(winkel_min);
+
+// Waage erkennen - machen wir vor dem Boot-Screen, dann hat sie 3 Sekunden Zeit zum aufwärmen
+ scale.begin(hx711_dt_pin, hx711_sck_pin);
+ if (scale.wait_ready_timeout(1000)) { // Waage angeschlossen?
+ scale.power_up();
+ waage_vorhanden = 1;
+#ifdef isDebug
+ Serial.println("Waage erkannt");
+#endif
+ }
+
+// Boot Screen
+ u8g2.setBusClock(800000); // experimental
+ u8g2.begin();
+ u8g2.enableUTF8Print();
+ u8g2.clearBuffer();
+ print_logo();
+ buzzer(BUZZER_SHORT);
+ delay(2000);
+ print_credits();
+ delay(4000);
+
+// Setup der Waage, Skalierungsfaktor setzen
+ if (waage_vorhanden ==1 ) { // Waage angeschlossen?
+ if ( faktor == 0 ) { // Vorhanden aber nicht kalibriert
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB18_tf);
+ u8g2.setCursor( 24, 24); u8g2.print("Nicht");
+ u8g2.setCursor( 10, 56); u8g2.print("kalibr.");
+ u8g2.sendBuffer();
+#ifdef isDebug
+ Serial.println("Waage nicht kalibriert!");
+#endif
+ delay(2000);
+ } else { // Tara und Skalierung setzen
+ scale.set_scale(faktor);
+ scale.set_offset(long(gewicht_leer));
+#ifdef isDebug
+ Serial.println("Waage initialisiert");
+#endif
+ }
+ } else { // Keine Waage angeschlossen
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB24_tf);
+ u8g2.setCursor( 14, 24); u8g2.print("Keine");
+ u8g2.setCursor( 6, 56); u8g2.print("Waage!");
+ u8g2.sendBuffer();
+ buzzer(BUZZER_ERROR);
+#ifdef isDebug
+ Serial.println("Keine Waage!");
+#endif
+ delay(2000);
+ }
+
+// initiale Kalibrierung des Leergewichts wegen Temperaturschwankungen
+// Falls mehr als 20g Abweichung steht vermutlich etwas auf der Waage.
+ if (waage_vorhanden == 1) {
+ gewicht = SCALE_GETUNITS(SCALE_READS);
+ if ( (gewicht > -20) && (gewicht < 20) ) {
+ scale.tare(10);
+ buzzer(BUZZER_SUCCESS);
+#ifdef isDebug
+ Serial.print("Tara angepasst um: ");
+ Serial.println(gewicht);
+#endif
+ } else if (faktor != 0) {
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_courB18_tf);
+ u8g2.setCursor( 24, 24); u8g2.print("Waage");
+ u8g2.setCursor( 10, 56); u8g2.print("leeren!");
+ u8g2.sendBuffer();
+#ifdef isDebug
+ Serial.print("Gewicht auf der Waage: ");
+ Serial.println(gewicht);
+#endif
+ delay(5000);
+
+ // Neuer Versuch, falls Gewicht entfernt wurde
+ gewicht = SCALE_GETUNITS(SCALE_READS);
+ if ( (gewicht > -20) && (gewicht < 20) ) {
+ scale.tare(10);
+ buzzer(BUZZER_SUCCESS);
+#ifdef isDebug
+ Serial.print("Tara angepasst um: ");
+ Serial.println(gewicht);
+#endif
+ } else { // Warnton ausgeben
+ buzzer(BUZZER_LONG);
+ }
+ }
+ }
+
+// die drei Datenstrukturen des Rotaries initialisieren
+ initRotaries(SW_WINKEL, 0, 0, 100, 5 ); // Winkel
+ initRotaries(SW_KORREKTUR, 0, -90, 20, 1 ); // Korrektur
+ initRotaries(SW_MENU, 0, 0, 7, 1 ); // Menuauswahlen
+
+// Parameter aus den Preferences für den Rotary Encoder setzen
+ setRotariesValue(SW_WINKEL, pos);
+ setRotariesValue(SW_KORREKTUR, korrektur);
+ setRotariesValue(SW_MENU, fmenge_index);
+}
+
+void loop()
+{
+ rotating = true; // debounce Management
+
+ // Setup Menu
+ if ((digitalRead(switch_setup_pin)) == HIGH)
+ processSetup();
+
+ // Automatik-Betrieb
+ if ((digitalRead(switch_betrieb_pin)) == HIGH)
+ processAutomatik();
+
+ // Handbetrieb
+ if ((digitalRead(switch_betrieb_pin) == LOW)
+ && (digitalRead(switch_setup_pin) == LOW))
+ processHandbetrieb();
+}
+
+void print_credits() {
+ u8g2.clearBuffer();
+ u8g2.setFont(u8g2_font_helvB08_tf);
+ u8g2.setCursor(0, 10); u8g2.print("Idee: M. Vasterling");
+ u8g2.setCursor(0, 23); u8g2.print("Code: M. Vasterling, M.");
+ u8g2.setCursor(0, 36); u8g2.print("Wetzel, C. Gruber, A.");
+ u8g2.setCursor(0, 49); u8g2.print("Holzhammer, M. Junker,");
+ u8g2.setCursor(0, 62); u8g2.print("J. Kuder, J. Bruker");
+ u8g2.sendBuffer();
+}
+
+void print_logo() {
+ const unsigned char logo_biene1[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0,
+ 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xC1, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x3F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x70, 0x00, 0xF0, 0xFF, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xE0, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0xFF, 0xFF, 0x80, 0xF1, 0x47, 0xF0, 0x07, 0x00, 0x3E, 0xE0, 0xFF, 0xFF, 0x07,
+ 0xF9, 0x07, 0x7E, 0x00, 0x00, 0x78, 0xF0, 0x03, 0xE0, 0x1F, 0xF8, 0x07, 0x1F, 0x00, 0x00, 0x70, 0x3C, 0x00, 0x00, 0xFE, 0x38, 0xC0, 0x03, 0x00,
+ 0x00, 0xF0, 0x0E, 0x00, 0x00, 0xF8, 0x03, 0xF8, 0x00, 0x00, 0x00, 0xE0, 0x06, 0x00, 0x00, 0xC0, 0x0F, 0x7C, 0x00, 0x00, 0x00, 0xE0, 0x06, 0x00,
+ 0x00, 0x00, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x00, 0xFC, 0x07, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x00, 0xF0, 0x03,
+ 0x00, 0x00, 0x00, 0x38, 0x03, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x1C, 0x07, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0x07, 0x00, 0x00, 0x0F,
+ 0x0F, 0x00, 0x00, 0x78, 0x78, 0xE0, 0x3F, 0x00, 0xC0, 0x07, 0x3E, 0x00, 0x80, 0xFF, 0x3C, 0xC0, 0x7F, 0x00, 0xF0, 0x01, 0xFC, 0x00, 0xE0, 0xFF,
+ 0x1C, 0x80, 0xFF, 0x01, 0x7E, 0x00, 0xF0, 0xFF, 0xFF, 0x3F, 0x0E, 0x00, 0xFE, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x07, 0x0F, 0x00, 0xC0, 0x1F,
+ 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x80, 0x03, 0x80, 0x03, 0xE0, 0x00, 0x70, 0x00, 0x00, 0x00, 0xC0,
+ 0x01, 0xC0, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xE0, 0x81, 0xC3, 0x01, 0xC0, 0x01, 0x00, 0x00, 0x70, 0x00, 0xE0, 0xF1, 0x8F,
+ 0x03, 0x80, 0x03, 0x00, 0x00, 0x38, 0x00, 0xF0, 0xFC, 0x9F, 0x07, 0x00, 0x07, 0x00, 0x00, 0x1C, 0x00, 0xF8, 0x1C, 0x1C, 0x0F, 0x00, 0x06, 0x00,
+ 0x00, 0x1C, 0x00, 0xFE, 0x00, 0x00, 0x1F, 0x00, 0x0C, 0x00, 0x00, 0x0E, 0x00, 0xF7, 0x00, 0x00, 0x7F, 0x00, 0x0C, 0x00, 0x00, 0x06, 0x80, 0x73,
+ 0x00, 0x00, 0xE6, 0x00, 0x0C, 0x00, 0x00, 0x07, 0xE0, 0x71, 0x00, 0x00, 0xC6, 0x03, 0x0C, 0x00, 0x00, 0x07, 0x70, 0x70, 0xF0, 0x0F, 0x86, 0x07,
+ 0x0C, 0x00, 0x00, 0x03, 0x3C, 0x70, 0xFC, 0x3F, 0x06, 0x1F, 0x0E, 0x00, 0x00, 0x03, 0x1E, 0x70, 0xFE, 0x3F, 0x06, 0xFC, 0x07, 0x00, 0x00, 0x87,
+ 0x0F, 0x70, 0x1E, 0x38, 0x06, 0xF0, 0x03, 0x00, 0x00, 0xFE, 0x03, 0xF0, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00, 0xFC, 0x00, 0xF0, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x80, 0x03, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xE0, 0x0F, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xE0, 0xF1, 0x9F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x3B, 0x9C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0,
+ 0x07, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0D,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ u8g2.clearBuffer();
+ u8g2.drawXBM(0,0,80,64,logo_biene1);
+ u8g2.setFont(u8g2_font_courB14_tf);
+ u8g2.setCursor(85, 27); u8g2.print("HANI");
+ u8g2.setCursor(75, 43); u8g2.print("MANDL");
+ u8g2.setFont(u8g2_font_courB08_tf);
+ u8g2.setCursor(77, 64); u8g2.print("v.0.2.13");
+ u8g2.sendBuffer();
+}
+
+// Wir nutzen einen aktiven Summer, damit entfällt die tone Library, die sich sowieso mit dem Servo beisst.
+void buzzer(byte type) {
+ if (buzzermode == 1) {
+ switch (type) {
+ case BUZZER_SHORT: //short
+ digitalWrite(buzzer_pin,HIGH);
+ delay(100);
+ digitalWrite(buzzer_pin,LOW);
+ break;
+
+ case BUZZER_LONG: //long
+ digitalWrite(buzzer_pin,HIGH);
+ delay(500);
+ digitalWrite(buzzer_pin,LOW);
+ break;
+
+ case BUZZER_SUCCESS: //success
+ digitalWrite(buzzer_pin,HIGH);
+ delay(100);
+ digitalWrite(buzzer_pin,LOW);
+ delay(100);
+ digitalWrite(buzzer_pin,HIGH);
+ delay(100);
+ digitalWrite(buzzer_pin,LOW);
+ delay(100);
+ digitalWrite(buzzer_pin,HIGH);
+ delay(100);
+ digitalWrite(buzzer_pin,LOW);
+ break;
+
+ case BUZZER_ERROR: //error
+ digitalWrite(buzzer_pin,HIGH);
+ delay(1500);
+ digitalWrite(buzzer_pin,LOW);
+ break;
+ }
+ }
+}
+
+// Supportfunktionen für stufenweise Gewichtsverstellung
+int step2weight( int step ) {
+ int sum = 0;
+
+ if ( step > 210 ) { sum += (step-210)*1000; step -= (step-210); }
+ if ( step > 200 ) { sum += (step-200)* 500; step -= (step-200); }
+ if ( step > 160 ) { sum += (step-160)* 100; step -= (step-160); }
+ if ( step > 140 ) { sum += (step-140)* 25; step -= (step-140); }
+ if ( step > 50 ) { sum += (step- 50)* 5; step -= (step- 50); }
+ sum += step;
+
+ return sum;
+}
+int weight2step ( int sum ) {
+ int step = 0;
+
+ if ( sum > 10000 ) { step += (sum-10000)/1000; sum -= ((sum-10000)); }
+ if ( sum > 5000 ) { step += (sum-5000)/500; sum -= ((sum-5000)); }
+ if ( sum > 1000 ) { step += (sum-1000)/100; sum -= ((sum-1000)); }
+ if ( sum > 500 ) { step += (sum-500)/25; sum -= ((sum-500)); }
+ if ( sum > 50 ) { step += (sum-50)/5; sum -= (sum-50); }
+ step += sum;
+
+ return step;
+}