Initial minimal Connect IQ scaffold
Verifies the toolchain: builds and runs on fenix7 simulator and Forerunner 265 hardware. No features yet — empty view rendering "Einsatzprotokoll" on black background. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
6f56c337f7
9 changed files with 266 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
bin/
|
||||
*.prg
|
||||
*.prg.debug.xml
|
||||
*.iq
|
||||
.DS_Store
|
||||
18
manifest.xml
Normal file
18
manifest.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
|
||||
<iq:application entry="EinsatzprotokollApp"
|
||||
id="1a4e06bd8e5b484f96c291bde9937b3a"
|
||||
launcherIcon="@Drawables.LauncherIcon"
|
||||
minApiLevel="3.2.0"
|
||||
name="@Strings.AppName"
|
||||
type="watch-app">
|
||||
<iq:products>
|
||||
<iq:product id="fenix7"/>
|
||||
<iq:product id="fr265"/>
|
||||
</iq:products>
|
||||
<iq:permissions/>
|
||||
<iq:languages>
|
||||
<iq:language>deu</iq:language>
|
||||
</iq:languages>
|
||||
<iq:barrels/>
|
||||
</iq:application>
|
||||
</iq:manifest>
|
||||
1
monkey.jungle
Normal file
1
monkey.jungle
Normal file
|
|
@ -0,0 +1 @@
|
|||
project.manifest = manifest.xml
|
||||
192
plan.md
Normal file
192
plan.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# App: "Einsatzprotokoll"
|
||||
|
||||
Eine App für Polizisten, die es ermöglicht die Uhrzeit und den Standort von Ereignissen bequem mit ihrer Smartwatch zu protokollieren.
|
||||
## Problem und Nutzungsszenario
|
||||
Als Polizist muss man häufig Uhrzeiten und Standorte von Ereignissen für den späteren Bericht protokollieren. Beispielsweise das Eintreffen am Einsatzort, das Erfolgen einer Festnahme oder die Wegstrecke bei Verfolgungsfahrten.
|
||||
|
||||
Dafür muss man stehts den Notizblock zücken und ggfs. noch die aktuelle Adresse ermitteln. Das kostet Zeit und nerven in einem Stressigen Einsatz.
|
||||
|
||||
Da viele Polizisten ohnehin eine Garmin Smartwatch haben, ermöglicht diese App das einfache Protokollieren von Zeiten und Standorten ohne das umständliche notieren oder im nachinein Rekonstruieren.
|
||||
|
||||
## Zielgruppe
|
||||
Die Zielgruppe sind zunächst nur Deutsche Polizisten, da ich selbst einer bin und die Anforderungen hier am besten einschätzen kann.
|
||||
Später könnte die App international oder auch auf andere Einsatzkräfte (z.B Feuerwehr) ausgeweitet werden
|
||||
|
||||
## Anforderungen
|
||||
1. Ein Eintrag soll immer das aktuelle Datum, Die Uhrzeit, Die Koordinaten und die nächstgelegene Adresse beinhalten.
|
||||
2. Ein Eintrag soll extrem einfach und schnell über wenige Klicks an der Uhr zu erstellen sein.
|
||||
3. Die App soll vorgefertigte Arten von Einträgen beinhalten um sauber trennen zu können, was wozu gehört. (Zum Beispiel: Festnahme, Einsatzbeginn, Einsatzende)
|
||||
4. Die Historie soll einfach über die Uhr zugänglich sein. Alte Events werden nach 7 Tagen automatisch gelöscht, sodass der Speicher der Uhr niemals voll wird.
|
||||
5. Bedienung soll rein über die Knöpfe möglich sein. Nicht über Touchscreen, damit man sie auch mit Handschuhen bedienen kann
|
||||
|
||||
## Design
|
||||
|
||||
# Farben
|
||||
Der Hintergrund der gesamten App ist schwarz um auf AMOLED-Displays Akku zu sparen: 000000
|
||||
Die Schrift ist Weiß: FFFFFF
|
||||
Die Icons haben knallige, bunte, unterschiedliche Farben, damit sie schnell Identifizierbar sind.
|
||||
### Öffnen der App
|
||||
Die App wird über einen Glance geöffnet. Der Nutzer kann das Widget so personalisiert in der Liste seiner Glances hinzufügen und verschieben.
|
||||
|
||||
Die Glances erreicht man vom Normalen Watch Face über den "DOWN" Button.
|
||||
### Glance
|
||||
Das Glance enthält das App-Logo auf der linken Seite und als Überschrift den App Namen "Einsatzprotokoll"
|
||||
|
||||
Darunter wird der letzte Eintrag angezeigt:
|
||||
Beispiel: "Festnahme: 11.04.2026 12:34"
|
||||
|
||||
Beim Klick auf das Glance öffnet sich die eigentliche Menü der App
|
||||
|
||||
Darüber Hinaus kann das App Menü auch auf Shortcuts gelegt werden, um sie noch schneller zu öffnen. Zum Beispiel auf "lange DOWN".
|
||||
|
||||
### Menü
|
||||
Das Menü der App ähnelt vom Design und der Bedienung dem "Controls Menu", also dem Menü das man über das lange Drücken auf den "LIGHT" Knopf erreicht und Systemeinstellungen, wie Handy Orten, Notfall oder Ausschalten beinhaltet.
|
||||
|
||||
Es soll also ebenso ein Kreis mit 10 Icons sein, welche sich über die "UP" und "DOWN" Knöpfe rotieren lassen. Das Icon auf Höhe des "START/STOP" Knopfes soll durch einen Dezenten visuellen Hinweis hervorgehoben werden. Mit einem Klick auf "START/STOP" wird es dann ausgewählt.
|
||||
|
||||
Das Menü merkt sich, den zuletzt ausgewählten Eintrag und ist beim nächsten öffnen wieder so rotiert, wie beim letzten verlassen.
|
||||
|
||||
In der Mitte des Auswahlringes befinden sich Informationen zum aktuell ausgewählten Element als einfacher, zentrierter Text.
|
||||
|
||||
Die Menüpunkte sind folgende:
|
||||
|
||||
| Nummer | Name | Icon |
|
||||
| ------ | ----------------------- | ------------------------ |
|
||||
| 1 | Verlauf | Liste mit Bulletpoints |
|
||||
| 2 | Allgemeines Ereignis | Plus-Symbol |
|
||||
| 3 | Einsatzbeginn | Play-Symbol |
|
||||
| 4 | Einsatzende | Häkchen im Kreis |
|
||||
| 5 | Eintreffen | Pin mit Pfeil nach unten |
|
||||
| 6 | Festnahme | Handschellen |
|
||||
| 7 | Zwanganwendung | Geballte Faust |
|
||||
| 8 | Beweismittel gesichert | Kamera |
|
||||
| 9 | Sichtung | Fernglas |
|
||||
| 10 | Letzten Eintrag löschen | Mülleimer |
|
||||
|
||||
Die Icons sollten knallige Farben haben.
|
||||
### Verlauf
|
||||
Der Verlaufsbildschirm ist in 3 Horizontal geteilte Bereiche Unterteilt.
|
||||
- Oberer Bereich: 15% der gesamten Bildschirmhöhe
|
||||
- Zeigt nur das Icon des chronologisch vorherigen Eintrages mit einem "Pfeil hoch" Icon.
|
||||
- Mittlerer Bereich: 70% der gesamten Bildschirmhöhe
|
||||
- In den mittleren 70% sind folgende Informationen:
|
||||
- Eintragsart
|
||||
- Datum + Uhrzeit
|
||||
- Koordinaten oder die Adresse (Adresse nur wenn sie nicht weiter als 20 Meter von der den Koordinaten entfernt ist)
|
||||
- Unterer Bereich: 15% der gesamten Bildschirmhöhe
|
||||
- Zeigt nur das Icon des chronologisch nächsten Eintrages mit einem "Pfeil runter" Icon.
|
||||
|
||||
Erst wenn der User den Eintrag hier geöffnet hat wird die Adresse zu den Koordinaten ermittelt. So wird während der Erstellung die Fehleranfälligkeit minimiert.
|
||||
|
||||
Falls kein Handy verbunden ist soll statt der Adresse "Verbinde Handy für Adresse" angezeigt werden.
|
||||
### Erstellung
|
||||
Nachdem im Menü ein Ereignis mit dem Druck auf "START/STOP" ausgewählt wurde, wird zunächst ein Ladescreen angezeigt während die GPS Koordinaten bestimmt werden. Dieser ist wie folgt aufgebaut.
|
||||
|
||||
Am runden Rand der Uhr zirkuliert ein weißer Balken der symbolisiert, dass der Ladevorgang läuft. In der Mitte steht "Standort wird bestimmt".
|
||||
|
||||
Wenn der Vorgang abgeschlossen ist, verschwindet der Ladebalken und es wird ein grüner Haken für 2.5 Sekunden angezeigt. Außerdem vibriert die Uhr kurz. Dannach schließt sich die App und der User ist wieder auf dem normalen Watchface.
|
||||
|
||||
Sollte der Vorgang aus irgendeinem Grund nicht erfolgreich sein, wird statt dem Haken für 5 Sekunden eine Fehlermeldung angezeigt. Diese sollte auf Deutsch und möglichst wenig technisch sein. Außerdem vibriert die Uhr drei Mal.
|
||||
|
||||
Die GPS Accuracy sollte möglichst auf mindestens 5m genau sein. Nach 10 Sekunden Ladezeit sollte jedoch einfach das aktuell beste Ergebnis gespeichert werden. Wenn kein GPS Signal gefunden werden konnte, werden die Koordinaten auf None gesetzt.
|
||||
|
||||
Die Uhr Speichert für jedes Ereignis folgende Werte:
|
||||
- Ereignisname (String)
|
||||
- Timestamp
|
||||
- Koordinaten
|
||||
- GPS Accuracy
|
||||
### Löschen
|
||||
Wenn der Nutzer im Menü "Löschen" auswählt beginnt ein roter, runder Balken auf höhe des "START/STOP" Knopfes um die Uhr zu laufen, während die Taste gedrückt gehalten wird. Der User muss die Taste 2.5 Sekunden gedrückt halten um das Löschen auszulösen. So wird verhindert, dass ausversehen Einträge gelöscht werden.
|
||||
## Technische Anforderungen
|
||||
|
||||
### Tech Stack
|
||||
- Programmiersprache: Monkey C
|
||||
- Framework/SDK: Garmin Connect IQ (CIQ) SDK
|
||||
|
||||
### Adressen Auflösung
|
||||
Die Adressen werden über meine selbst gehostete Photon Instanz unter "gps.moritz.run" aufgelöst.
|
||||
|
||||
**Beispiel:**
|
||||
```
|
||||
GET https://gps.moritz.run/reverse?lon=13.517740504968456&lat=52.52408130696999
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"osm_type": "N",
|
||||
"osm_id": 11286301180,
|
||||
"osm_key": "place",
|
||||
"osm_value": "house",
|
||||
"type": "house",
|
||||
"housenumber": "59A",
|
||||
"street": "Rhinstraße",
|
||||
"district": "Lichtenberg",
|
||||
"city": "Berlin",
|
||||
"country": "Deutschland",
|
||||
"postcode": "10315",
|
||||
"countrycode": "DE"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
13.517589,
|
||||
52.5239594
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Die Implementierung der eigenen Instanz steht noch aus. Zum testen und für die Entwicklung sollte dieser öffentliche Endpunkt verwendet werden.
|
||||
|
||||
```
|
||||
https://photon.komoot.io/reverse?lon=13.38&lat=52.51
|
||||
```
|
||||
### Logging
|
||||
Zur Verfolgbarkeit von Abstürzen speichert die Uhr Fehlermeldungen nach abstürzen. Auch diese werden jedoch nur 7 Tage vorgehalten.
|
||||
### Coding Anforderungen
|
||||
1. App auf Deutsch. Code auf Englisch.
|
||||
2. Nur Kommentare an kritischen Stellen.
|
||||
3. Moderne, objektorientierte Programmierung
|
||||
4. Saubere Github Commits für Versionshistorie
|
||||
5. Einstellungen, Schwellwerte, Zeiten für Animationen, Strings usw. sollten in zentralen Dateien gespeichert, und entsprechend kommentiert werden, sodass sie leicht angepasst werden können.
|
||||
### Hardware
|
||||
Die App soll auf allen modernen Garmin Modellen Funktionieren. Das Layout muss also relativ zur Bildschirmgröße berechnet werden.
|
||||
|
||||
### Icon-Quellen
|
||||
Die Icons werde ich selbst erstellen. Für die Entwicklung genügen Platzhalter Icons.
|
||||
|
||||
### Probleme
|
||||
1. Nächstgelegene Adresse von Koordinaten ermitteln.
|
||||
- Geht nur über eine HTTP API die über die Bluetooth Verbindung zum Handy abgerufen werden muss. Hier steht eine finale Entscheidung aus.
|
||||
2. Datenschutz und Dienstrecht
|
||||
- Um Rechtliche Probleme zu minimieren syncronisiert die App keine Nutzerdaten mit der Garmin Cloud. Alle Einträge bleiben nur Lokal auf der Uhr. Lediglich die Koordinaten müssen das Gerät zur Adressermittlung verlassen.
|
||||
- Außerdem wird ein Dienstrechtlicher Disclaimer in die App-Beschreibung eingebaut.
|
||||
|
||||
## Ideen und Next steps:
|
||||
Sobald der Prototyp steht wird näher Über diese Features nachgedacht.
|
||||
1. Asynchrones Ermitteln der GPS-Koordinaten im Hintergrund.
|
||||
- Pro
|
||||
- Der Nutzer kann die Uhr gleich weiter nutzen. Er muss nicht auf ein GPS-Signal warten.
|
||||
- Contra
|
||||
- Der Nutzer bekommt kein Feedback ob die Signalfindung erfolgreich war
|
||||
- Es ist schwerer zu implementieren und fehleranfälliger
|
||||
2. Im Menü ein weiterer Punkt "Einstellungen"
|
||||
- Hier könnte der Nutzer die Menüanordnung personalisieren
|
||||
- Außerdem könnte er eigene Ereignisarten erstellen
|
||||
- Er könnte Zeiten und variablen verändern
|
||||
- Pro
|
||||
- Mehr Personalisierungsmöglichkeiten
|
||||
- Breitere Anwendungsbereiche
|
||||
- Contra
|
||||
- Mehr Komplexität für den Nutzer: Er könnte überfordert sein
|
||||
- Schwierige Implementierung auf einem kleinen Smartwatchdisplay
|
||||
- Großer Aufwand bei Programmierung, Wartung und Design
|
||||
3. Neuer "Verfolgungsmodus"
|
||||
- Bei Verfolgungsjagden muss man später die genaue Wegstrecke rekonstruieren. In diesem Modus zeichnet die Uhr konstant die Wegstrecke auf. Außerdem wird auf der Uhr die aktuellen Straße auf der man Fährt angezeigt und die Fahrtrichtung um dies über Funk durchzugeben.
|
||||
3
resources/drawables/drawables.xml
Normal file
3
resources/drawables/drawables.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<drawables>
|
||||
<bitmap id="LauncherIcon" filename="launcher_icon.svg"/>
|
||||
</drawables>
|
||||
7
resources/drawables/launcher_icon.svg
Executable file
7
resources/drawables/launcher_icon.svg
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" fill="#000000"/>
|
||||
<path d="M3 4C3 3.44772 3.44772 3 4 3H10C10.5523 3 11 3.44772 11 4V10C11 10.5523 10.5523 11 10 11H4C3.44772 11 3 10.5523 3 10V4Z" fill="#F4F4F4"/>
|
||||
<path d="M3 14C3 13.4477 3.44772 13 4 13H10C10.5523 13 11 13.4477 11 14V20C11 20.5523 10.5523 21 10 21H4C3.44772 21 3 20.5523 3 20V14Z" fill="#F4F4F4"/>
|
||||
<path d="M13 4C13 3.44772 13.4477 3 14 3H20C20.5523 3 21 3.44772 21 4V10C21 10.5523 20.5523 11 20 11H14C13.4477 11 13 10.5523 13 10V4Z" fill="#F4F4F4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.3 14.3V19.7H19.7V14.3H14.3ZM14 13C13.4477 13 13 13.4477 13 14V20C13 20.5523 13.4477 21 14 21H20C20.5523 21 21 20.5523 21 20V14C21 13.4477 20.5523 13 20 13H14Z" fill="#F4F4F4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 832 B |
3
resources/strings/strings.xml
Normal file
3
resources/strings/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<strings>
|
||||
<string id="AppName">Einsatzprotokoll</string>
|
||||
</strings>
|
||||
18
source/EinsatzprotokollApp.mc
Normal file
18
source/EinsatzprotokollApp.mc
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import Toybox.Application;
|
||||
import Toybox.Lang;
|
||||
import Toybox.WatchUi;
|
||||
|
||||
class EinsatzprotokollApp extends Application.AppBase {
|
||||
|
||||
function initialize() {
|
||||
AppBase.initialize();
|
||||
}
|
||||
|
||||
function onStart(state as Dictionary?) as Void {}
|
||||
|
||||
function onStop(state as Dictionary?) as Void {}
|
||||
|
||||
function getInitialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
|
||||
return [ new EinsatzprotokollView() ];
|
||||
}
|
||||
}
|
||||
19
source/EinsatzprotokollView.mc
Normal file
19
source/EinsatzprotokollView.mc
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import Toybox.Graphics;
|
||||
import Toybox.Lang;
|
||||
import Toybox.WatchUi;
|
||||
|
||||
class EinsatzprotokollView extends WatchUi.View {
|
||||
|
||||
function initialize() {
|
||||
View.initialize();
|
||||
}
|
||||
|
||||
function onUpdate(dc as Dc) as Void {
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
|
||||
dc.clear();
|
||||
var cx = dc.getWidth() / 2;
|
||||
var cy = dc.getHeight() / 2;
|
||||
dc.drawText(cx, cy, Graphics.FONT_MEDIUM, "Einsatzprotokoll",
|
||||
Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue