Habits

Aus HRW FabLab MediaWiki
Wechseln zu: Navigation, Suche

Habits ist ein System, welches das Verhalten eines Nutzers verfolgt. Es besteht aus drei Komponenten, einer App, einem Backend und einem Gerät, welches von dem/der Nutzer*in getragen wird. Das Gerät misst mithilfe verschiedener Sensoren Daten zur Stehzeit, Schrittanzahl und zur verbrachten Zeit draußen verarbeitet diese und gibt sie an das Backend weiter. Das Backend verarbeitet diese Informationen, ordnet sie den verschiedenen Nutzern und Nutzerinnen zu und berechnet einen Punktestand. All diese Daten werden schließlich in einer App gebündelt dargestellt.

Habits

Habits Logo.png

Entwickler

Gordon Krisch, Raoul Müller, Nils Betten, Daniel Grigorasch

Verwendete Programmiersprachen

C, C++, Java, JavaScript, Dart, HTML

Dateien

GitLab, Thingiverse

Eingesetzte Software

Autodesk Fusion 360, Arduino IDE, IntelliJ Idea

Eingesetzte Werkzeuge

Prusa Mk3s, Anycubic i3 Mega, Lötkolben

Plakat[Bearbeiten]

Habits Plakat.png

Hardware[Bearbeiten]

Komponenten[Bearbeiten]

In dem Gerät sind eingesetzt:

- 1x ESP32

- 1x DHT22

- 1x MPU 6050

- 1x Piezo Buzzer

- 4x Jumperwire female-female

- 2x modified Jumperwire female-female

Cable modified.jpeg

Case[Bearbeiten]

Das Case ist ein selbstdesigntes und mittels additiver Fertigung produziertes Gehäuse, die .stl Dateien finden sich auf Thingiverse. Das Case besteht aus zwei Teilen, einem Gürtelclip und einer Haube, welche an einem Scharnier hängt. Der Pin des Scharniers ist normales 1,75mm Filament eines 3D-Druckers, welches an den Enden mit einer Zange zusammengepresst wurde und dadurch nicht herausrutschen kann. Am oberen Teil des Cases wird ebenfalls Filament verwendet, um das Case geschlossen zu halten, dafür sind zwei korrespondierende Löcher in Kappe und Schnalle eingearbeitet. Im Case selbst findet der ESP mit dem Display nach außen Platz, Kleber wird empfohlen, wenn das Gerät langfristig zusammengesetzt bleiben soll, mittelfristig hält der ESP jedoch auch als press fit im Gehäuse. Der USB-C Port des ESPs ist auf der Unterseite zugänglich, um einen Zugriff und in der Prototypenphase eine Stromversorgung sicherzustellen. Auf der (von vorne) linken Seite befindet sich eine Öffnung mit einem danebenliegenden Pin, in welche ein DHT22 Sensor passt. Der Pin stellt sicher, dass der Sensor nicht verrutscht oder zu weit aus dem Gehäuse herausragt. Der Sensor muss nicht verklebt werden und hält so. In die obrige Aussparung kommt der Piezobuzzer, welcher durch ein kleines Loch nach oben raus Töne abgeben kann. Auch hier: kein Kleber erforderlich. Der MPU6050 kommt an das zweite Bauteil des Gehäuses auf die beiden Pins oben. Auch dies hält ohne Kleber.

Case Inside Empty.jpeg Case Outside Empty.jpeg Case Back Empty.jpeg

Assembly[Bearbeiten]

Der Zusammenbau beginnt damit, die beiden Bauteile des Gehäuses am Scharnier zusammenzustecken und ein Stück 1,75mm Filament als Scharnierpin durchzuführen.

Als Nächstes am ESP alle Kabel anzuschließen, da dieser als Erstes eingesetzt wird und ein Zugang später sehr schwierig ist.

Schematische Verkabelung

Dann den ESP in seine Aussparung im Gehäuse platzieren und reindrücken. Bei Bedarf mit einem Tropfen Kleber festkleben.

Case PartlyAssembled 1.jpeg

Als Nächstes alle benötigten Kabel an den DHT22 anschließen und diesen in die entsprechende Aussparung in der Seite stecken. Das Kabel bereits im Gehäuse verstauen.

