Skip to content

Commit 833cab8

Browse files
committed
Merge main into external-gps: bring RaceBox branch up to v0.6.3
# Conflicts: # .github/workflows/branch-apk.yml # BRANCH.md # app/src/main/java/com/eried/eucplanet/data/db/AppDatabase.kt # app/src/main/java/com/eried/eucplanet/data/model/AppSettings.kt # app/src/main/java/com/eried/eucplanet/ui/dashboard/DashboardScreen.kt # app/src/main/java/com/eried/eucplanet/ui/dashboard/DashboardViewModel.kt # app/src/main/java/com/eried/eucplanet/ui/recording/TripDetailScreen.kt # app/src/main/java/com/eried/eucplanet/ui/settings/SettingsScreen.kt
2 parents 7416547 + 620579b commit 833cab8

201 files changed

Lines changed: 28664 additions & 856 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/app_bug.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: App bug
2+
description: "Something in the app is wrong, regardless of what wheel you have"
3+
title: "[App bug] "
4+
labels: ["app-bug"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Use this for bugs that aren't tied to a specific wheel: layout glitches,
10+
crashes, settings that don't save, voice not playing, watch issues,
11+
backup problems, etc. If the bug only happens with a specific wheel,
12+
please use the **Wheel report** template instead.
13+
14+
- type: dropdown
15+
id: area
16+
attributes:
17+
label: Where in the app?
18+
description: Pick the closest match.
19+
options:
20+
- Dashboard
21+
- Settings
22+
- Trip recording
23+
- Alarms
24+
- Service Mode
25+
- Wear OS watch app
26+
- Cloud backup / sync
27+
- Flic / volume buttons
28+
- Other (describe below)
29+
validations:
30+
required: true
31+
32+
- type: input
33+
id: app_version
34+
attributes:
35+
label: EUC Planet app version
36+
description: Visible at the bottom of the dashboard.
37+
placeholder: e.g. 0.3.2
38+
39+
- type: input
40+
id: phone
41+
attributes:
42+
label: Phone model + Android version
43+
description: Helps a lot for crashes, layout, BLE, and audio bugs.
44+
placeholder: e.g. Pixel 7 / Android 14
45+
46+
- type: textarea
47+
id: steps
48+
attributes:
49+
label: Steps to reproduce
50+
description: What were you doing when the bug happened? The more specific, the faster we can fix it.
51+
placeholder: |
52+
1. Open Settings
53+
2. Tap "Backup settings & trips"
54+
3. Switch the cloud folder to OneDrive
55+
4. App freezes for 5 seconds, no error shown
56+
validations:
57+
required: true
58+
59+
- type: textarea
60+
id: expected
61+
attributes:
62+
label: What did you expect vs what happened?
63+
placeholder: |
64+
Expected: cloud folder switches and a confirmation toast.
65+
Actual: app freezes, then nothing visibly changes.
66+
validations:
67+
required: true
68+
69+
- type: textarea
70+
id: extras
71+
attributes:
72+
label: Screenshots / video / log
73+
description: |
74+
Optional but very useful. For crashes you can grab a stack trace from About > Crash logs.
75+
For BLE / protocol issues the Service Mode log (long-press the app icon in About) is gold:
76+
tap Attach to bundle App config / Wheel state / Trips summary into the log, then Share.
77+
placeholder: Drag images or paste log text here
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: Wheel report
2+
description: "Report behavior on your wheel: works, broken, or partially working"
3+
title: "[Wheel report] "
4+
labels: ["wheel-report"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Thanks for helping! Tell us what wheel you have and what's working or broken.
10+
The more detail, the faster we can fix things.
11+
12+
- type: dropdown
13+
id: wheel
14+
attributes:
15+
label: Wheel
16+
description: Pick the closest match. Use "Other" for anything not listed.
17+
options:
18+
- InMotion V14 (50GB)
19+
- InMotion V14 (50S)
20+
- InMotion V13
21+
- InMotion V13 Pro
22+
- InMotion V12 HS
23+
- InMotion V12 HT
24+
- InMotion V12 Pro
25+
- InMotion V12S
26+
- InMotion V11
27+
- InMotion V11Y
28+
- InMotion V10F
29+
- InMotion V10
30+
- InMotion V8 / V8F
31+
- InMotion V9
32+
- InMotion P6
33+
- KingSong S18
34+
- KingSong S18L
35+
- KingSong S20 Eagle
36+
- KingSong S22 Eagle
37+
- KingSong KS-16 / KS-18 (other)
38+
- Veteran Lynx
39+
- Veteran Sherman / Sherman S
40+
- Veteran Patton
41+
- Begode / Gotway (any)
42+
- Other (please describe in details)
43+
validations:
44+
required: true
45+
46+
- type: input
47+
id: firmware
48+
attributes:
49+
label: Wheel firmware version
50+
description: If known. The app shows this on the dashboard once connected.
51+
placeholder: e.g. Main 1.2.3 / Driver 4.5.6
52+
53+
- type: input
54+
id: app_version
55+
attributes:
56+
label: EUC Planet app version
57+
description: Visible at the bottom of the dashboard.
58+
placeholder: e.g. 0.3.0
59+
60+
- type: dropdown
61+
id: status
62+
attributes:
63+
label: Overall status
64+
options:
65+
- Works perfectly
66+
- Connects but some features missing or wrong
67+
- Connects but telemetry shows wrong values
68+
- Doesn't connect at all
69+
- App crashes
70+
- Other (describe below)
71+
validations:
72+
required: true
73+
74+
- type: checkboxes
75+
id: working
76+
attributes:
77+
label: What works
78+
description: Tick everything that worked correctly during your test.
79+
options:
80+
- label: App found and connected to the wheel
81+
- label: Telemetry (speed, voltage, battery) shows correct values
82+
- label: Horn
83+
- label: Lights toggle
84+
- label: Lock / unlock
85+
- label: Safety mode / max-speed limit
86+
- label: Volume control on the wheel
87+
- label: Trip recording (CSV export)
88+
- label: Voice announcements
89+
- label: Flic button actions
90+
91+
- type: textarea
92+
id: details
93+
attributes:
94+
label: Details
95+
description: What's broken? Steps to reproduce? Screenshots? Anything else useful for debugging.
96+
placeholder: |
97+
Examples:
98+
- "Speed shows 250 km/h when I'm standing still"
99+
- "Horn works but light button does nothing"
100+
- "App crashes when I open Settings → Alarms"
101+
validations:
102+
required: true
103+
104+
- type: input
105+
id: phone
106+
attributes:
107+
label: Phone model + OS version
108+
description: Optional but helps for crashes or BLE issues.
109+
placeholder: e.g. Pixel 7 / Android 14

.github/workflows/branch-apk.yml

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,16 @@ jobs:
4444
- name: Make gradlew executable
4545
run: chmod +x ./gradlew
4646

47-
- name: Build debug APK
48-
run: ./gradlew :app:assembleDebug --no-daemon --stacktrace
47+
- name: Build debug APKs
48+
# Build the phone APK on every branch. Build the wear APK too when
49+
# the branch has a wear/ module (currently only wear-os-watch-ultra),
50+
# so testers can grab both halves from the same rolling release.
51+
run: |
52+
targets=":app:assembleDebug"
53+
if [ -f wear/build.gradle.kts ]; then
54+
targets="$targets :wear:assembleDebug"
55+
fi
56+
./gradlew $targets --no-daemon --stacktrace
4957
env:
5058
GRADLE_OPTS: -Xmx4g -Dorg.gradle.jvmargs=-Xmx4g
5159

@@ -56,7 +64,8 @@ jobs:
5664
BRANCH="${GITHUB_REF##*/}"
5765
SHORT_SHA="$(git rev-parse --short HEAD)"
5866
TAG="branch-${BRANCH}"
59-
APK_NAME="EUC-Planet-${BRANCH}-${SHORT_SHA}.apk"
67+
APK_NAME="EUC-Planet-${BRANCH}-${SHORT_SHA}-phone.apk"
68+
WEAR_APK_NAME="EUC-Planet-${BRANCH}-${SHORT_SHA}-wear_os.apk"
6069
6170
# Pull the per-branch description if BRANCH.md exists, otherwise
6271
# fall back to a generic notice. This becomes the release body header.
@@ -91,20 +100,34 @@ jobs:
91100
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
92101
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
93102
echo "apk_name=${APK_NAME}" >> "$GITHUB_OUTPUT"
103+
echo "wear_apk_name=${WEAR_APK_NAME}" >> "$GITHUB_OUTPUT"
94104
95-
- name: Rename APK for release
105+
- name: Rename APKs for release
96106
if: github.event_name != 'pull_request'
97107
run: |
98108
cp app/build/outputs/apk/debug/app-debug.apk \
99109
"/tmp/${{ steps.meta.outputs.apk_name }}"
110+
# Wear APK only exists on branches that have a wear/ module.
111+
if [ -f wear/build/outputs/apk/debug/wear-debug.apk ]; then
112+
cp wear/build/outputs/apk/debug/wear-debug.apk \
113+
"/tmp/${{ steps.meta.outputs.wear_apk_name }}"
114+
fi
100115
101-
- name: Upload APK as workflow artifact
116+
- name: Upload phone APK as workflow artifact
102117
uses: actions/upload-artifact@v4
103118
with:
104119
name: app-debug-${{ github.run_id }}
105120
path: app/build/outputs/apk/debug/app-debug.apk
106121
retention-days: 90
107122

123+
- name: Upload wear APK as workflow artifact
124+
if: hashFiles('wear/build/outputs/apk/debug/wear-debug.apk') != ''
125+
uses: actions/upload-artifact@v4
126+
with:
127+
name: wear-debug-${{ github.run_id }}
128+
path: wear/build/outputs/apk/debug/wear-debug.apk
129+
retention-days: 90
130+
108131
- name: Strip prior APKs from rolling release
109132
if: github.event_name != 'pull_request'
110133
env:
@@ -133,6 +156,7 @@ jobs:
133156
body_path: /tmp/release-body.md
134157
prerelease: true
135158
# Replace the existing tag/release on every push so testers can
136-
# always find the latest at the same URL.
159+
# always find the latest at the same URL. The glob picks up both
160+
# the phone APK and (if the branch has wear/) the watch APK.
137161
make_latest: "false"
138-
files: /tmp/${{ steps.meta.outputs.apk_name }}
162+
files: /tmp/EUC-Planet-${{ steps.meta.outputs.branch }}-${{ steps.meta.outputs.short_sha }}*.apk

.gitignore

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,15 @@ keystore.properties
2020
release/
2121
/.playwright-mcp
2222
/.claude
23-
/.claude
23+
24+
# Scratch files (analysis, screenshots, ad-hoc)
25+
.tmp_*
26+
*.btsnoop
27+
*.cfa
28+
*.log
29+
deletelater.html
30+
delete.html
31+
32+
# Stray build-output paths that have leaked into the repo before
33+
**/eucplanet_*.png
34+
**/build/eucplanet_*
-130 KB
Binary file not shown.

README.md

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
# EUC Planet
22

3-
An open-source Android companion app for the **InMotion V14** electric unicycle.
4-
5-
Built because every other EUC app either asks for a monthly subscription, ships a crummy UI, loses connection, or locks useful features behind paywalls. This one is free, does the things I actually want while riding, and doesn't phone home.
6-
7-
> **Scope note:** right now this supports the **InMotion V14 only** (V2 BLE protocol, `Adventure-*` devices). The architecture is protocol-agnostic, so other InMotion wheels, and eventually other brands, can be added later.
3+
[![Latest release](https://img.shields.io/github/v/release/eried/eucplanet)](https://github.com/eried/eucplanet/releases)
4+
[![License: MIT](https://img.shields.io/github/license/eried/eucplanet)](LICENSE)
5+
[![Google Play](https://img.shields.io/badge/Google_Play-EUC_Planet-3DDC84?logo=googleplay&logoColor=white)](https://play.google.com/store/apps/details?id=com.eried.eucplanet)
6+
[![Telegram](https://img.shields.io/badge/Telegram-EUCPlanetApp-26A5E4?logo=telegram&logoColor=white)](https://t.me/EUCPlanetApp)
7+
[![Downloads](https://img.shields.io/github/downloads/eried/eucplanet/total)](https://github.com/eried/eucplanet/releases)
8+
9+
An open-source Android companion app for **electric unicycles**, with a Wear OS companion for Galaxy Watch Ultra and other Wear OS 5+ watches.
10+
11+
Built because every other EUC app either asks for a monthly subscription, ships a crummy UI, loses connection, or locks useful features behind paywalls. This one is free, open source, no ads, no tracking, no upselling, no subscriptions, no telemetry phoned home, no nonsense.
12+
13+
> **Wheel support tiers:**
14+
>
15+
> | Tier | Wheels | What it means |
16+
> |---|---|---|
17+
> | **Verified** | InMotion V14 50GB / 50S, P6 | Author's daily wheel + telemetry/controls confirmed against labelled real-hardware captures |
18+
> | **Preliminary** | InMotion V12 HS / HT / Pro | Parser exists, not yet author-tested |
19+
> | **Preview** | KingSong S22 / S20 / S19 / S18 / S16 / KS-14/16/18 / F18P / F22P | Telemetry + commands implemented from the public protocol; needs a real-hardware tester |
20+
> | **Preview** | Begode/Gotway Master / Master Pro / T3 / T4 / RS / RS-HT / EX / EX.N / EX2 / MSP / MSX / Hero / XWay / Mten4 / Mten5 / MCM5 | same; high-voltage tiltback (>100 km/h) handled since v0.6.2 |
21+
> | **Preview** | Veteran Sherman / Sherman S / Sherman Max / Patton / Lynx / Abrams | same |
22+
> | **Preview** | InMotion V1 family: V5 / V8 / V8F / V8S / V10 / V10F / V10S / V10T / V10FT / L6 / Lively / Glide 3 | same |
23+
> | **Preview** | Ninebot Z6 / Z10, plus legacy One E / E+ / S2 / Mini (read-only) | same; Ninebot Z uses the documented XOR keystream encryption |
24+
> | **Experimental** | InMotion V11, V13, V9 | In the model registry; please file a wheel report if you try them |
25+
> | Not yet | Onewheel and other non-EUC vehicles | Different protocol family, out of scope |
26+
>
27+
> Preview wheels are implemented from the spec docs in [`docs/protocols/`](docs/protocols/) (KingSong, Begode, Veteran, InMotion V1, Ninebot) but have not yet been tested against the actual hardware. If your wheel is in this tier and you can ride it, please connect through the app and file a wheel report via the orange in-app banner — telemetry verification is the fastest path to upgrading the tier.
28+
>
29+
> Want to help add a wheel that's not on the list? See the [BLE capture guide](docs/BLE_CAPTURE_GUIDE.md) — record one labelled riding session and we can usually map it in a single pass.
830
931
---
1032

@@ -50,14 +72,16 @@ Built because every other EUC app either asks for a monthly subscription, ships
5072
### Integrations
5173
- **Flic 2 buttons**: pair up to two buttons.
5274
- **Volume keys**: use the phone's physical volume up/down for extra shortcuts.
53-
-
75+
- **Wear OS companion**: full-bleed speed dial, three batteries (wheel/phone/watch), accent and unit settings synced from the phone, horn + light remote controls. Tested on Galaxy Watch Ultra; works on any Wear OS 5+ watch.
76+
5477
---
5578

5679
## Requirements
5780

5881
- Android 10 (API 29) or newer.
59-
- InMotion V14 (firmware running the V2 BLE protocol, device advertises as `Adventure-…`).
82+
- A supported wheel (see the support-tier table at the top of this README). The V14 and P6 are the most thoroughly tested today; KingSong / Begode / Veteran / InMotion V1 / Ninebot wheels work in preview and need community testing to graduate to "verified".
6083
- Bluetooth + location permissions (location is required by Android for BLE scanning).
84+
- Wear OS companion (optional): Wear OS 5 or newer, paired through the Wear OS by Google app.
6185

6286
## Install
6387

@@ -82,7 +106,7 @@ I got tired of:
82106

83107
## Contributing
84108

85-
The BLE protocol layer is separate from the UI, so adding a new wheel is mostly: write a new protocol encoder/decoder and a new parser, then wire it into `WheelRepository`. PRs welcome. Bug reports go on [GitHub Issues](../../issues); feature ideas and votes live on the community board at [ideas.ried.no/euc-planet](https://ideas.ried.no/euc-planet).
109+
The BLE protocol layer is separate from the UI: each brand family has its own `WheelAdapter` implementation in [`app/src/main/java/com/eried/eucplanet/ble/`](app/src/main/java/com/eried/eucplanet/ble/), and `CompositeWheelAdapter` routes connect-time by the BLE-advertised name. Spec docs for each family live under [`docs/protocols/`](docs/protocols/). To add a new wheel: write a parser + commands + adapter that implement `WheelAdapter`, register it in `CompositeWheelAdapter`, add the BLE-name pattern to `BleScanner`. The fastest path is the [BLE capture guide](docs/BLE_CAPTURE_GUIDE.md) — record a single labelled riding session and we can usually map a new wheel in one pass. PRs welcome. Bug reports go on [GitHub Issues](../../issues); feature ideas and votes live on the community board at [ideas.ried.no/euc-planet](https://ideas.ried.no/euc-planet).
86110

87111
## License
88112

app/build.gradle.kts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ android {
2727
applicationId = "com.eried.eucplanet"
2828
minSdk = 29
2929
targetSdk = 35
30-
versionCode = 7
31-
versionName = "0.3.0"
30+
versionCode = 42
31+
versionName = "0.6.3"
3232

3333
val buildStamp = SimpleDateFormat("yyMMdd.HHmm")
3434
.apply { timeZone = TimeZone.getTimeZone("UTC") }
@@ -132,4 +132,11 @@ dependencies {
132132

133133
// Drag-to-reorder for settings lists
134134
implementation(libs.reorderable)
135+
136+
// ExoPlayer — gapless looping for the multi-section engine sound composition
137+
implementation(libs.media3.exoplayer)
138+
implementation(libs.media3.common)
139+
140+
// Wear OS Data Layer (talks to the wear/ companion module on paired watches)
141+
implementation(libs.play.services.wearable)
135142
}

app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@
6161
android:foregroundServiceType="connectedDevice|location"
6262
android:exported="false" />
6363

64+
<!-- Wear OS Data Layer: receives /euc/control messages from a paired watch. -->
65+
<service
66+
android:name=".wear.PhoneWearListenerService"
67+
android:exported="true">
68+
<intent-filter>
69+
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
70+
<data android:scheme="wear" android:host="*" android:pathPrefix="/euc/" />
71+
</intent-filter>
72+
</service>
73+
6474
<provider
6575
android:name="androidx.core.content.FileProvider"
6676
android:authorities="${applicationId}.fileprovider"

0 commit comments

Comments
 (0)