Skip to content

Instantly share code, notes, and snippets.

@StephaneDelcroix
Created March 7, 2026 11:59
Show Gist options
  • Select an option

  • Save StephaneDelcroix/2a5c3c75bcf11184d7f870264c1b5eec to your computer and use it in GitHub Desktop.

Select an option

Save StephaneDelcroix/2a5c3c75bcf11184d7f870264c1b5eec to your computer and use it in GitHub Desktop.
BrewBuddy MAUI Session State - Jura E8 Coffee Machine Controller

BrewBuddy Session State

Project Overview

A .NET MAUI iOS app ("BrewBuddy") to control a Jura E8 coffee machine via Bluetooth, track coffee beans, brew history, and preferences, with AI-powered recommendations using Microsoft.Extensions.AI and AppleIntelligenceChatClient. Fun coffee-themed color scheme.

Architecture

  • .NET 10 MAUI targeting net10.0-ios and net10.0-maccatalyst
  • CommunityToolkit.Mvvm for [ObservableProperty] and [RelayCommand]
  • SQLite via sqlite-net-pcl for local persistence
  • Plugin.BLE v3 for Bluetooth LE
  • Microsoft.Extensions.AI v10.3.0 for IChatClient abstraction
  • Microsoft.Maui.Essentials.AI (nightly) for AppleIntelligenceChatClient (iOS 26+)
  • NuGet source for nightly: https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10/nuget/v3/index.json

Project Structure

BrewBuddy/
├── Models/
│   ├── CoffeeBean.cs
│   ├── BrewHistory.cs
│   ├── CoffeePreference.cs
│   ├── MachineStatus.cs
│   ├── DrinkOption.cs          # Has ProductCode + DefaultWaterMl for BlueFrog protocol
│   └── ChatEntry.cs
├── Services/
│   ├── DatabaseService.cs      # SQLite CRUD + GetProfileSummaryAsync for AI
│   ├── JuraBtService.cs        # BlueFrog BLE protocol (rewritten from Jutta-Proto)
│   └── CoffeeAdvisor.cs        # MEAI chat with debug logging
├── ViewModels/
│   ├── DashboardViewModel.cs   # Connect, brew, ConnectionLog property
│   ├── BeansViewModel.cs
│   ├── HistoryViewModel.cs
│   └── AdvisorViewModel.cs     # Debug info banner
├── Views/
│   ├── DashboardPage.xaml(.cs) # Hero header, connection card, BT log, brew grid
│   ├── BeansPage.xaml(.cs)
│   ├── HistoryPage.xaml(.cs)
│   └── AdvisorPage.xaml(.cs)   # Chat UI with debug banner
├── Converters/Converters.cs    # BoolToColor, InverseBool, PercentToWidth, BoolToString, StringNotEmpty
├── Resources/
│   ├── Styles/CoffeeColors.xaml  # Espresso #3C1518, DarkRoast #69140E, Caramel #A44200, Crema #F2E8CF, Latte #F5F0E8, SteamedMilk #E8DCC8
│   ├── Styles/CoffeeStyles.xaml  # CardFrame, PageTitle, PrimaryButton, DrinkButton, etc.
│   ├── Images/tab_dashboard.png  # PNG tab icons (Pillow-generated, black on transparent)
│   ├── Images/tab_beans.png
│   ├── Images/tab_history.png
│   ├── Images/tab_ai.png
│   ├── AppIcon/appicon.svg + appiconfg.svg
│   └── Splash/splash.svg
├── Platforms/iOS/
│   ├── AppDelegate.cs          # ContinueUserActivity for Siri
│   ├── SiriIntentDonor.cs      # NSUserActivity donations
│   ├── SiriIntentHandler.cs    # Activity continuation handler
│   ├── EdgeToEdgeHandler.cs    # PageHandler mapper (responder chain approach)
│   ├── Entitlements.plist      # Siri entitlement REMOVED (wildcard profile)
│   └── Info.plist              # BLE + Siri permissions
├── App.xaml                    # Coffee resource dictionaries
├── AppShell.xaml               # 4-tab layout, NavBarIsVisible=False, PNG icons
├── MauiProgram.cs              # Full DI, AI client selection, EdgeToEdge, NoOpChatClient
├── nuget.config                # dotnet10-nightly feed
└── BrewBuddy.csproj            # iOS targets, packages, signing, MAUIAI0001 suppression

Key Technical Details

