Repository navigation
Expand file tree
/
Copy pathTaskfile.yml
More file actions
282 lines (267 loc) · 13.3 KB
/
Copy pathTaskfile.yml
File metadata and controls
282 lines (267 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
version: "3"
# Vars the root owns because more than one included Taskfile needs them.
# Task does not share vars between included Taskfiles, so a definition that
# lives in one include is only visible to the others by the accident of
# flatten order: src/python/Taskfile.yml uses {{.BASH}} nine times and has
# never defined it, and worked only because other/tasks/platform.yml, which is
# flattened first, did. That is invisible coupling -- reorder the includes, or
# un-flatten platform, and nine robot tasks break with "BASH: not found".
# Defining them here, where every include can see them, removes the ordering
# dependency and the duplicate copies each include used to keep in sync.
#
# A var a caller may override is written as its own default. Task lets a
# global in an included Taskfile beat a CLI `VAR=x`; a plain global here still
# shadows the CLI for an included task exactly as the include's own copy did,
# so the self-default is what keeps `VAR=x` working everywhere.
vars:
# EXE resolves to ".exe" on Windows and "" on Linux/macOS so binary
# references work cross-platform. Falls back to "" when Go isn't installed
# (e.g. on the robot/Pi) so non-Go tasks still run.
EXE:
sh: go env GOEXE 2>/dev/null || true
# Shell scripts must run under Git Bash on Windows, never plain "bash".
# C:\Windows\System32\bash.exe is the WSL launcher and usually wins on PATH
# from PowerShell, which puts the script inside a Linux distro that has its
# own $HOME -- so ~/.ssh/config is absent, SSH host aliases resolve as
# literal hostnames, and .local mDNS does not work at all. The failure
# surfaces as "Could not resolve hostname rpi-5-local", naming the alias
# rather than the host it should have expanded to.
# Override with BASH=... if Git is installed somewhere else.
BASH: '{{.BASH | default .OS_BASH}}'
# The OS-aware fallback BASH defaults to. It is a separate var rather than an
# inline expression because Task rejects `if` inside a default pipeline.
OS_BASH: '{{if eq OS "windows"}}C:/Program Files/Git/bin/bash.exe{{else}}bash{{end}}'
# Which physical robot a sim task models. The base robot.toml describes the
# chassis only and declares no motor or servo, so config loading FAILS unless
# one profile of each is named. Defaulted here, in a checked-in Taskfile,
# which is visible and reviewable, rather than in the config loader, where
# the old invisible default silently made every unset run model whichever
# hardware happened to be committed. Deliberately NOT applied to robot:test:
# the tests pin their own hardware in tests/conftest.py. Override per
# invocation, e.g. PROFILE=180deg-injora-14kg,generic-motor-1500rpm.
PROFILE: '{{.PROFILE | default "270deg-hiwonder-35kg,rev-hd-hex-motor-6000rpm"}}'
# Full path to pixi on the Pi 5 / Pi Zero specifically -- bare `pixi` only
# resolves under an interactive login shell (.bashrc adds ~/.pixi/bin to
# PATH); a one-shot `ssh host "task robot:..."` gets the minimal
# non-interactive PATH and fails with "pixi: not found". Every robot:* task
# that runs directly ON a Pi should use {{.ROBOT_PIXI}}, not a bare `pixi`.
ROBOT_PIXI: ~/.pixi/bin/pixi
# Which challenge a generator targets.
CHALLENGE: '{{.CHALLENGE | default "open"}}'
# Where generated scenarios and recordings live. Anchored to the repo root
# because the includes run from different working dirs -- gazebo's record:*
# tasks run with dir: other/apps/gazebo/runtime -- and a relative default
# would resolve to a different place in each. Override per invocation.
OUTPUT_DIR: '{{.OUTPUT_DIR | default (printf "%s/src/go/training_data" .ROOT_DIR)}}'
# Sample scenario metadata, for replaying/previewing one scenario. Anchored
# to the repo root for the same reason as OUTPUT_DIR.
METADATA: '{{.METADATA | default (printf "%s/src/go/training_data/open/scenarios/scenario_0000_metadata.json" .ROOT_DIR)}}'
# Board addresses, shared by other/tasks/fleet.yml (windows:*/rpi:* tasks)
# and other/deploy/ansible/Taskfile.yml. Change them here, once.
# RPI_LOCAL_IP/ZERO_WIFI_IP are the router subnet; RPI_ZERO_GADGET_IP is the
# Zero's end of the Pi5<->Zero usb0 gadget link (Zero=.1, Pi5=.2). Distinct
# from the direct-cable subnet (RPI_DIRECT_IP, 192.168.251.x) to avoid both
# landing on Pi 5's usb0 and 'Wired connection 1' with the same address.
RPI_LOCAL_IP: 192.168.0.50
ZERO_WIFI_IP: 192.168.0.51
RPI_ZERO_GADGET_IP: 192.168.250.1
includes:
platform:
taskfile: ./other/tasks/platform.yml
dir: .
flatten: true
infra:
taskfile: ./other/tasks/infra.yml
dir: .
flatten: true
fleet:
taskfile: ./other/tasks/fleet.yml
dir: .
flatten: true
src:
taskfile: ./src/Taskfile.yml
dir: .
flatten: true
src-python:
taskfile: ./src/python/Taskfile.yml
dir: ./src/python
flatten: true
src-go:
taskfile: ./src/go/Taskfile.yml
dir: ./src/go
flatten: true
apps-frontend:
taskfile: ./other/apps/frontend/Taskfile.yml
dir: ./other/apps/frontend
flatten: true
apps-backend:
taskfile: ./other/apps/backend/Taskfile.yml
dir: ./other/apps/backend
flatten: true
apps-gazebo:
taskfile: ./other/apps/gazebo/runtime/Taskfile.yml
dir: ./other/apps/gazebo/runtime
flatten: true
deploy-ansible:
taskfile: ./other/deploy/ansible/Taskfile.yml
dir: ./other/deploy/ansible
flatten: true
# The four app includes below are flattened, so their Taskfiles carry
# fully-qualified task keys (`hailo:lint`, not `lint`) and the include key is
# only a label. That keeps every reachable name identical while leaving no
# raw key defined by two files, so include order can never decide a winner.
auto-annotator:
taskfile: ./other/apps/auto-annotator/Taskfile.yml
dir: ./other/apps/auto-annotator
flatten: true
hailo:
taskfile: ./other/ml/hailo/Taskfile.yml
dir: ./other/ml/hailo
flatten: true
models:
taskfile: ./other/ml/weights/Taskfile.yml
dir: ./other/ml/weights
flatten: true
hugo-docs:
taskfile: ./other/apps/hugo-docs/Taskfile.yml
dir: ./other/apps/hugo-docs
flatten: true
tasks:
default:
desc: List all available tasks
cmds:
- task --list
# DOCS (repo-root documentation artifacts)
docs:diagrams:
desc: >-
Render every schemes/flowcharts/{common,open,obstacles}/mermaid/*.mmd
file to a lossless WebP in the matching .../webp/ folder (usage: task
docs:diagrams). common/ holds logic shared by both challenges (direction
inference, collision/stuck escape, lap counting) — open/ and obstacles/
reference it instead of redrawing it, to avoid duplication. Requires the
Mermaid CLI (npx @mermaid-js/mermaid-cli, aka mmdc) — no local install
needed, npx fetches it on first run. Re-run after editing or adding a
.mmd file; output filenames mirror the source (foo.mmd -> foo.webp).
mmdc only emits PNG/SVG, so each diagram is rendered to a temporary PNG
and re-encoded with `cwebp -lossless` (pixel-identical, ~66% smaller);
the temporary PNG is deleted. Requires cwebp (libwebp) on PATH. %%
comment lines inside a .mmd are code/constant references for maintainers
and are never rendered. The wiring harness is NOT rendered here: it is a
tscircuit project, see schemes/wiring/tscircuit/.
vars:
DIAGRAMS_DIR: schemes/flowcharts
cmds:
- cmd: mkdir -p "{{.DIAGRAMS_DIR}}/common/webp" "{{.DIAGRAMS_DIR}}/open/webp" "{{.DIAGRAMS_DIR}}/obstacles/webp"
- cmd: |
for challenge in common open obstacles; do
for f in "{{.DIAGRAMS_DIR}}/$challenge/mermaid"/*.mmd; do
name="${f##*\\}"
name="${name%.mmd}"
echo "Rendering $challenge/mermaid/$name.mmd -> $challenge/webp/$name.webp"
tmp="{{.DIAGRAMS_DIR}}/$challenge/webp/$name.png"
npx --yes @mermaid-js/mermaid-cli -i "$f" -o "$tmp" -b white -s 3
cwebp -quiet -mt -lossless -z 9 "$tmp" -o "{{.DIAGRAMS_DIR}}/$challenge/webp/$name.webp"
rm -f "$tmp"
done
done
platforms: [windows]
- cmd: |
for challenge in common open obstacles; do
for f in "{{.DIAGRAMS_DIR}}/$challenge/mermaid"/*.mmd; do
name="${f##*/}"
name="${name%.mmd}"
echo "Rendering $challenge/mermaid/$name.mmd -> $challenge/webp/$name.webp"
tmp="{{.DIAGRAMS_DIR}}/$challenge/webp/$name.png"
npx --yes @mermaid-js/mermaid-cli -i "$f" -o "$tmp" -b white -s 3
cwebp -quiet -mt -lossless -z 9 "$tmp" -o "{{.DIAGRAMS_DIR}}/$challenge/webp/$name.webp"
rm -f "$tmp"
done
done
platforms: [linux, darwin]
docs:blueprints:
desc: >-
Re-encode every dimensioned drawing under models/*/blueprints/ to
high-quality lossy WebP (usage: task docs:blueprints). Run it after
exporting a new drawing from SolidWorks, so the folder keeps one encoding
policy instead of whatever each export happened to produce. Idempotent by
construction: only LOSSLESS inputs are candidates, so a drawing already at
q90 is skipped and re-running never stacks a second generation of loss.
A .png dropped in the folder is converted and the .png removed. Requires
cwebp (libwebp) on PATH.
WHY q90 AND NOT LOSSLESS, measured 2026-09-14 over the 47 drawings:
lossless 3308 KB, q95 2908 KB (-9%), q90 2496 KB (-25%), q85 2077 KB
(-35%). Fidelity against the lossless original on the most
dimension-dense drawing: q95 53 dB PSNR, q90 49 dB, q85 46 dB. Above
~40 dB is the usual visually-lossless threshold, so q90 is the knee:
a quarter of the bytes for fidelity nobody can see, while q85 starts
eating into the margin that keeps dimension text crisp.
DO NOT copy this setting to schemes/flowcharts/. That was measured too,
and lossy makes flowcharts BIGGER, not smaller: q90 +86%, q95 +131%.
Flat-colour line art with text is exactly what lossless WebP is good at,
which is why docs:diagrams uses `-lossless -z 9` and should keep doing so.
vars:
BLUEPRINT_DIRS: models/vtitan/blueprints models/klevor/blueprints
cmds:
- cmd: |
for d in {{.BLUEPRINT_DIRS}}; do
for f in "$d"/*.png "$d"/*.webp; do
[ -e "$f" ] || continue
case "$f" in
*.webp)
# Only LOSSLESS files are candidates. A file already at q90 is
# skipped, so re-running never stacks a second generation of
# loss on the drawings. VP8L in the header means lossless.
head -c 64 "$f" | grep -q VP8L || { echo "skip (already lossy) $f"; continue; }
out="$f" ;;
*.png)
out="${f%.png}.webp" ;;
esac
cwebp -quiet -mt -q 90 "$f" -o "$out.tmp" && rm "$f" && mv "$out.tmp" "$out"
echo "q90 $out"
done
done
docs:mermaid:
desc: >-
Refresh the Mermaid blocks inlined in README.md from their .mmd sources in
schemes/flowcharts/*/mermaid/ (usage: task docs:mermaid). The .mmd file is
the single source of truth: GitHub renders it when opened directly, `task
docs:diagrams` renders it to WebP, and the README carries an inlined copy
so the diagram shows without leaving the page. Each copy is anchored by an
HTML comment naming its source, and %% maintenance comments are dropped on
the way in. Run this after editing any .mmd. Add -- --check to report
stale blocks and fail instead of rewriting, which is what CI should call.
cmds:
- cmd: python other/scripts/sync-readme-mermaid.py {{.CLI_ARGS}}
# GPG
gpg:fix-tty:
desc: "Fix 'Inappropriate ioctl for device' GPG error"
cmds:
- export GPG_TTY=$(tty)
platforms: [linux, darwin]
# Shared brand assets
assets:sync-logos:
desc: >-
Copy the canonical Voltimor logo files (other/assets/) into every
frontend's public/ dir. other/assets/ is the single source of truth --
Vite can't reference files outside a project's own root, so each
frontend needs its own copy; re-run this after replacing either source
file.
cmds:
- cp other/assets/voltimor-logo-rounded-square.webp other/assets/voltimor-logo-banner.webp other/apps/auto-annotator/frontend/public/
- cp other/assets/voltimor-logo-rounded-square.webp other/assets/voltimor-logo-banner.webp other/apps/frontend/public/
- echo "[OK] Synced logo assets to other/apps/auto-annotator/frontend/public and other/apps/frontend/public"
ci:secrets:
desc: Render .secrets (gitignored) from the secrets store for `task ci:local`
cmds:
- |
S="${SECRETS_DIR:-$HOME/Secrets}"
[ -r "$S/github/ci-modules" ] || { echo "missing $S/github/ci-modules" >&2; exit 1; }
printf 'GH_PRIVATE_MODULES_TOKEN=%s\n' "$(cat "$S/github/ci-modules")" > .secrets
chmod 600 .secrets
# ci.yml is workflow_dispatch only, so nothing runs on a billed GitHub-hosted
# runner. `act` runs that same file locally; the token is the same one CI uses.
ci:local:
desc: Run ci.yml locally with act (the workflow is workflow_dispatch only)
cmds:
- task: ci:secrets
- act workflow_dispatch -W .github/workflows/ci.yml --action-offline-mode --pull=false --secret-file .secrets --env-file /dev/null