Skip to content

Commit 0303327

Browse files
eriedclaude
andcommitted
Merge multi-wheel-support: V12 + P6 wheels + virtual simulator + verified P6 telemetry
Brings in the multi-wheel adapter rewrite (Phase 1/2A/2B/3/7-prep), V12 HS/HT support, the virtual-wheel debug simulator, the InMotion P6 protocol work verified against the labelled FINAL+P6-again captures (signed reverse speed, torque off 12-13, PWM off 14-15, MOS+motor temps from off 28/30, drive flag off 80, mode/DRL/auto-headlight opcodes), and the BLE capture guide. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents a28cfd7 + 225ed21 commit 0303327

46 files changed

Lines changed: 5949 additions & 250 deletions

Some content is hidden

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

.github/workflows/branch-apk.yml

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: Branch APK
2+
3+
# On every push to a non-main branch, build a debug APK and replace the
4+
# rolling pre-release tagged `branch-<name>` with the freshly built artifact.
5+
# Testers always get the latest commit by visiting the same release URL.
6+
#
7+
# Pull-request runs build the APK as a workflow artifact (downloadable from
8+
# the Actions tab for ~90 days) but do not touch the release list, so PRs
9+
# from forks can't push tags.
10+
11+
on:
12+
push:
13+
branches-ignore:
14+
- main
15+
tags-ignore:
16+
- "**"
17+
pull_request:
18+
branches:
19+
- "**"
20+
workflow_dispatch:
21+
22+
permissions:
23+
contents: write
24+
25+
jobs:
26+
build:
27+
runs-on: ubuntu-latest
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
with:
33+
fetch-depth: 0 # full history so we can produce a "since main" log
34+
35+
- name: Set up JDK 17
36+
uses: actions/setup-java@v4
37+
with:
38+
distribution: temurin
39+
java-version: "17"
40+
41+
- name: Set up Gradle
42+
uses: gradle/actions/setup-gradle@v3
43+
44+
- name: Make gradlew executable
45+
run: chmod +x ./gradlew
46+
47+
- name: Build debug APK
48+
run: ./gradlew :app:assembleDebug --no-daemon --stacktrace
49+
env:
50+
GRADLE_OPTS: -Xmx4g -Dorg.gradle.jvmargs=-Xmx4g
51+
52+
- name: Compute build metadata
53+
id: meta
54+
if: github.event_name != 'pull_request'
55+
run: |
56+
BRANCH="${GITHUB_REF##*/}"
57+
SHORT_SHA="$(git rev-parse --short HEAD)"
58+
TAG="branch-${BRANCH}"
59+
APK_NAME="EUC-Planet-${BRANCH}-${SHORT_SHA}.apk"
60+
61+
# Pull the per-branch description if BRANCH.md exists, otherwise
62+
# fall back to a generic notice. This becomes the release body header.
63+
if [ -f BRANCH.md ]; then
64+
cp BRANCH.md /tmp/branch-notes.md
65+
else
66+
cat > /tmp/branch-notes.md <<EOF
67+
# ${BRANCH}
68+
69+
No \`BRANCH.md\` found at repo root. Add one to describe what this
70+
branch is for and who should test it.
71+
EOF
72+
fi
73+
74+
# Recent commits since branching from main, capped to keep the
75+
# release body readable on big branches.
76+
git log --pretty=format:'- %h %s' main..HEAD | head -50 > /tmp/commit-log.md || true
77+
78+
{
79+
echo "**Pre-release from branch \`${BRANCH}\`** — automatic build at \`${SHORT_SHA}\`."
80+
echo
81+
echo "This is a CI-built debug APK. It can't update an existing Play Store install in place — uninstall the Play version first to install this build."
82+
echo
83+
cat /tmp/branch-notes.md
84+
echo
85+
echo "## Recent commits"
86+
echo
87+
cat /tmp/commit-log.md
88+
} > /tmp/release-body.md
89+
90+
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
91+
echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT"
92+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
93+
echo "apk_name=${APK_NAME}" >> "$GITHUB_OUTPUT"
94+
95+
- name: Rename APK for release
96+
if: github.event_name != 'pull_request'
97+
run: |
98+
cp app/build/outputs/apk/debug/app-debug.apk \
99+
"/tmp/${{ steps.meta.outputs.apk_name }}"
100+
101+
- name: Upload APK as workflow artifact
102+
uses: actions/upload-artifact@v4
103+
with:
104+
name: app-debug-${{ github.run_id }}
105+
path: app/build/outputs/apk/debug/app-debug.apk
106+
retention-days: 90
107+
108+
- name: Strip prior APKs from rolling release
109+
if: github.event_name != 'pull_request'
110+
env:
111+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
112+
run: |
113+
# action-gh-release attaches new files but doesn't remove the previous
114+
# commit's APK; without this step the rolling pre-release accumulates
115+
# one APK per push. Delete every existing .apk on the tag before the
116+
# next step uploads the freshly built one. Tolerate the tag not
117+
# existing yet (first run on a brand-new branch).
118+
tag="${{ steps.meta.outputs.tag }}"
119+
if gh release view "$tag" >/dev/null 2>&1; then
120+
gh release view "$tag" --json assets \
121+
--jq '.assets[] | select(.name | endswith(".apk")) | .name' \
122+
| while read -r asset; do
123+
[ -n "$asset" ] && gh release delete-asset "$tag" "$asset" --yes || true
124+
done
125+
fi
126+
127+
- name: Replace rolling pre-release with new APK
128+
if: github.event_name != 'pull_request'
129+
uses: softprops/action-gh-release@v2
130+
with:
131+
tag_name: ${{ steps.meta.outputs.tag }}
132+
name: ${{ steps.meta.outputs.tag }} (${{ steps.meta.outputs.short_sha }})
133+
body_path: /tmp/release-body.md
134+
prerelease: true
135+
# Replace the existing tag/release on every push so testers can
136+
# always find the latest at the same URL.
137+
make_latest: "false"
138+
files: /tmp/${{ steps.meta.outputs.apk_name }}

.gitignore

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,13 @@ 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+
30+
# Stray build-output paths that have leaked into the repo before
31+
**/eucplanet_*.png
32+
**/build/eucplanet_*

BRANCH.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# multi-wheel-support
2+
3+
## What this branch adds
4+
5+
Foundation work for supporting wheels beyond the V14. The protocol layer
6+
splits into per-family adapters and the dashboard learns to surface its
7+
state for any of them. Concretely shipped here:
8+
9+
- **InMotion V2 model registry** covering V11 / V11Y / V12 HS / HT / Pro /
10+
V12S / V13 / V13 Pro / V14 50GB / V14 50S / V9 / P6. The wheel reports
11+
its model ID on connect and the registry maps it to the right command
12+
variant (horn opcode, max-speed packet shape, etc.).
13+
- **P6 connect path + parser pass.** The scan now lists `P6-XXXXXXXX`
14+
peripherals; the adapter switches to the extended-routing-only command
15+
set when it sees that name. Voltage, discharge current, real per-pack
16+
battery percent, total mileage, and current tiltback all parse from
17+
real-hardware captures. The control plane now uses the P6-specific
18+
opcodes the InMotion app sends — light becomes a 3-byte mirrored
19+
packet, horn drops the V14 sound id, setMaxSpeed sends a single
20+
uint16 followed by a flash-commit. Speed, PWM, and per-sensor
21+
temperatures still wait on a labelled riding capture
22+
(`docs/BLE_CAPTURE_GUIDE.md`).
23+
- **Wheel simulator** in the connect screen. Two virtual wheels (V14 and
24+
P6) feed canned BLE responses through the real adapter pipeline, so the
25+
whole UI works without hardware. Useful for translation, layout, and
26+
capability-gating work.
27+
- **Experimental banner** on the dashboard for any wheel that isn't a
28+
verified V14. Tapping it opens a prefilled GitHub Issue (`Wheel report`
29+
template) with the connected wheel and firmware already filled in.
30+
- **Wheel report template** at `.github/ISSUE_TEMPLATE/wheel_report.yml`
31+
with structured fields for wheel model, firmware, status, what works,
32+
details, and phone OS.
33+
34+
## Who should test this
35+
36+
- **V14 owners**: confirm that nothing changed for you. The banner stays
37+
hidden, the dashboard reads the same values, horn / light / lock /
38+
safety mode still work.
39+
- **P6 owners**: connecting now works and the control plane is wired up
40+
properly. Verify horn beeps, light toggles on/off, and that adjusting
41+
the tiltback slider actually changes the wheel's speed cap (the
42+
flash-commit packet is meant to persist it past sleep). Voltage,
43+
per-pack battery, and total mileage should match the InMotion app.
44+
Speed, PWM, and temperatures still read zero — those need a labelled
45+
riding capture to confirm offsets.
46+
- **Owners of any other InMotion wheel** (V11, V12, V13, V9): try
47+
connecting. Telemetry decoding outside the V14 family is unverified,
48+
so expect wrong values. If anything works or fails, tap the orange
49+
banner to fire off a wheel report.
50+
- **No-wheel testers**: in the connect screen, scroll past the BLE list to
51+
the "Simulate a wheel" section. Tap V14 or P6 to drive the dashboard
52+
with synthetic telemetry.
53+
54+
## Known limits
55+
56+
- **P6 real-hardware support is preliminary.** Connecting to a real P6
57+
now works (the BLE name `P6-XXXXXXXX` puts the adapter on the
58+
extended-routing protocol the wheel actually speaks) and the dashboard
59+
shows live voltage and discharge current plus a rough battery estimate
60+
from the pack voltage. The remaining telemetry — speed, PWM, motor
61+
temperature, trip distance, the per-pack battery split — is still
62+
unmapped because the data block's byte layout differs from V14 and we
63+
only have parked captures so far. Help us pin those offsets by recording
64+
a labeled session (`docs/BLE_CAPTURE_GUIDE.md`) while riding.
65+
- **P6 settings, locking, and safety-mode max-speed control are
66+
disabled** until the matching control packets are reverse-engineered.
67+
The UI doesn't gate them yet, so tapping those buttons on a P6 is a
68+
no-op — that should be obvious from the wheel not responding.
69+
- **KingSong, Veteran, Begode/Gotway, InMotion V1 family (V8 / V10)**:
70+
their adapters aren't built yet, so connecting won't work. They appear
71+
in the wheel-report dropdown for users to manually pick if they want
72+
to file feedback.
73+
74+
## Feedback
75+
76+
Tap the orange banner on the dashboard for a prefilled GitHub issue, or
77+
open one manually at
78+
https://github.com/eried/eucplanet/issues/new?template=wheel_report.yml
-130 KB
Binary file not shown.

0 commit comments

Comments
 (0)