Den Piezolautsprecher verkabeln und im Oberteil einsetzen, die Kabel verstauen.

Dann den MPU 6050 verkabeln, auf seine Pins an der Schnalle stecken und alle Kabel final im Gehäuse verstauen.

Case FullyAssembled 1.jpeg

Das Gehäuse schließen und oben mit einem kleinen Stück Filament gegen ungewolltes Öffnen sichern.

Case FullyAssembled Outside.jpeg

ESP[Bearbeiten]

Grobes Flussdiagramm des ESP

Beim Starten des ESP’s wird zuerst das Display initialisiert. Alle Icons werden an die entsprechenden Stellen geladen. Die WiFiManager-Library kümmert sich um die WiFi-Verbindung. Sie prüft zuerst, ob Konfigurationsdaten, sprich Netz-SSID und Password im EEPROM-Speicher vorliegen. Ist dies der Fall, wird ein Verbindungsversuch unternommen. Gibt es keine Netz-Konfiguration (z.B. bei der ersten Inbetriebnahme), oder hat die Verbindung fehlgeschlagen wechselt der ESP in den AccessPoint-Mode. In diesem Modus kann sich der/die Nutzer*in über eine WLAN-Verbindung mit dem ESP verbinden. Die Netz-SSID des ESP fängt mit den Zeichen „ESP32_“ an, gefolgt von der einmaligen BoardID (z.B. ESP32_123A45678). Nach der Verbindung öffnet sich automatisch eine Website. Über diese kann eine Netzwerkverbindung eingerichtet werden (z.B. Verbindung an einen Handy-Hotspot). Der Access-Point-Mode wird alle 30 Sekunden für einen Bruchteil einer Sekunde unterbrochen, um zu prüfen, ob eine Verbindung mit dem Netzwerk aus der Netz-Konfiguration aus dem EEPROM möglich ist. Besteht eine Verbindung mit einem Netzwerk, verbindet sich der ESP mit dem MQTT-Server. Sobald das Setup abgeschlossen ist, fängt der ESP an die Sensordaten zu messen und auszuwerten. Dabei werden die Messwerte über einen bestimmten Zeitraum in einer for-Schleife gemessen und es wird, bis auf die Schrittzahl, der Mittelwert gebildet. Dadurch haben fehlerhafte Werte einen weniger relevanten Einfluss auf das Endergebnis. Nach dem Messen werden die Daten in eine JSON-Datei verpackt und in einen „Events“-Channel auf dem MQTT-Server hochgeladen. Sobald die Daten ankommen werden diese im Server verarbeitet und die Tagesdaten werden in einen MQTT-Channel geschickt. Dieser trägt den Namen der Netz-SSID des ESP’s (für jeden ESP unterschiedlich). Der ESP besitzt eine Funktion, die automatisch ausgelöst wird, sobald eine neue Nachricht in den besagten Channel eingeht. Die Funktion erhält die Daten als JSON-Datei, entpackt diese und gibt sie auf dem Display wieder. Danach wird das Programm an der Stelle fortgesetzt, an der es unterbrochen wurde. Nach jedem Durchlauf wird erneut überprüft, ob alle Verbindungen immer noch stabil sind.

Aktorik[Bearbeiten]

Beispiel Display

Das Display zeigt die verschiedenen Messwerte (inklusive Score) anschaulich formatiert auf dem eingebauten TFT-Display des ESPs an. Zusätzlich werden/können ganz oben noch Systeminformationen angezeigt werden. Ein WLAN-Icon zeigt an, ob eine Verbindung zum WiFi, Internet und MQTT-Server besteht. Es wird rot und durchgestrichen, wenn die Verbindung unterbrochen ist. Vorab haben wir schon einmal ein Akku-Icon platziert, welcher in Zukunft für den Akkustand genutzt werden könnte. Über den Speaker können wir den/die Nutzer*in mithilfe drei verschiedener Töne informieren, in welchem Zustand sich das System befindet. Da die in Arduino enthaltene Tone-Library nicht mit dem ESP kompatibel ist mussten wir, um die Töne erzeugen zu können, die Tone32 Library verwenden.

Der Startup-Sound wird abgespielt, sobald der ESP an eine Stromversorgung angeschlossen wird. Es signalisiert dem/der Nutzer*in, dass das System hochfährt.


