tirtc-device-builder 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +44 -14
- package/bin/tirtc-device-builder.js +33 -1
- package/package.json +1 -1
- package/skills/tirtc-esp32-builder/SKILL.md +139 -39
- package/skills/tirtc-esp32-builder/USAGE.md +3 -2
- package/skills/tirtc-esp32-builder/VERSION +1 -1
- package/skills/tirtc-esp32-builder/agents/openai.yaml +1 -1
- package/skills/tirtc-esp32-builder/assets/board-audio-contract.example.json +4 -0
- package/skills/tirtc-esp32-builder/assets/board-identity.example.json +16 -0
- package/skills/tirtc-esp32-builder/assets/developer-intake-prompt.md +3 -3
- package/skills/tirtc-esp32-builder/assets/hardware-ir-v2.example.json +9 -1
- package/skills/tirtc-esp32-builder/assets/lckfb-szpi-esp32s3-portable-prompt.md +5 -5
- package/skills/tirtc-esp32-builder/assets/report-template.md +6 -1
- package/skills/tirtc-esp32-builder/assets/tirtc-runtime-contract.example.json +13 -0
- package/skills/tirtc-esp32-builder/knowledge/board-registry.json +4 -0
- package/skills/tirtc-esp32-builder/references/audio-contract.md +33 -3
- package/skills/tirtc-esp32-builder/references/board-knowledge.md +72 -0
- package/skills/tirtc-esp32-builder/references/capability-rules.md +11 -1
- package/skills/tirtc-esp32-builder/references/environment.md +20 -0
- package/skills/tirtc-esp32-builder/references/hardware-ir.md +17 -3
- package/skills/tirtc-esp32-builder/references/porting-risks.md +25 -0
- package/skills/tirtc-esp32-builder/references/reporting.md +1 -1
- package/skills/tirtc-esp32-builder/references/runtime-contract.md +12 -2
- package/skills/tirtc-esp32-builder/references/tirtc-platform.md +92 -0
- package/skills/tirtc-esp32-builder/references/workflow.md +31 -11
- package/skills/tirtc-esp32-builder/scripts/audio_contract.py +105 -3
- package/skills/tirtc-esp32-builder/scripts/board_registry.py +677 -0
- package/skills/tirtc-esp32-builder/scripts/hardware_ir.py +147 -8
- package/skills/tirtc-esp32-builder/scripts/project_portability.py +178 -3
- package/skills/tirtc-esp32-builder/scripts/runtime_contract.py +122 -1
|
@@ -192,18 +192,114 @@ def check_topology(contract: dict[str, Any], errors: list[str]) -> None:
|
|
|
192
192
|
)
|
|
193
193
|
if simultaneous is False and handoff == "none":
|
|
194
194
|
errors.append("half-duplex shared clocks require an explicit handoff")
|
|
195
|
+
if simultaneous is True and handoff != "none":
|
|
196
|
+
errors.append("simultaneous directions must not use a clock handoff")
|
|
197
|
+
paired_channels = shared.get("paired_channels", False)
|
|
198
|
+
if not isinstance(paired_channels, bool):
|
|
199
|
+
errors.append("shared_clock.paired_channels must be true or false")
|
|
200
|
+
paired_channels = False
|
|
201
|
+
|
|
202
|
+
mixed_paired_ok = False
|
|
195
203
|
if simultaneous is True and capture.get("mode") != playback.get("mode"):
|
|
196
|
-
|
|
204
|
+
if paired_channels is not True:
|
|
205
|
+
errors.append(
|
|
206
|
+
"simultaneous mixed I2S modes require shared_clock.paired_channels=true"
|
|
207
|
+
)
|
|
208
|
+
if capture.get("controller") != playback.get("controller"):
|
|
209
|
+
errors.append(
|
|
210
|
+
"simultaneous mixed I2S modes require paired directions on one controller"
|
|
211
|
+
)
|
|
212
|
+
frame_bits: dict[str, int] = {}
|
|
213
|
+
for endpoint, label in ((capture, "capture"), (playback, "playback")):
|
|
214
|
+
slots = require_positive_int(
|
|
215
|
+
endpoint.get("slot_count"), f"{label}.slot_count", errors
|
|
216
|
+
)
|
|
217
|
+
width = require_positive_int(
|
|
218
|
+
endpoint.get("slot_bit_width"),
|
|
219
|
+
f"{label}.slot_bit_width",
|
|
220
|
+
errors,
|
|
221
|
+
)
|
|
222
|
+
if slots is not None and width is not None:
|
|
223
|
+
frame_bits[label] = slots * width
|
|
224
|
+
if len(frame_bits) == 2 and frame_bits["capture"] != frame_bits["playback"]:
|
|
225
|
+
errors.append(
|
|
226
|
+
"simultaneous mixed I2S modes require equal BCLKs per frame: "
|
|
227
|
+
f"capture={frame_bits['capture']} playback={frame_bits['playback']}"
|
|
228
|
+
)
|
|
229
|
+
mixed_paired_ok = (
|
|
230
|
+
paired_channels is True
|
|
231
|
+
and capture.get("controller") == playback.get("controller")
|
|
232
|
+
and len(frame_bits) == 2
|
|
233
|
+
and frame_bits["capture"] == frame_bits["playback"]
|
|
234
|
+
)
|
|
197
235
|
if (
|
|
198
236
|
capture.get("controller") == playback.get("controller")
|
|
199
237
|
and capture.get("mode") != playback.get("mode")
|
|
238
|
+
and not mixed_paired_ok
|
|
200
239
|
and handoff != "delete_recreate"
|
|
201
240
|
):
|
|
202
241
|
errors.append(
|
|
203
242
|
"one I2S controller cannot retain different capture/playback modes; "
|
|
204
|
-
"use distinct controllers or
|
|
243
|
+
"use paired frame-compatible directions, distinct controllers, or "
|
|
244
|
+
"delete_recreate handoff"
|
|
205
245
|
)
|
|
206
246
|
|
|
247
|
+
aec_value = contract.get("echo_cancellation")
|
|
248
|
+
if aec_value is None:
|
|
249
|
+
return
|
|
250
|
+
aec = require_mapping(aec_value, "echo_cancellation", errors)
|
|
251
|
+
enabled = aec.get("enabled")
|
|
252
|
+
if not isinstance(enabled, bool):
|
|
253
|
+
errors.append("echo_cancellation.enabled must be true or false")
|
|
254
|
+
return
|
|
255
|
+
if not enabled:
|
|
256
|
+
return
|
|
257
|
+
if simultaneous is not True:
|
|
258
|
+
errors.append("echo cancellation requires simultaneous capture and playback")
|
|
259
|
+
if capture_mode != "tdm":
|
|
260
|
+
errors.append("echo cancellation reference mapping requires TDM capture")
|
|
261
|
+
sample_rate = require_positive_int(
|
|
262
|
+
aec.get("sample_rate_hz"), "echo_cancellation.sample_rate_hz", errors
|
|
263
|
+
)
|
|
264
|
+
clock = contract.get("clock", {})
|
|
265
|
+
if sample_rate is not None and sample_rate != clock.get("sample_rate_hz"):
|
|
266
|
+
errors.append("echo cancellation sample rate must match the hardware clock")
|
|
267
|
+
|
|
268
|
+
microphone_slot = aec.get("microphone_slot")
|
|
269
|
+
reference_slot = aec.get("reference_slot")
|
|
270
|
+
microphone_signal = require_nonempty_string(
|
|
271
|
+
aec.get("microphone_signal"), "echo_cancellation.microphone_signal", errors
|
|
272
|
+
)
|
|
273
|
+
reference_signal = require_nonempty_string(
|
|
274
|
+
aec.get("reference_signal"), "echo_cancellation.reference_signal", errors
|
|
275
|
+
)
|
|
276
|
+
slot_signals = capture.get("slot_signals")
|
|
277
|
+
slot_count = capture.get("slot_count")
|
|
278
|
+
for slot, signal, label in (
|
|
279
|
+
(microphone_slot, microphone_signal, "microphone"),
|
|
280
|
+
(reference_slot, reference_signal, "reference"),
|
|
281
|
+
):
|
|
282
|
+
if (
|
|
283
|
+
isinstance(slot, bool)
|
|
284
|
+
or not isinstance(slot, int)
|
|
285
|
+
or slot < 0
|
|
286
|
+
or not isinstance(slot_count, int)
|
|
287
|
+
or slot >= slot_count
|
|
288
|
+
):
|
|
289
|
+
errors.append(
|
|
290
|
+
f"echo_cancellation.{label}_slot must select an available TDM slot"
|
|
291
|
+
)
|
|
292
|
+
elif isinstance(slot_signals, list) and slot < len(slot_signals):
|
|
293
|
+
if signal != slot_signals[slot]:
|
|
294
|
+
errors.append(
|
|
295
|
+
f"echo_cancellation.{label}_signal does not match "
|
|
296
|
+
f"capture.slot_signals at slot {slot}"
|
|
297
|
+
)
|
|
298
|
+
if microphone_slot == reference_slot:
|
|
299
|
+
errors.append("echo cancellation microphone and reference slots must differ")
|
|
300
|
+
if microphone_signal is not None and microphone_signal == reference_signal:
|
|
301
|
+
errors.append("echo cancellation microphone and reference signals must differ")
|
|
302
|
+
|
|
207
303
|
|
|
208
304
|
def check_assertions(
|
|
209
305
|
project: Path,
|
|
@@ -277,12 +373,18 @@ def verify_contract(contract_path: Path, project_path: Path) -> dict[str, Any]:
|
|
|
277
373
|
check_topology(contract, errors)
|
|
278
374
|
check_assertions(project, contract, errors, inputs)
|
|
279
375
|
clock = contract.get("clock", {})
|
|
376
|
+
shared = contract.get("shared_clock", {})
|
|
377
|
+
aec = contract.get("echo_cancellation", {})
|
|
280
378
|
return {
|
|
281
379
|
"ok": not errors,
|
|
282
380
|
"summary": (
|
|
283
381
|
f"sample_rate={clock.get('sample_rate_hz')}Hz "
|
|
284
|
-
f"mclk={clock.get('mclk_hz')}Hz ratio={clock.get('mclk_ratio')}"
|
|
382
|
+
f"mclk={clock.get('mclk_hz')}Hz ratio={clock.get('mclk_ratio')} "
|
|
383
|
+
f"simultaneous={shared.get('directions_simultaneous')} "
|
|
384
|
+
f"aec={aec.get('enabled', False)}"
|
|
285
385
|
),
|
|
386
|
+
"simultaneous_capture_playback": shared.get("directions_simultaneous") is True,
|
|
387
|
+
"echo_cancellation_enabled": aec.get("enabled") is True,
|
|
286
388
|
"inputs": inputs,
|
|
287
389
|
"errors": errors,
|
|
288
390
|
}
|