Jura BlueFrog BLE Protocol (from https://github.com/Jutta-Proto/protocol-bt-cpp)

  • Device advertises as "TT214H BlueFrog" or "TT214H BlueProg" — match with StartsWith("TT") or Contains("BlueProg/BlueFrog")
  • Encryption key extracted from manufacturer advertisement data byte 0
  • Nibble-shuffle cipher (symmetric) — same function encrypts/decrypts:
    • Lookup tables: Numbers1 = [14,4,3,2,1,13,8,11,6,15,12,7,10,5,0,9], Numbers2 = [10,6,13,12,14,11,1,9,15,7,0,5,3,2,4,8]
    • Each byte split into nibbles, shuffled with key nibbles
  • Heartbeat every 8s to P Mode characteristic (0x5a401529) — encode [key, 0x7F, 0x80]
  • Service UUID: 5a401523-ab2e-2548-c435-08c300000710
  • Characteristics:
    • Machine Status: 5a401524 (read/notify, encoded)
    • Start Product: 5a401525 (write, encoded) — brew commands
    • Product Progress: 5a401527 (notify, encoded)
    • P Mode: 5a401529 (write, encoded) — heartbeat
    • Barista Mode: 5a401530 (write, encoded) — lock/unlock
    • About Machine: 5a401531 (read, NOT encoded)
    • Stats Command: 5a401533 (write, encoded)
    • Stats Data: 5a401534 (read, encoded)
  • Brew command format: [key, productCode, 0x00, strength(1-8), waterSec, 0x00, 0x00, temp(01=normal/02=high), padding..., key]
  • Status bits: byte1 bit0=tray missing, bit1=water empty
  • Characteristic write: must check Properties for Write vs WriteWithoutResponse and set WriteType accordingly

Apple Intelligence Integration

  • Package: Microsoft.Maui.Essentials.AI version 10.0.50-ci.* from dotnet10-nightly
  • Provides AppleIntelligenceChatClient — implements IChatClient on-device via FoundationModels
  • Guarded by OperatingSystem.IsIOSVersionAtLeast(26) runtime check
  • Falls back to OpenAI (if API key in Preferences) then NoOpChatClient
  • Suppress MAUIAI0001 via <NoWarn> in csproj
  • Works on iOS 26+ simulator (confirmed on 26.3)
  • Does NOT work on older iOS (no FoundationModels)

Code Signing (Physical Device)

  • Signing identity: "Apple Development: stephane@delcroix.org (8XS436J752)"
  • Team ID: 8DGK8L344W
  • Build: dotnet build -f net10.0-ios -r ios-arm64 -p:EnableAutomaticSigning=true -p:CodesignKey="Apple Development" -p:DevelopmentTeam=8DGK8L344W
  • Deploy: xcrun devicectl device install app --device D1DB8E7B-0E4A-5976-9DA3-711A89FF7783 <path>
  • Launch: xcrun devicectl device process launch --device D1DB8E7B-0E4A-5976-9DA3-711A89FF7783 com.companyname.brewbuddy
  • Siri entitlement removed from Entitlements.plist (wildcard profile doesn't support it)

Devices

  • iPhone 15 Pro "sdx" UDID: D1DB8E7B-0E4A-5976-9DA3-711A89FF7783
  • iPhone 17 Pro simulator (iOS 26.3) UDID: AA261DAB-BFF9-4FE2-A35D-94D92790556B

Edge-to-Edge Layout

  • All 4 pages have ios:Page.UseSafeArea="False" (xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls")
  • Headers use Padding="20,60,20,24" to push content below status bar while extending espresso background to screen top
  • EdgeToEdgeHandler.cs uses PageHandler mapper walking responder chain to find UIViewController

Tab Bar Icons

  • SVG icons failed on physical device (iOS template rendering issues)
  • Switched to Pillow-generated PNG icons (60x60, black on transparent)
  • tab_dashboard.png (coffee cup), tab_beans.png (beans), tab_history.png (clock), tab_ai.png (sparkle)
  • Shell.TabBarForegroundColor=Caramel, Shell.TabBarUnselectedColor=SteamedMilk

MAUI 10 API Changes

  • DisplayAlertDisplayAlertAsync (old is obsolete)
  • DisplayActionSheetDisplayActionSheetAsync
  • AddDebug() needs using Microsoft.Extensions.Logging;
  • Multi-byte emoji (☕) can't be used with new string(char, count) — use string.Concat(Enumerable.Repeat())

Appium Automation

  • Script: /Users/sde/.copilot/installed-plugins/maui-copilot-plugins/appium-automation/skills/appium-automation/scripts/automate.py
  • Must --server-stop between sessions if app was reinstalled
  • Usage: python3 "$SCRIPT" --platform ios --app-id com.companyname.brewbuddy --screenshot /tmp/out.png

What's Done ✅

  • Full MAUI project scaffold with all packages
  • All data models (with ProductCode/DefaultWaterMl for BlueFrog)
  • SQLite database service with CRUD + analytics
  • Jura BLE service rewritten with proper BlueFrog protocol (nibble-shuffle cipher, heartbeat, per-characteristic handling)
  • MEAI coffee advisor with debug logging
  • Coffee theme (colors + styles)
  • All 4 pages with XAML UI (ios:Page.UseSafeArea="False" on all)
  • All 4 ViewModels with commands
  • AppShell tab navigation with PNG icons
  • Siri NSUserActivity donations + handler
  • AppleIntelligenceChatClient integration (works on iOS 26.3 sim!)
  • App icon and splash screen
  • Debug diagnostics for AI (backend type, latency, response length)
  • ConnectionLog property on DashboardViewModel for BT debug
  • StringNotEmpty converter for connection log visibility
  • Deployed to both iPhone 15 Pro and iPhone 17 Pro sim (26.3)

What's Broken / TODO 🔧

  1. EMOJI RENDERING: All emojis show as ? on iOS 26.3 simulator. Need to investigate — may be a simulator font issue or an iOS 26 regression. Need to check physical device too. Options: replace emojis with text/SF Symbols, or check if it's sim-only.
  2. BLE not yet tested end-to-end: The BlueFrog protocol rewrite (cipher, heartbeat, per-characteristic) compiles but hasn't been tested against the real Jura E8 machine yet.
  3. Edge-to-edge verification: UseSafeArea=False added to all pages but not visually confirmed on physical device after rebuild.
  4. Siri: Entitlement removed due to wildcard profile. Needs proper provisioning profile with Siri capability.
  5. Settings page: No UI for configuring OpenAI API key (currently only via Preferences API).
  6. Stats parsing: ReadTotalProductCountAsync written but not wired into UI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment