Skip to content

Commit acc5b30

Browse files
author
Nitin Kumar
committed
Replace prototype with integrated CrowMotion hardware
1 parent e48b5b3 commit acc5b30

43 files changed

Lines changed: 948 additions & 1239 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/build.yml

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,26 @@ on:
1313
pull_request:
1414

1515
jobs:
16-
tracker:
16+
release-contract:
1717
runs-on: ubuntu-latest
18-
strategy:
19-
fail-fast: false
20-
matrix:
21-
target: [esp32c3, esp32s3, esp32c6]
2218
steps:
2319
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
20+
- name: Run release contract tests
21+
run: python3 -m unittest discover -s tests -v
2422

25-
- name: Build tracker (${{ matrix.target }})
26-
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
27-
with:
28-
esp_idf_version: v5.5.4
29-
target: ${{ matrix.target }}
30-
31-
- name: Upload firmware artifact
32-
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
33-
with:
34-
name: crowmotion-${{ matrix.target }}
35-
path: build/crowmotion.bin
36-
37-
bridge:
23+
tracker:
3824
runs-on: ubuntu-latest
3925
steps:
4026
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
4127

42-
- name: Build CrowLink bridge (esp32c6)
28+
- name: Build CrowMotion (ESP32-C3 Super Mini)
4329
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
4430
with:
4531
esp_idf_version: v5.5.4
46-
target: esp32c6
47-
path: bridge
32+
target: esp32c3
4833

4934
- name: Upload firmware artifact
5035
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
5136
with:
52-
name: crowlink-esp32c6
53-
path: bridge/build/crowlink.bin
37+
name: crowmotion-esp32c3
38+
path: build/crowmotion.bin

.github/workflows/release-ota.yml

Lines changed: 25 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,16 @@
1-
# Publish a firmware release to the OTA update server and browser flasher.
2-
#
3-
# Triggered ONLY when a GitHub release is published (not on pushes or tag
4-
# creation). Builds the tracker for every supported target plus the bridge
5-
# from the tagged source, verifies the tag matches version.h, attaches the
6-
# app binaries to the release, and deploys the full site to Cloudflare Pages
7-
# (updates.crowpilot.in):
8-
#
9-
# / browser flasher (ESP Web Tools)
10-
# /flash/manifest.json legacy manifest (C3 tracker + C6 bridge)
11-
# /flash/crowmotion.json CrowMotion manifest (C3/S3/C6 builds)
12-
# /flash/crowlink.json CrowLink manifest (C6)
13-
# /flash/*-merged*.bin complete images (bootloader+partitions+app)
14-
# /crowmotion/latest.json legacy OTA manifest (fielded C3 devices)
15-
# /crowmotion/latest-<target>.json per-target OTA manifests (0.1.3+)
16-
# /crowmotion/crowmotion-X.Y.Z[-<target>].bin OTA app images
17-
#
18-
# Each Pages deploy replaces the site, so only the newest binaries live on
19-
# the update host; all historical binaries remain on GitHub Releases.
1+
# Build and publish a signed ESP32-C3 CrowMotion release.
202
name: release-ota
213

224
on:
235
release:
246
types: [published]
257

268
permissions:
27-
contents: write # upload the built binaries to the release
9+
contents: write
2810

2911
jobs:
3012
publish:
3113
runs-on: ubuntu-latest
32-
# The release tag is attacker-influenceable text; never interpolate it
33-
# into a run: script directly (that is a shell-injection vector). Bind it
34-
# to an env var, validate the format once, and reference "$TAG" quoted.
3514
env:
3615
TAG: ${{ github.event.release.tag_name }}
3716
steps:
@@ -46,219 +25,61 @@ jobs:
4625
with:
4726
ref: ${{ github.event.release.tag_name }}
4827

