-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathMakefile
More file actions
231 lines (202 loc) · 9.6 KB
/
Copy pathMakefile
File metadata and controls
231 lines (202 loc) · 9.6 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
IDF_PATH ?= $(HOME)/esp-idf/5.5.5
IDF_EXPORTS = $(IDF_PATH)/export.sh
TARGET ?= esp32s3
# Board variant. Selects the PSRAM mode (Quad vs Octal) and per-board sdkconfig
# overrides; the resulting firmware is NOT cross-compatible because PSRAM mode
# is locked in at boot by the bootloader.
# cores3 — M5Stack CoreS3 + M5/Takao base (Quad PSRAM, 320x240, ES8311 N/A)
# atoms3r — AtomS3R + Atomic ECHO BASE (Octal PSRAM, 128x128, ES8311)
# atoms3 — Plain AtomS3 + Atomic ECHO BASE — slim profile (no PSRAM):
# avatar + jtts + nekomimi LED + settings UI; no realtime
# conversation / no BLE audio / no RTP / no camera.
# stopwatch — M5Stack StopWatch (C152): ESP32-S3R8 + 16 MB flash +
# 8 MB Octal PSRAM + 466×466 AMOLED 円形 + CST820B touch +
# ES8311 + MEMS mic + AW8737A speaker + M5PM1 PMIC +
# M5IOE1 IO expander + RX8130CE RTC + BMI270 IMU + 振動モータ.
# No PY32 / no Si12T / no nekomimi LED 配線. Servo I/O は背面
# 2.54 mm バス経由のオプション (Phase 3).
# Default cores3 keeps `make build` backward-compatible.
BOARD ?= cores3
BUILD_DIR = build-$(BOARD)
# Serial port for flash / monitor / erase-flash. Leave unset to let idf.py
# auto-detect; pass on the command line (`make flash PORT=/dev/ttyACM0`) or
# pin in Makefile.local.
PORT ?=
IDFPY_PORT = $(if $(PORT),-p $(PORT))
# Allow developers to keep local overrides in an untracked file.
-include Makefile.local
# Build sdkconfig defaults chains.
# - sdkconfig.defaults (committed, common)
# - sdkconfig.defaults.<target> (committed, per-chip — esp32s3 here)
# - sdkconfig.defaults.<board> (committed, per-board variant — cores3
# / atoms3r; PSRAM mode lives here)
# - sdkconfig.defaults.local (gitignored, host-specific overrides;
# the last entry wins for duplicate keys so local values trump committed
# defaults)
SDKCONFIG_DEFAULTS_HW = sdkconfig.defaults
ifneq (,$(wildcard sdkconfig.defaults.$(TARGET)))
SDKCONFIG_DEFAULTS_HW := $(SDKCONFIG_DEFAULTS_HW);sdkconfig.defaults.$(TARGET)
endif
ifneq (,$(wildcard sdkconfig.defaults.$(BOARD)))
SDKCONFIG_DEFAULTS_HW := $(SDKCONFIG_DEFAULTS_HW);sdkconfig.defaults.$(BOARD)
endif
ifneq (,$(wildcard sdkconfig.defaults.local))
SDKCONFIG_DEFAULTS_HW := $(SDKCONFIG_DEFAULTS_HW);sdkconfig.defaults.local
endif
.PHONY: build clean set-target flash flash-monitor monitor monitor-log erase-flash \
build-docker docker-shell docker-clean \
audio-cli audio-play audio-test recovery-build pack
# Capture serial output non-interactively (for CI / agent contexts where
# idf.py monitor refuses to attach without a TTY). SECONDS defaults to 8.
MONITOR_LOG_SECONDS ?= 8
# --- Target builds ----------------------------------------------------------
set-target:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) -DSDKCONFIG=$(BUILD_DIR)/sdkconfig -DSDKCONFIG_DEFAULTS='$(SDKCONFIG_DEFAULTS_HW)' set-target $(TARGET)"
$(MAKE) normalize-lock
build:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) -DSDKCONFIG=$(BUILD_DIR)/sdkconfig -DSDKCONFIG_DEFAULTS='$(SDKCONFIG_DEFAULTS_HW)' build"
$(MAKE) normalize-lock
# dependencies.lock is committed so CI and local builds resolve the same
# managed-component versions. The component manager rewrites the local
# (override_path) M5GFX entry with an ABSOLUTE path on every configure, which
# would make the file machine-specific; rewrite it back to a project-relative
# path (the manager accepts that form). Run after every build / set-target.
.PHONY: normalize-lock
normalize-lock:
@sed -i 's|path: $(CURDIR)/third_party/m5gfx|path: third_party/m5gfx|' dependencies.lock 2>/dev/null || true
flash:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) $(IDFPY_PORT) flash"
monitor:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) $(IDFPY_PORT) monitor"
# Non-interactive log capture: resets the target and reads stdout for
# MONITOR_LOG_SECONDS seconds. Requires pyserial.
monitor-log:
python3 tools/monitor_log.py --port $(if $(PORT),$(PORT),/dev/ttyACM0) --seconds $(MONITOR_LOG_SECONDS)
# Convenience: flash + monitor in a single idf.py invocation (faster than
# `make flash monitor` because the IDF env is sourced once).
flash-monitor:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) $(IDFPY_PORT) flash monitor"
erase-flash:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) $(IDFPY_PORT) erase-flash"
clean:
bash -c "source $(IDF_EXPORTS) && idf.py -B $(BUILD_DIR) fullclean"
# --- Docker build (mirrors CI) ----------------------------------------------
DOCKER_IMAGE ?= espressif/idf:release-v6.0
DOCKER_WORKDIR ?= /work
DOCKER_BUILD_DIR ?= build-docker
DOCKER_SDKCONFIG ?= sdkconfig.docker
DOCKER_TARGET_CHIP ?= esp32s3
define docker-run
docker run --rm -t \
-u $$(id -u):$$(id -g) \
-v "$(CURDIR):$(DOCKER_WORKDIR)" \
-w $(DOCKER_WORKDIR) \
-e HOME=/tmp \
-e CI=true \
$(DOCKER_IMAGE) \
bash -c '\
git config --global --add safe.directory "$(DOCKER_WORKDIR)" && \
. "$$IDF_PATH/export.sh" && \
$(1)'
endef
build-docker:
$(call docker-run, \
idf.py -B $(DOCKER_BUILD_DIR) -DSDKCONFIG=$(DOCKER_SDKCONFIG) \
-DSDKCONFIG_DEFAULTS=sdkconfig.defaults \
set-target $(DOCKER_TARGET_CHIP) && \
idf.py -B $(DOCKER_BUILD_DIR) -DSDKCONFIG=$(DOCKER_SDKCONFIG) \
-DSDKCONFIG_DEFAULTS=sdkconfig.defaults build)
docker-clean:
$(call docker-run, \
idf.py -B $(DOCKER_BUILD_DIR) -DSDKCONFIG=$(DOCKER_SDKCONFIG) \
fullclean)
@rm -f $(DOCKER_SDKCONFIG)
# --- BLE audio streaming CLI (tools/audio-cli) ------------------------------
#
# Wraps the Rust CLI for end-to-end BLE audio test. The combined target
# starts the device serial log capture in the background so the AAC
# decoder diagnostics line up with the CLI's send progress in the same
# transcript.
AUDIO_CLI_BIN ?= tools/audio-cli/target/release/audio-cli
# BLE local-name prefix to connect to. "Stackchan-" matches any unit (the CLI
# connects to the first match); pin the full name (Stackchan-XXXXXX) to target
# a specific device. Name suffix = Wi-Fi STA MAC lower 3 bytes.
AUDIO_DEVICE ?= Stackchan-
AUDIO_FILE ?= tools/audio-cli/out.aac
# Throughput ceiling in KiB/s, applied on top of the device's credit-based
# flow control. 0 = no artificial cap: the CLI polls the AudioCredit
# characteristic and paces itself to the device's playback rate, so the PCM
# ring can't overrun. Set a non-zero value only to debug a slower link.
# (Fixed-rate guessing is what overran the ring before — too fast garbled
# playback, too slow starved it.)
AUDIO_RATE ?= 0
AUDIO_FLUSH ?= 16
AUDIO_CHUNK ?= 250
AUDIO_LOG_SEC ?= 120
audio-cli:
cd tools/audio-cli && cargo build --release
audio-play: audio-cli
$(AUDIO_CLI_BIN) \
--device "$(AUDIO_DEVICE)" \
--file "$(AUDIO_FILE)" \
--chunk-size $(AUDIO_CHUNK) \
--rate-kbps $(AUDIO_RATE) \
--flush-every $(AUDIO_FLUSH)
# Parallel: serial log capture in the background while audio-play runs in
# the foreground. The device-side `audio-stream:` and `cfg-gatt:` lines
# interleave with the CLI's "sent N / total B" progress so the failure
# mode is obvious in one pane.
#
# The CLI is held back until the conv-task reports "session ready" so the
# streaming test runs against the real BLE/Wi-Fi radio-contention picture
# the conversation backend creates, not the wide-open BLE we'd see
# pre-association.
AUDIO_TEST_READY_TIMEOUT ?= 60
audio-test: audio-cli
@echo "=== launching serial log capture (PORT=$(if $(PORT),$(PORT),/dev/ttyACM0), $(AUDIO_LOG_SEC) s) ==="
@python3 tools/monitor_log.py \
--port $(if $(PORT),$(PORT),/dev/ttyACM0) \
--seconds $(AUDIO_LOG_SEC) > /tmp/audio-test-monitor.log 2>&1 & \
MONITOR_PID=$$!; \
echo "=== waiting for conv-task: session ready (≤$(AUDIO_TEST_READY_TIMEOUT) s) ==="; \
for i in $$(seq 1 $(AUDIO_TEST_READY_TIMEOUT)); do \
if grep -q "conv-task: session ready" /tmp/audio-test-monitor.log 2>/dev/null; then \
echo "conv-task ready at +$${i}s"; \
break; \
fi; \
sleep 1; \
done; \
if ! grep -q "conv-task: session ready" /tmp/audio-test-monitor.log 2>/dev/null; then \
echo "WARN: conv-task: session ready did not show within $(AUDIO_TEST_READY_TIMEOUT)s — running test anyway"; \
fi; \
echo "=== streaming $(AUDIO_FILE) to $(AUDIO_DEVICE) ==="; \
$(AUDIO_CLI_BIN) \
--device "$(AUDIO_DEVICE)" \
--file "$(AUDIO_FILE)" \
--chunk-size $(AUDIO_CHUNK) \
--rate-kbps $(AUDIO_RATE) \
--flush-every $(AUDIO_FLUSH); \
echo "=== waiting for monitor to finish ==="; \
wait $$MONITOR_PID; \
echo "=== device log (tail) ==="; \
tail -n 80 /tmp/audio-test-monitor.log
docker-shell:
docker run --rm -it \
-u $$(id -u):$$(id -g) \
-v "$(CURDIR):$(DOCKER_WORKDIR)" \
-w $(DOCKER_WORKDIR) \
-e HOME=/tmp \
$(DOCKER_IMAGE) \
bash -c '\
git config --global --add safe.directory "$(DOCKER_WORKDIR)" && \
. "$$IDF_PATH/export.sh" && \
exec bash'
# --- ADR-001 release packaging ---------------------------------------------
# Main (build-$(BOARD)) と Recovery (recovery/build-$(BOARD)) を揃えてから、
# 機体に書く一式 + flash_manifest.json + 結合イメージを release/$(BOARD) に作る。
VERSION ?= $(shell git describe --tags --always --dirty)
recovery-build:
$(MAKE) -C recovery set-target BOARD=$(BOARD) IDF_PATH=$(IDF_PATH)
$(MAKE) -C recovery build BOARD=$(BOARD) IDF_PATH=$(IDF_PATH)
pack:
python3 script/pack_release.py --board $(BOARD) --version $(VERSION) --out release/$(BOARD)