Der Disconnected-Sound wird immer dann abgespielt, wenn der ESP nicht mehr mit dem WiFi, dem Internet, oder dem MQTT-Server verbunden ist. Da der ESP die Daten nicht zwischenspeichert können wir dadurch verhindern, dass die Werte verloren gehen und der/die Nutzer*in nichts davon mitkriegt. Das WLAN-Icon ändert sich in etwa zeitgleich in ein rotes durchgestrichenes WLAN-Icon. Der Sound wird kurz nach dem Start immer einmal abgespielt.


Der Connected-Sound wird immer abgespielt, wenn der ESP wieder eine Verbindung zum WiFi, Internet, und MQTT-Server hat. Das WLAN-Icon ändert sich auch hier in etwa zeitgleich in ein grünes WLAN-Icon.

Sensorik[Bearbeiten]

MPU 6050 Gyroskop- und Beschleunigungssensor[Bearbeiten]

Ausrichtung im Raum[Bearbeiten]

Berechnung der Ausrichtung im Raum

Um zu ermitteln, ob der/die Nutzer*in steht (oder nicht) muss mit dem MPU 6050 seine/ihre Ausrichtung im Raum ermittelt werden. Unser erster Gedanke war es dies mit dem Gyroskopsensor des Moduls zu erreichen. Leider mussten wir feststellen, dass dieser nur die Beschleunigung um die jeweiligen Achsen (in m/s^2) anzeigt. Unser zweiter Ansatz war es die Beschleunigung zu nutzen und mithilfe der Eulerschen Winkel die Position zu errechnen. Da wir alle drei Achsen zur selben Zeit betrachtet haben stießen wir auf das Gimbal Lock Problem. Wenn das System senkrecht stand, also so, wie wir es brauchten, um es als stehend zu definieren, traten manchmal Anomalien auf. Die Lösung zu dem Gimbal Lock Problem ist die Nutzung von Quaternionen. Für die Berechnung fehlten uns noch die Werte q0, q1, q2, und q3. Um diese zu erhalten, nutzten wir Madgwicks IMU Algorithmus. Mithilfe dieser Library konnten wir das Problem des Gimbal Locks umgehen.

Dabei fiel uns aber ein weiteres Problem auf. Für den Algorithmus der q0 bis q3 ermittelt sollten die Sensoren möglichst genau sein. Optional sind hierzu auch Sensordaten eines Magnetometers. Da wir dies jedoch nicht hatten, waren die Werte, die wir erhielten relativ ungenau und teilweise fehlerberauscht. Schließlich kamen wir auf die Idee nur zwei Achsen / Raumdimensionen gleichzeitig zu betrachten. Dadurch ergaben sich für die zwei für uns relevanten Rotationen folgende Formeln:

g1(x) = m1 * x
g2(x) = m2 * x

m1 = x/z (x und z in m/s^2 -> m einheitslos)
m2 = x/y (x und y in m/s^2 -> m einheitslos)

Mit a = arctan(m) folgt:

Roll = arctan(m1)*180/PI
Pitch = arctan(m2)*180/PI

Wir haben diese noch einmal etwas umgeändert, um bei einer aufrechten Position für beide Werte 0° zu erhalten:

Roll = 90-arctan(m1)*180/PI
Pitch = 90-arctan(m2)*180/PI

(Da es auf die Ausrichtung des MPU-6050 ankommt sind diese Formeln nur gültig, sofern der MPU-6050 genau wie bei uns montiert wird!)


Durch diese Methode hatten wir zwar dreidimensional gesehen nur eine Halbkugel in der wir die Position in Grad messen und errechnen konnten, aber dieser Bereich reichte uns aus, um festzustellen, ob der/die Nutzer*in steht oder nicht steht. Bei der unteren Halbkugel waren uns die konkreten Positionen des/der Nutzers/Nutzerin gleichgültig. Die gesamte untere Halbkugel haben wir als nicht stehend definiert und mit einer If-Abfrage im Code ausgeschlossen.

Schritte[Bearbeiten]

Um die Schritte zu zählen wollten wir zuerst die Beschleunigungen, die wir erhielten, in einen Vektor umrechnen:

normAccel(x,y,z) = sqrt(x^2+y^2+z^2)

Danach wollten wir den erhaltenen Vektor der Beschleunigung zwei mal integrieren, um so die zurückgelegte Strecke (in Metern) zu erhalten:

a(t) = normAccel(x,y,z)
v(t) = a*∆t + v(t-1)
s(t) = 1/2*a*∆t^2 + v(t-1)*∆t + s(t-1)
∆t = Abtastrate (z.B. 10 ms)

Hier war nun das Problem, dass durch die zweifache Integration die Ungenauigkeiten des Sensors mehrfach verstärkt wurden und so unbrauchbare Ergebnisse (zum Beispiel 1000 m Distanz nach 30 Sekunden) auftraten. Diese konnten nur durch komplexe Filter gelöst werden. Daher entschieden wir uns auch hier für einen anderen Ansatz.

Wir haben zunächst die Norm der Beschleunigung berechnet und diese mit der zuvor gemessenen verglichen. Wenn der Unterschied einen gewissen Wert (trebble) übersteigt deuten wir dies als ein Schritt. Die Zeit zwischen den Messungen, sowie der trebble sind Parameter die wir durch Heuristik bestimmt haben, bis die Schritte in etwa richtig gemessen wurden. Einfach formuliert messen wir die Erschütterung des Beschleunigungssensors und prüfen, ob diese einen gewissen Wert übersteigt.

a = normAccel(x,y,z)
x = |a - aOld|
x > trebble -> Step

Durch diese Methode ist eine genaue Ermittlung der Schritte nicht möglich, von uns aber auch nicht notwendig gewollt. Zusätzlich setzen wir die Schrittzahl manuell auf 0, wenn das System erkennt, dass der/die Nutzer*in nicht steht.

TMP36 & DHT22[Bearbeiten]

Die Grundidee besagt, dass aus der anliegenden Umgebungstemperatur geschlossen werden kann, ob sich eine Person draußen, oder drinnen aufhält. Dazu haben wir einen Temperaturbereich definiert (ca. 14° C bis 26° C), bei dem wir annehmen, dass der/die Nutzer*in sich drinnen aufhält. Dabei stießen wir auf zwei Probleme. Während der Tests (bei ca. 5° C Außentemperatur) ist uns aufgefallen, dass das System recht schnell erkennt, wenn die Umgebungstemperatur außerhalb des Temperaturbereiches liegt (ca. 1 min). Es dauert aber relativ lange, bis der TMP36 Sensor die tatsächliche Umgebungstemperatur annimmt. Bis der Sensor von den 5° C Außentemperatur wieder auf eine Temperatur, die in dem von uns definierten Temperaturbereich liegt, gestiegen ist, sind ungefähr 40 Minuten vergangen. Das heißt, dass bei größeren Temperaturunterschieden (drinnen/draußen) es theoretisch bis zu 45 Minuten dauern könnte, bis das System registriert, dass der Nutzer sich jetzt drinnen bzw. draußen aufhält. Ein weiteres Problem, welches wir nur theoretisch festgestellt haben, ist, dass nicht zwischen drinnen und draußen unterschieden werden kann, wenn die Temperaturen draußen und drinnen in etwa gleich sind.

Beide Probleme haben wir (zumindest teilweise) dadurch gelöst, dass wir einen DHT22 Temperatursensor verwendet haben. Dieser passt sich schneller an die Umgebungstemperatur an. Eine Verzögerung war zwar immer noch festzustellen, wurde aber deutlich minimiert. Das Problem, dass die Außentemperatur mit der Raumtemperatur zu Hause übereinstimmen könnte, haben wir dadurch gelöst, dass wir den Feuchtigkeitssensor des DHT22 mitgenutzt haben, um die Luftfeuchtigkeit zu ermitteln und daraus die gefühlte Lufttemperatur zu berechnen. Die gefühlte Temperatur drinnen müsste sich mit einer höheren Wahrscheinlichkeit von der draußen unterscheiden. Das Problem ist damit leider nicht komplett behoben, aber die Wahrscheinlichkeit, dass ein solches auftritt wurde minimiert.

Formel zur Berechnung drinnen/draußen:
 (|gefühlteLufttemperatur - gefühlteTemperaturZuHause| > trebble) -> draußen
 (|gefühlteLufttemperatur - 20° C| > 6° C) -> draußen

