Skip to content

Repository files navigation

FTW app

Your home's energy, on the phone.

Native iOS (SwiftUI) and Android (Jetpack Compose). Shared logic is Kotlin Multiplatform: pairing, passkeys, Noise, the relay, the session. The box at home is the record. This app is a cached projection of it. The cloud is blind.

Not a wrap of the web app. Same protocol, same QR, same relay, same RP ID (app.ftw.energy).

Product direction and contributions

Follow FTW's shared vision and roadmap. The target experience needs few routine choices, honest live feedback and reliable daily charging. The native status below is a dated record and its verification gates still apply; the vision does not declare new native features shipped.

Sourceful develops this app. PRs are welcome, preferably based on issues. Share a short Markdown proposal or a focused fix with relevant test evidence. See CONTRIBUTING.md.

Shape

SwiftUI / Compose
        │
        ▼
   shared (KMP)  — enrollment, vault, Noise IK, frames, session, relay
        │
        ▼
  wss://relay.ftw.energy   (encrypted)
        │
        ▼
     FTW box

Two taps: scan the QR on the box, Face ID / biometrics, the house.

Status (2026-08-22)

V1 is Pair + Now. That is on main as of 2026-08-22. Not a wrap of the web app. Not Flutter, not React Native.

In the apps today

  • Scan or paste a v2 pairing QR (https://app.ftw.energy/p#v2.…).
  • One passkey prompt at enroll. RP ID app.ftw.energy. PRF salt ftw.prf.v1.vault.
  • Noise_IK_25519_ChaChaPoly_SHA256 to the box through wss://relay.ftw.energy.
  • Now shows headline plus grid / solar / battery / house from frozen field ids.
  • Vault, site and last readings live in iOS Keychain / Android EncryptedSharedPreferences. Cold start paints from cache, then reconnects without Face ID. Forget wipes the store.

Proven here

Check Result
./gradlew :shared:jvmTest Green
Live box e2e (127.0.0.1:18080 + production relay) hello_ok + snapshot, phase streaming
iOS Simulator (iPhone 17, iOS 26.5) Built and launched
Android emulator FTW_Phone (API 35 ARM64) APK installed, Pair shown twice

Passkey PRF cannot run on the JVM. Live e2e uses a local wrapping key for the ceremony and the real Noise / relay / box path. The Android emulator has no camera feed — paste the pairing link.

Not v1 (do not start these next)

Energy, History, Plan, EV, commands, escrow restore, spoken codes, LAN, WebRTC, push, App Store / Play listing.

Known holes

  • srcState should follow the Now fields' srcId in the dict, not every driver on the site.
  • Wrap key is raw PRF bytes, not the web app's HKDF. A native vault will not open in the PWA, and the other way around.
  • PasskeyHost.enroll from Kotlin still blocks. The UIs call the async ceremony and skip that path.
  • Field ids in Explanation.kt are still hand-written; they should come from protocol/registry.yaml.

Tests

JDK 21.

export JAVA_HOME="$(brew --prefix openjdk@21)/libexec/openjdk.jdk/Contents/Home"
./gradlew :shared:jvmTest

That suite covers enrollment URLs, Cacophony Noise IK, hello/sub CBOR matching the box's interop hex, 512-byte lane 0 frames, the vault, and a session that turns hello_ok + snap into Now state.

E2E against FTW

Start a box (make dev in srcfl/ftw, or go run ./cmd/ftw with app_link.enabled: true). The box joins wss://relay.ftw.energy — that origin is fixed on the box, so the client meets it there.

export FTW_LIVE_BOX=127.0.0.1:8080   # or 18080 if the API port moved
export FTW_LIVE_RELAY=wss://relay.ftw.energy
./scripts/e2e-ftw.sh

The test mints POST /api/app-link/pairing with {"role":"owner"}, runs the shipped connectToSite path (Noise IK, pairing code in handshake message 1, prologue ftw.session.v1: + box static), and asserts hello_ok plus a snapshot that includes the frozen field ids.

Native apps

iOS: open iosApp/iosApp.xcodeproj. SwiftUI Pair (camera QR + paste) and Now. Xcode 16+, iOS 18 for passkey PRF. A Run Script build phase compiles the Shared framework with ./gradlew :shared:embedAndSignAppleFrameworkForXcode.

Android: ./gradlew :androidApp:assembleDebug (minSdk 28). Pair uses CameraX

  • ML Kit for the QR. Passkeys go through Credential Manager.

Emulator (AVD FTW_Phone, API 35 ARM64 Google APIs):

export ANDROID_HOME="$HOME/Android/sdk"
"$ANDROID_HOME/emulator/emulator" -avd FTW_Phone
adb install -r androidApp/build/outputs/apk/debug/androidApp-debug.apk
adb shell am start -n energy.ftw.app/.MainActivity

RP ID app.ftw.energy. PRF salt ftw.prf.v1.vault. Reading uses a local wrapping copy so Now paints without a passkey prompt.

Layout

Path What
shared/ KMP: identity, crypto, protocol, relay, session
androidApp/ Compose UI
iosApp/ SwiftUI UI
protocol/registry.yaml Names shared with the box
scripts/e2e-ftw.sh Live box e2e

Licence

AGPL-3.0-only with the Energyplan combination permission. See LICENSE, LICENSING.md and NOTICE. Earlier Apache-licensed versions retain their earlier grants.

About

FTW native app — SwiftUI + Compose, KMP shared client for local-first home energy.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages