Major changes: - Added Debug tab with real-time message logging and hex viewer - Implemented timed data logging ($UMTDL command) per API v1.3 - Fixed BLE message parsing to handle notifications without line endings - Added Schema 2 support (22-field timed logging packets) - Updated Maglink BLE UUIDs (fff0/fff1/fff2) - Added connection timeout (10s) to prevent hanging on unavailable devices - Added connection status display and manual push-button logging trigger - Improved retry logging with attempt numbers and delays - Added console window allocation for Windows debug output - Added BLE disconnect on app close Bug fixes: - Fixed device name filtering for Maglink (ML-* prefix) - Fixed RtkGps enum reference in DeviceScanViewModel - Fixed DebugMessage namespace (moved to Models) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Field Logger
Cross-platform .NET MAUI app (Windows, macOS, iOS, Android) for logging utility locate points from an Underground Magnetics locating receiver paired with RTK GPS positions from a Maglink (H11) RTK receiver, both over BLE.
Maps API Key: AIzaSyDhH16gF-7UN-CBsTQGfQSHNGjLC6VJ5dI
Solution layout
FieldLogger/
Models/ Device protocol models (UmLogPacket, GnssFix) and DB entities (Job, LoggedPoint)
Services/
Ble/ BleSerialClient (line-oriented GATT serial) and BleScanner (Plugin.BLE)
UmReceiverService.cs $UMPBDL protocol, PBDL schema-1 packet parsing
MaglinkService.cs $GNPOS/$GNDEV NMEA parsing + AT commands
DeviceConnectionManager.cs Auto-connect on launch, reconnect with backoff
PointLogger.cs Joins locator packets with the latest GNSS fix, saves to SQLite
Data/AppDatabase.cs sqlite-net-pcl store (jobs + points)
Sync/IMqttSyncService.cs Stub for the future MQTT job/point synchronization
ViewModels/ + Views/ Shell tabs: Home, Jobs, Map, Settings (+ DeviceScan, JobDetail)
Resources/Raw/map.html Google Maps JS page used by the Windows map view
doc/ Device protocol documentation
How it works
- First launch: the Home page redirects to the device scan page. Pick your locating
receiver (names starting with
UMRXorDT100) and, from Settings or Home, the Maglink RTK receiver. Selections are persisted. - Subsequent launches:
DeviceConnectionManagerauto-connects to both saved devices and keeps retrying/reconnecting with backoff. Devices can be changed or forgotten in Settings. - Logging: create a job (Home or Jobs tab) — it becomes the active job. When the operator
presses the log button on the UM receiver, the app receives the PBDL packet, attaches the
most recent RTK fix (if fresher than 5 s), and stores the combined record with all metadata
in SQLite (
fieldlogger.db3in app data). - Map: plots the active (or selected) job's points. Native map control on iOS/Android/macOS (Google Maps on Android, Apple Maps on iOS/macOS); Google Maps JavaScript in a WebView on Windows.
Setup required before running
- Google Maps key (Android): replace
YOUR_GOOGLE_MAPS_ANDROID_API_KEYinFieldLogger/Platforms/Android/AndroidManifest.xml(Google Cloud Console → Maps SDK for Android). - Google Maps key (Windows): create a Maps JavaScript API key and paste it into the Settings page of the app.
- Maglink GATT UUIDs: the Maglink docs describe the serial protocol but not its GATT
table.
MaglinkServicecurrently assumes the Nordic UART Service (6e400001-b5a3-f393-e0a9-e50e24dcca9e). Verify against the actual hardware (any BLE scanner app will show the service UUIDs) and update the constants at the top ofFieldLogger/Services/MaglinkService.csif they differ.
Building and running
All commands are run from the repository root. (dotnet run does not support MAUI
projects — use dotnet build -t:Run to deploy and launch instead.)
Windows
# Build
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-windows10.0.19041.0
# Run (unpackaged; WindowsPackageType=None)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-windows10.0.19041.0 -t:Run
# ...or launch the built exe directly:
.\FieldLogger\bin\Debug\net9.0-windows10.0.19041.0\win10-x64\FieldLogger.exe
Android
# Build (APK is produced under bin/Debug/net9.0-android)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-android
# Deploy + launch on the connected device or running emulator
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-android -t:Run
# If several devices/emulators are attached, pick one by adb serial (see `adb devices`)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-android -t:Run -p:AdbTarget="-s emulator-5554"
Note: BLE does not work in the Android emulator — use a physical device
(enable USB debugging, then adb devices to confirm it is attached).
iOS (requires a Mac; run these on the Mac, or from Windows via Visual Studio's paired Mac)
# Build
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-ios
# Run in the default iOS Simulator (note: the simulator has no Bluetooth)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-ios -t:Run
# Run on a specific simulator by UDID (list with: xcrun simctl list devices)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-ios -t:Run -p:_DeviceName=:v2:udid=<SIMULATOR_UDID>
# Run on a physical iPhone (requires provisioning profile / signing set up in Xcode first)
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-ios -t:Run -p:RuntimeIdentifier=ios-arm64 -p:_DeviceName=<DEVICE_UDID>
macOS (Mac Catalyst — requires a Mac)
# Build
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-maccatalyst
# Build + launch
dotnet build FieldLogger/FieldLogger.csproj -f net9.0-maccatalyst -t:Run
Visual Studio
Open FieldLogger.sln, pick the target framework/device in the debug-target dropdown,
and press F5. This is the most convenient route for iOS from Windows (via a paired Mac)
and for Android device debugging.
Roadmap
- MQTT sync (
Services/Sync/IMqttSyncService.csis the seam): receive configured jobs from the server and publish logged points.Job.RemoteId/SyncedandLoggedPoint.Syncedcolumns are already in the schema. Suggested client: MQTTnet. - The Maglink can also upload positions directly via MQTT (
AT+UPLOADDATA_*commands, seedoc/AT_UPLOADDATA_MQTT_Configuration_EN.md) if server-side ingestion is preferred.