49-
- name: Verify tag matches version.h
28+
- name: Verify tag matches firmware version
5029
run: |
5130
VER="${TAG#v}"
5231
SRC=$(sed -n 's/#define CROWMOTION_VERSION "\(.*\)"/\1/p' main/version.h)
53-
echo "release tag: $VER version.h: $SRC"
54-
if [ "$VER" != "$SRC" ]; then
55-
echo "::error::version.h ($SRC) does not match the release tag ($VER). Bump version.h before releasing."
56-
exit 1
57-
fi
32+
test "$VER" = "$SRC" || { echo "::error::version.h ($SRC) does not match $VER"; exit 1; }
5833
59-
- name: Sign-enable the tracker build (M13)
34+
- name: Enable signed OTA images
6035
env:
6136
OTA_SIGNING_KEY_B64: ${{ secrets.OTA_SIGNING_KEY_B64 }}
6237
run: |
63-
if [ -z "$OTA_SIGNING_KEY_B64" ]; then
64-
echo "::error::OTA_SIGNING_KEY_B64 secret is missing; cannot sign the release"; exit 1
65-
fi
38+
test -n "$OTA_SIGNING_KEY_B64" || { echo "::error::OTA_SIGNING_KEY_B64 is missing"; exit 1; }
6639
echo "$OTA_SIGNING_KEY_B64" | base64 -d > ota_signing_key.pem
67-
# Layer the CI-only signing overlay onto the shared defaults so every
68-
# tracker target build produces an RSA-3072 signed image. Local/dev
69-
# builds do not run this, so they stay unsigned and need no key. Only
70-
# affects the root (tracker) project; the bridge uses bridge/sdkconfig.
7140
cat sdkconfig.ci.signed >> sdkconfig.defaults
72-
echo "signing enabled for the tracker builds"
7341
74-
# The tracker builds run sequentially in the same workspace: stash each
75-
# target's outputs, then wipe build/ and sdkconfig so the next set-target
76-
# starts clean from the (sign-enabled) defaults.
77-
- name: Build tracker (esp32c3)
42+
- name: Build ESP32-C3 firmware
7843
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
7944
with:
8045
esp_idf_version: v5.5.4
8146
target: esp32c3
8247

