Als ich nach einem nützlichen Projekt für den Arduino Nano RP2040 Connect suchte, bekam ich den Vorschlag, eine leicht zu handhabende Zeiterfassung für verschiedene Arbeitszeiten zu bauen.
Die Idee ist, dass man einen kleinen Gegenstand auf seinem Schreibtisch oder einem zentralen Ort der
Wohnung hat, mit dem man schnell verschiedene Arbeits-Typen zeitlich erfassen kann, indem man die
Erfassung startet und später wieder stoppt. Dies könnte man durch Taster lösen, aber ich entschied mich
die IMU (Inertial Measurement Unit) des Nano RP2040 Connect dafür zu verwenden, d.h. die Lage des Boards.
Die Ruhe- bzw. Normallage des Boards soll die Zeiterfassung stoppen. Kippt man das Board um 90° in eine
andere Lage, so wird die zeitliche Erfassung eines entsprechend vorher bestimmten Arbeits-Typs gestartet.
Bei der Drehung in Normallage wird der Vorgang wieder gestoppt. Jede dieser Änderungen wird an eine
Server-Komponente im WWW geschickt und dort gespeichert. Hier findet auch die Auswertung statt, die zunächst
nur die Summe der erfassten Arbeitszeit getrennt nach den Arbeits-Typen anzeigt.
Damit die Erfassung leicht und schnell durchgeführt werden kann, wird das Board in einen kleinen Würfel
verpackt, an dessen sichtbaren Seitenflächen (nicht unten) jeweils eine LED steckt, die durch ein kurzes
Aufblinken den Status anzeigen soll.
Wie der folgende Schaltplan zeigt, ist der Aufbau recht einfach. Es werden lediglich 5 LEDs mit entsprechenden Vorwiderständen an jeweils einen Ausgang des Boards verbunden. Der benötigte IMU-Sensor und das WiFi-Modul sind ja schon auf dem Nano RP2040 Connect vorhanden.
Der Würfel beinhaltet sowohl den Nano RP2040 Connect als auch die LEDs.
time_tracker_cube.scadDer ausgedruckte Würfel kann nun verwendet werden, um den Nano RP2040 Connect und die LEDs mit ihren Vorwiderständern aufzunehmen:
(Die grundlegende Arbeitsweise und Programmierung ist in dem Beitrag
Arduino Nano RP2040 Connect beschrieben.)
Der folgende Sketch ist das gesamte Programm, welches zur Erfassung und Übertragung der Arbeitszeit an
den Server notwendig ist. Hierbei werden die beiden Libraries
Arduino_LSM6DSOX und
WiFiNINA verwendet.
Vor der Benutzung müssen noch einige Werte auf die eigenen Bedürfnisse eingestellt werden:
Ich hatte auf unterschiedlichen Rechnern (speziell: Debian-Linux) Probleme den Sketch erfolgreich zum Kompilieren zu bringen. Es gab immer wieder Fehler mit der WiFiNINA-Library. Ich konnte die Probleme nur lösen, indem ich auf die Libary WiFiNINA_Generic ausgewichen bin.
WIFI_SSID
und WIFI_PASSWORD
sind die Zugangsdaten des eigenen, lokalen WLAN-Netzwerks,
welches vom Nano RP2040 Connect zur Übertragung der Daten benutzt wird.
SERVER_PROT
SERVER_HOST
und SERVER_PORT
entsprechend angepasst werden. Für den SERVER_PSK
wird eine zufällige
Zeichen- und Nummernfolge (=starkes Passwort) gewählt.
LED_PINS
beinhaltet die Pin-Nummers der verwendeten LEDs.// WiFi settings:
const char* WIFI_SSID = "xxxxx";
const char* WIFI_PASSWORD = "xxxxx";
// Server settings:
const char* SERVER_PROT = "http://";
const char* SERVER_HOST = "localhost";
const uint16_t SERVER_PORT = 80;
const char* SERVER_PSK = "xxxxx";
// Pin with index=0 should be the "Idle-LED"
const byte LED_PINS[5] = {2, 3, 4, 5, 6};
#include <Arduino_LSM6DSOX.h>
#include <WiFiNINA.h>
#define lmillis() ((long)millis())
// remove/comment the following line to disable serial debug console
#define _DEBUG_MODE
#ifdef _DEBUG_MODE
#define DEBUG_INIT(baudSpeed) Serial.begin(baudSpeed)
#define DEBUG_PRINTLN(value) Serial.println(value)
#define DEBUG_PRINT(value) Serial.print(value)
#else
#define DEBUG_INIT(baudSpeed)
#define DEBUG_PRINTLN(value)
#define DEBUG_PRINT(value)
#endif
const int INTERVAL_MEASURE = 5000;
short cubePos = -1, lastCubePos = -1;
WiFiClient client;
void setup()
{
DEBUG_INIT(9600);
if (!IMU.begin()) {
DEBUG_PRINTLN("Error initializing IMU!");
while (true) {
digitalWrite(LED_PINS[0], !digitalRead(LED_PINS[0]));
delay(500);
}
}
for (byte i = 0; i < 5; i++) {
pinMode(LED_PINS[i], OUTPUT);
digitalWrite(LED_PINS[i], LOW);
}
resetLeds();
}
void loop()
{
static long lastMeasure = lmillis() + INTERVAL_MEASURE;
if (lmillis() - lastMeasure >= 0) {
DEBUG_PRINTLN("Measuring IMU/acceleration...");
lastMeasure = lmillis() + INTERVAL_MEASURE;
if (IMU.accelerationAvailable()) {
cubePos = getCubePosition();
if (cubePos != -1 && lastCubePos != cubePos) {
DEBUG_PRINTLN("Cube location has changed to: " + String(cubePos));
blinkCurrentLed();
sendHttpRequest();
lastCubePos = cubePos;
}
}
}
}
void connectWiFi()
{
if (WiFi.status() == WL_CONNECTED) {
return;
}
DEBUG_PRINT("Connecting to WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
DEBUG_PRINT(".");
}
DEBUG_PRINTLN("[connected]");
}
void sendHttpRequest()
{
byte action = 0, typeId = cubePos;
if (cubePos == 0 && lastCubePos == -1) {
return;
}
if (cubePos == 0 && lastCubePos > 0) {
action = 1;
typeId = lastCubePos;
}
connectWiFi();
String auth = "/?psk=" + String(SERVER_PSK);
String param = "&t=" + String(typeId) + "&a=" + String(action);
String url = String(SERVER_PROT) + String(SERVER_HOST) + ":" + String(SERVER_PORT) + auth + param;
DEBUG_PRINTLN("Connecting server: " + url + "...");
if (client.connect(SERVER_HOST, SERVER_PORT)) {
client.println("GET " + url + " HTTP/1.1");
client.println("Connection: close");
client.println();
} else {
DEBUG_PRINTLN("Connection to server failed!");
}
client.stop();
}
short getCubePosition()
{
float x = 0.0, y = 0.0, z = 0.0;
const static short pos[5][3] = {
{ 0, 0, 1},
{ 0, -1, 0},
{ 0, 1, 0},
{ -1, 0, 0},
{ 1, 0, 0},
};
IMU.readAcceleration(x, y, z);
for (byte i = 0; i < 5; i++) {
if ((short)round(x) == pos[i][0] &&
(short)round(y) == pos[i][1] &&
(short)round(z) == pos[i][2]) {
return i;
}
}
return -1;
}
void resetLeds()
{
for (byte i = 0; i < 5; i++) {
digitalWrite(LED_PINS[i], LOW);
}
}
void blinkCurrentLed()
{
if (cubePos < 0) {
return;
}
for (byte i = 0; i < 10; i++) {
digitalWrite(LED_PINS[cubePos], i % 2);
delay(150);
}
digitalWrite(LED_PINS[cubePos], LOW);
}
Wird der Sketch ausgeführt, so erscheinen in der seriellen Konsole der Arduino-IDE folgende, ähnliche Ausgaben:
Die Server-Komponente besteht primär aus einem PHP-Script, welches sowohl die Zeitdaten empfängt und in einer Datenbank (MySQL) speichert, als auch eine Statistik über die getätigten Arbeitszeiten anzeigt.
Um die Server-Komponente betreiben zu können sind folgende Voraussetzungen notwendig:
/var/www/timetracker
)
htdocs
verweist, hier ein Beispiel
für nginx (hier ist auch gleich die Konfiguration für PHP-FPM enthalten):
server {
server_name timetracker.example.com;
listen 80;
root /var/www/timetracker/htdocs;
index server.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors off;
fastcgi_buffer_size 16k;
fastcgi_buffers 4 16k;
}
}
database/structure.sql
die benötigten
Tabellen und den Benutzer. Diese Daten müssen ebenfalls in der config.php abgeändert werden.
PSK
in denselben Wert abgeändert, der zuvor im Sketch als SERVER_PSK
gewählt wurde.
$typeIds
sind die vier verschiedenen, gültigen Arbeitszeit-Typen hinterlegt. Es sind momentan
nicht mehr als 4 Typen implementiert.
$colors
festgelegt.
Nach dem Einrichten sollte nun unter der URL, die man im Webserver konfiguriert hat und mit Angabe des gültigen psk
(z.B. "http://timetracker.example.com/?psk=xxxxx") die Oberfläche der Server-Komponente erscheinen.
Der erste Funktionstest verlief erfolgreich, wie man an den folgenden Bildern sieht:
Das dieses Projekt nur als proof-of-concept gedacht ist, fehlen etliche Features, die das Ganze zu einem brauchbaren Produkt machen. Ein ähnliches, im Handel erhältliches, Produkt ist der Timeular Tracker: Zeiterfassungswürfel, welcher allerdings über Bluetooth die Zeitdaten erfasst und dann in einer eigenen App auswertet.