TimeTracker (Arbeitszeiterfassung)

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.

Arbeitszeiterfassung
Quelle: Pixabay

Funktionsweise

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.

Verwendete Bauteile

Schaltplan & Aufbau

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.

Schaltplan des Time trackers mit dem Nano RP2040 Connect

TimeTracker-Gehäuse

Vorschau des Time tracker cubes

Der Würfel beinhaltet sowohl den Nano RP2040 Connect als auch die LEDs.

time_tracker_cube.scad

Der ausgedruckte Würfel kann nun verwendet werden, um den Nano RP2040 Connect und die LEDs mit ihren Vorwiderständern aufzunehmen:

Gedruckter Würfel mit eingebauter Elektronik
Abb.: Die LEDs und der Nano RP2040 Connect sind in dem gedruckten Würfel eingebaut, wobei die Verkabelung noch lose mit Dupont-Kabeln durchgeführt wurde.
Vorwiderstände der LEDs auf einer Platine
Abb.:Die Vorwiderstände für die LEDs wurden auf eine kleine Platine gelötet, damit sie nicht im Freiform-Aufbau an die LEDs befestigt werden mussten.

Sketch (für den Würfel)

(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 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:

Ausgabe auf der seriellen Konsole

Server-Komponente

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.

Voraussetzungen

Um die Server-Komponente betreiben zu können sind folgende Voraussetzungen notwendig:

Server-Komponente laden [4,5 kB]

Setup

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.

Oberfläche der Server-Komponente
Abb.: Auswertung der gemessenen Arbeitszeiten im Browser in der Server-Komponente.

Fertiger Prototyp des TimeTrackers

Der erste Funktionstest verlief erfolgreich, wie man an den folgenden Bildern sieht:

TimeTracker in Ruhe-/Normalposition
Abb.: TimeTracker in Ruhe-/Normalposition (Das Loch für den Mikro-USB-Stecker habe ich nachträglich in den Würfel hineingebohrt und ist nicht im 3D-Entwurf vorgesehen)
Lageänderung des TimeTrackers
Abb.: TimeTracker liegt auf der Seite und blinkt orange, um die Lageänderung anzuzeigen.

Weiterführung

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.

Fehlende Features & Ideen

zurück