Die gefühlte Lufttemperatur berechnet die DHT Library von Adafruit nach folgendem Prinzip für uns.

Bei der Verwendung eines Gehäuses ist uns ein weiteres Problem aufgefallen. Da der Kunststoff die Temperatur im Gehäuse relativ gut isoliert und kaum absorbiert und der ESP sich nach kürzerer Nutzung (ca. 10 Minuten) leicht erhitzt zeigte uns der Temperatursensor überraschenderweise Temperaturwerte von bis zu 27° C an. Mit der simplen Annahme, dass die Temperatur aufgrund der Erhitzung konstant ungefähr 5° C höher ist haben wir den Standardwert für die gefühlte Temperatur drinnen einfach um diese 5° C erhöht. Dadurch erhielten wir einen neuen Temperaturbereich von ca. 19° C bis 31° C.

App[Bearbeiten]

Habits App.png

Die zu Habits gehörende App hat zwei Aufgaben. Einerseits wird sie gebraucht, um den Tracker einzurichten und andererseits wird in ihr der aktuelle Punktestand aller Nutzer*innen angezeigt.

Screens[Bearbeiten]

Startup[Bearbeiten]

Dieser Screen erscheint wenn die App gestartet wird für einen kurzen Moment. Währenddessen wird die Verbindung zum Backend aufgebaut und erste Daten heruntergeladen. Ist dies abgeschlossen, wird der/die Nutzer*in entweder zum Setup- oder zum Standings-Screen weitergeleitet. Kann keine Verbindung zum Backend aufgebaut werden, wird der/die Nutzer*in aufgefordert, sein Smartphone mit dem Internet zu verbinden und auf einen "Refresh"-Button zu klicken.

Setup[Bearbeiten]

Dieser Screen erscheint, wenn die App auf einem Gerät geöffnet wird, das dem Backend bisher nicht bekannt ist. Hier kann der/die Nutzer*in ein Profilbild machen, einen Profilnamen eingeben und seinen/ihren Tracker in einem Dropdown-Menü auswählen. Nach Klick auf den "Send"-Button werden die Daten an das Backend übermittelt und der/die Nutzer*in wird zum Standings-Screen weitergeleitet.

Standings[Bearbeiten]

Dieser Screen ist der Haupt-Screen der App. Hier werden dem/der Nutzer*in die aktuellen Punktestände aller Nutzenden in einem Ranking angezeigt. Der eigene Punktestand ist dabei optisch hervorgehoben. Durch Klick auf ein Profil öffnet sich eine Detail-Ansicht, in der der/die Nutzer*in den Punkteverlauf des aktuellen und des letzten Tages betrachten kann. Durch Swipen des Diagramms können die Punkte auch aufgeschlüsselt nach den Disziplinen "steps", "standing" und "outside" angezeigt werden.

Settings[Bearbeiten]

Dieser Screen ist durch Klick auf das Zahnrad-Symbol im Standings-Screen erreichbar. Hier können die im Setup-Screen angegebenen Daten bearbeitet werden.

Genutzte Frameworks/Libraries[Bearbeiten]

Zur Entwicklung der App wurde das Framework Flutter genutzt. Ursprünglich war das Ziel von Flutter, native Android- und iOS-Apps mit nur einer Codebasis (Programmiersprache Dart) zu entwickeln. Seit Version 2.0, welche im März erschien, werden allerdings auch native Websites, sowie MacOS-, Linux- und Windows-Apps im stable Release unterstützt. Für unsere Zwecke haben wir uns auf Android und iOS als Zielplattformen beschränkt, wobei die Funktion, ein Profilbild zu machen, der iOS-App vorbehalten ist. Zusätzlich zum Funktionsumfang, den Flutter selbst mit sich bringt, wurden folgende Libraries genutzt:

- bloc: 7.0.0-nullsafety.0 (für das State Management)

- device_info: 2.0.0-nullsafety.1 (zur Bestimmung der DeviceId des Smartphones)

- fl_chart: 0.12.2 (für die Diagramme)

- google_fonts: 1.1.1 (zum Ändern der Schriftart im Header)

- http: 0.12.2 (zur Kommunikation mit der REST-Schnittstelle des Backends)

- image_picker: 0.6.7 (für das Profilbild)

Backend[Bearbeiten]