83-
- name: Stash esp32c3 outputs
84-
run: |
85-
mkdir -p out/esp32c3
86-
cp build/crowmotion.bin build/bootloader/bootloader.bin \
87-
build/partition_table/partition-table.bin build/ota_data_initial.bin \
88-
out/esp32c3/
89-
sudo rm -rf build sdkconfig
90-
91-
- name: Build tracker (esp32s3)
92-
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
93-
with:
94-
esp_idf_version: v5.5.4
95-
target: esp32s3
96-
97-
- name: Stash esp32s3 outputs
98-
run: |
99-
mkdir -p out/esp32s3
100-
cp build/crowmotion.bin build/bootloader/bootloader.bin \
101-
build/partition_table/partition-table.bin build/ota_data_initial.bin \
102-
out/esp32s3/
103-
sudo rm -rf build sdkconfig
104-
105-
- name: Build tracker (esp32c6)
106-
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
107-
with:
108-
esp_idf_version: v5.5.4
109-
target: esp32c6
110-
111-
- name: Stash esp32c6 outputs
112-
run: |
113-
mkdir -p out/esp32c6
114-
cp build/crowmotion.bin build/bootloader/bootloader.bin \
115-
build/partition_table/partition-table.bin build/ota_data_initial.bin \
116-
out/esp32c6/
117-
118-
- name: Build CrowLink bridge (esp32c6)
119-
uses: espressif/esp-idf-ci-action@e6f5c74232b1ccd4c97ed641f1e48553853f1fd5 # v1.2.0
120-
with:
121-
esp_idf_version: v5.5.4
122-
target: esp32c6
123-
path: bridge
124-
125-
- name: Stage site (flasher + OTA)
48+
- name: Verify signature and stage release
12649
run: |
12750
VER="${TAG#v}"
12851
pip install --quiet "esptool==4.8.1"
129-
130-
# Fail the release unless every tracker image is actually signed with
131-
# our key (guards against a misbuild shipping an unverifiable image
132-
# that would then reject every future OTA on 0.1.3+ devices).
133-
for T in esp32c3 esp32s3 esp32c6; do
134-
espsecure.py verify_signature --version 2 --keyfile ota_signing_key.pem \
135-
"out/$T/crowmotion.bin" | tee /tmp/verify.txt
136-
grep -q "verification successful" /tmp/verify.txt \
137-
|| { echo "::error::$T tracker image is not validly signed"; exit 1; }
138-
done
52+
espsecure.py verify_signature --version 2 --keyfile ota_signing_key.pem build/crowmotion.bin | tee /tmp/verify.txt
53+
grep -q "verification successful" /tmp/verify.txt || { echo "::error::firmware signature verification failed"; exit 1; }
13954
rm -f ota_signing_key.pem
140-
14155
mkdir -p site/flash site/crowmotion
142-
# Copy the whole flasher (index.html + vendored esp-web-tools);
143-
# nothing is loaded from a third-party CDN.
14456
cp -R flasher/. site/
145-
146-
# CORS: let the flasher embedded on crowpilot.in fetch the
147-
# manifest and images cross-origin (Cloudflare Pages _headers).
14857
printf '/flash/*\n Access-Control-Allow-Origin: *\n' > site/_headers
149-
150-
# Complete flash images for the browser flasher (offset 0), one per
151-
# tracker target plus the bridge.
152-
for T in esp32c3 esp32s3 esp32c6; do
153-
esptool.py --chip "$T" merge_bin \
154-
-o "site/flash/crowmotion-merged-$T.bin" \
155-
--flash_mode dio --flash_size 4MB \
156-
0x0 "out/$T/bootloader.bin" \
157-
0x8000 "out/$T/partition-table.bin" \
158-
0xf000 "out/$T/ota_data_initial.bin" \
159-
0x20000 "out/$T/crowmotion.bin"
160-
done
161-
esptool.py --chip esp32c6 merge_bin \
162-
-o site/flash/crowlink-merged.bin \
163-
--flash_mode dio --flash_size 2MB \
164-
0x0 bridge/build/bootloader/bootloader.bin \
165-
0x8000 bridge/build/partition_table/partition-table.bin \
166-
0x10000 bridge/build/crowlink.bin
167-
168-
# Legacy names, kept so the currently-published flasher page and
169-
# product pages keep working until they switch to the per-product
170-
# manifests below.
171-
cp site/flash/crowmotion-merged-esp32c3.bin site/flash/crowmotion-merged.bin
172-
cat > site/flash/manifest.json <<EOF
173-
{
174-
"name": "CrowMotion / CrowLink",
175-
"version": "${VER}",
176-
"new_install_prompt_erase": true,
177-
"builds": [
178-
{
179-
"chipFamily": "ESP32-C3",
180-
"parts": [ { "path": "crowmotion-merged.bin", "offset": 0 } ]
181-
},
182-
{
183-
"chipFamily": "ESP32-C6",
184-
"parts": [ { "path": "crowlink-merged.bin", "offset": 0 } ]
185-
}
186-
]
187-
}
188-
EOF
189-
190-
# Per-product manifests: CrowMotion picks the image for whichever
191-
# supported chip is plugged in; CrowLink is its own button.
192-
cat > site/flash/crowmotion.json <<EOF
193-
{
194-
"name": "CrowMotion",
195-
"version": "${VER}",
196-
"new_install_prompt_erase": true,
197-
"builds": [
198-
{
199-
"chipFamily": "ESP32-C3",
200-
"parts": [ { "path": "crowmotion-merged-esp32c3.bin", "offset": 0 } ]
201-
},
202-
{
203-
"chipFamily": "ESP32-S3",
204-
"parts": [ { "path": "crowmotion-merged-esp32s3.bin", "offset": 0 } ]
205-
},
206-
{
207-
"chipFamily": "ESP32-C6",
208-
"parts": [ { "path": "crowmotion-merged-esp32c6.bin", "offset": 0 } ]
209-
}
210-
]
211-
}
212-
EOF
213-
cat > site/flash/crowlink.json <<EOF
214-
{
215-
"name": "CrowLink",
216-
"version": "${VER}",
217-
"new_install_prompt_erase": true,
218-
"builds": [
219-
{
220-
"chipFamily": "ESP32-C6",
221-
"parts": [ { "path": "crowlink-merged.bin", "offset": 0 } ]
222-
}
223-
]
224-
}
225-
EOF
226-
227-
# OTA: per-target app images + manifests (0.1.3+ devices fetch
228-
# latest-<target>.json), plus the legacy pair for fielded C3 devices
229-
# running 0.1.2 or older.
230-
for T in esp32c3 esp32s3 esp32c6; do
231-
cp "out/$T/crowmotion.bin" "site/crowmotion/crowmotion-${VER}-$T.bin"
232-
printf '{"version":"%s","url":"https://updates.crowpilot.in/crowmotion/crowmotion-%s-%s.bin"}\n' \
233-
"$VER" "$VER" "$T" > "site/crowmotion/latest-$T.json"
234-
done
235-
cp out/esp32c3/crowmotion.bin "site/crowmotion/crowmotion-${VER}.bin"
58+
esptool.py --chip esp32c3 merge_bin -o site/flash/crowmotion-merged.bin \
59+
--flash_mode dio --flash_size 4MB \
60+
0x0 build/bootloader/bootloader.bin \
61+
0x8000 build/partition_table/partition-table.bin \
62+
0xf000 build/ota_data_initial.bin \
63+
0x20000 build/crowmotion.bin
64+
cp build/crowmotion.bin "site/crowmotion/crowmotion-${VER}.bin"
23665
printf '{"version":"%s","url":"https://updates.crowpilot.in/crowmotion/crowmotion-%s.bin"}\n' \
23766
"$VER" "$VER" > site/crowmotion/latest.json
238-
echo "manifests:" && cat site/crowmotion/latest.json site/crowmotion/latest-*.json
67+
cp site/crowmotion/latest.json site/crowmotion/latest-esp32c3.json
68+
cat > site/flash/manifest.json <<EOF
69+
{"name":"CrowMotion","version":"${VER}","new_install_prompt_erase":true,"builds":[{"chipFamily":"ESP32-C3","parts":[{"path":"crowmotion-merged.bin","offset":0}]}]}
70+
EOF
23971
240-
- name: Attach binaries to the release
72+
- name: Attach binaries to release
24173
env:
24274
GH_TOKEN: ${{ github.token }}
24375
run: |
24476
VER="${TAG#v}"
245-
cp bridge/build/crowlink.bin "crowlink-${VER}.bin"
246-
gh release upload "$TAG" \
247-
"site/crowmotion/crowmotion-${VER}-esp32c3.bin" \
248-
"site/crowmotion/crowmotion-${VER}-esp32s3.bin" \
249-
"site/crowmotion/crowmotion-${VER}-esp32c6.bin" \
250-
"crowlink-${VER}.bin" \
251-
"site/flash/crowmotion-merged-esp32c3.bin" \
252-
"site/flash/crowmotion-merged-esp32s3.bin" \
253-
"site/flash/crowmotion-merged-esp32c6.bin" \
254-
"site/flash/crowlink-merged.bin" --clobber
77+
gh release upload "$TAG" "site/crowmotion/crowmotion-${VER}.bin" \
78+
site/flash/crowmotion-merged.bin --clobber
25579
256-
- name: Deploy to Cloudflare Pages
80+
- name: Deploy update site
25781
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
25882
with:
25983
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
26084
accountId: 2fca98f9bd732794c53117b67c5b4593
261-
# --branch main forces a production deployment; the release event
262-
# checks out a detached tag, which wrangler would otherwise treat
263-
# as a preview branch ("HEAD") and never update the live site.
26485
command: pages deploy site --project-name crowmotion-updates --branch main

CONTRIBUTING.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ reliable are very welcome.
88

99
Build instructions (ESP-IDF v5.5.4, ESP32-C3) are in
1010
[docs/BUILD.md](docs/BUILD.md). The only supported v1 hardware is the
11-
ESP32-C3 Super Mini plus an MPU6500 module.
11+
ESP32-C3 Super Mini plus an MPU6050 module.
1212

1313
## Making changes
1414

@@ -31,10 +31,9 @@ where possible.
3131

3232
## Scope notes
3333

34-
- v1 is deliberately minimal: one board, one IMU, PARA BLE trainer output.
35-
- Feature ideas beyond that (other boards, other output protocols, battery
36-
operation) are v2 territory; feel free to open a discussion first so we
37-
can align before you write code.
34+
- Version 0.2 is deliberately focused on one board and one IMU, with PARA BLE
35+
and local trainer-jack PPM from the same self-powered unit.
36+
- Open a discussion before adding other boards or output protocols.
3837

3938
## License
4039

0 commit comments

Comments
 (0)