Das Backend besteht aus vier grundlegenden Komponenten. Diese Komponenten sind einmal die MongoDB Datenbank, ein Websocket, die Verbindung zum MQTT-Server und ein ExpressJS Webserver. Beim Starten des Servers werden diese vier Komponenten initialisiert. Dafür wird die Verbindung zur Datenbank und zum MQTT-Server hergestellt und der Websocket und Webserver werden gestartet.

IONOS Server-Standort (Host des Backends)

Beim Start wird zusätzlich der EventHandleService eingerichtet. Dieser Service ist der "Kleber" der die Komponenten zusammenfügt und ist für die wichtigste Funktion verantwortlich, die Berechnung des Punktestands. Im EventHandleService werden die Nachrichten des Websocket und MQTT-Servers empfangen und verarbeitet. Empfängt der Server eine Nachricht im Channel "Event" des MQTT-Servers, wird aus den Informationen in der Nachricht, welche im JSON-Format gesendet werden, der neue Punktestand des Benutzers, des Geräts von dem die Nachricht gesendet wurde, berechnet. Anschließend wird die neue Rangordnung der Benutzer erstellt und über den Websocket an alle Benutzer gesendet. Zudem wird der aktuelle Punktestand des Benutzers für den ESP aufbereitet, da der ESP über MQTT nur eine begrenzte Datenmenge empfangen kann, und dann über einen eigenen MQTT-Channel an den ESP gesendet.

Die restlichen Funktionen des Servers werden durch die API, die über den Webserver erreichbar ist, abgedeckt. Über die API können Benutzer- und Gerätedaten abgerufen, angelegt und bearbeitet werden, sowie Profilbilder für Benutzer hochgeladen werden.

EventHandleService[Bearbeiten]

Der EventHandleService speichert den Punktestand des Nutzers pro Stunde. Dazu wird für jede empfangene Nachricht eines Geräts, der dazu gehörige Benutzer und der Punktestand der aktuellen Stunde aus der Datenbank geladen. Sollte für eine Stunde noch kein Punktestand existieren, wird ein neuer Eintrag erstellt und dieser wird mit den Daten des letzten Punktestands des Tages vorbefüllt. Danach kommt man zur Berechnung der Punkte anhand der neuen Informationen des Geräts und der vorliegenden Daten:

let lastUpdate = DateTime.fromJSDate(scoreEntry.lastUpdate).toUTC();
let minutesSinceLastUpdate = Math.min(Interval.fromDateTimes(lastUpdate, now).length('minutes'), 0.5);
if (event.standing) {
    scoreEntry.standingMinutes += minutesSinceLastUpdate;
    scoreEntry.standingMinutes = Number(scoreEntry.standingMinutes).toFixed(2);
}
if (event.outside) {
    scoreEntry.outsideMinutes += minutesSinceLastUpdate;
    scoreEntry.outsideMinutes = Number(scoreEntry.outsideMinutes).toFixed(2);
}

scoreEntry.steps += event.stepsSinceLastUpdate;
scoreEntry.lastUpdate = now.toMillis();

const newScore = scoreEntry.steps * 0.2 + (scoreEntry.standingMinutes * 2) + (scoreEntry.outsideMinutes * 2);
scoreEntry.score = Math.ceil(newScore);

Um den Punktestand zu berechnen wird zuerst die Zeit zwischen dem Erhalt der Nachricht und dem Erhalt der letzten Nachricht berechnet. Diese Differenz wird auf 30 Sekunden beschränkt, um bei Verlust der Internetverbindung des ESPs, die Punktzahl nicht zu verfälschen. Wenn der Benutzer seit der letzten Nachricht gestanden hat oder sich draußen aufgehalten hat, wird die Zeitdifferenz um die Anzahl der gestandenen, bzw. draußen aufgehaltenen Minuten erhöht. Danach werden die Schritte seit dem letzten Update addiert.

Der tatsächliche Punktestand setzt sich dann aus den einzelnen Komponenten wie folgt zusammen:

Anzahl an Schritten am Tag * 0.2 + gestandene Minuten am Tag * 2 + Minuten draußen am Tag * 2

Nachdem der neue Punktestand berechnet wurde, wird der Eintrag in der Datenbank gespeichert und alle Benutzer über die neue Rangordnung informiert.