tan-cli 0.7.0__py3-none-any.whl
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.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Alp Lab AB
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* cold-chain-monitor
|
|
6
|
+
* ==================
|
|
7
|
+
*
|
|
8
|
+
* Pharma/food cold-chain integrity monitor. Pipeline:
|
|
9
|
+
*
|
|
10
|
+
* BME280 (I2C) --read_raw + compensate--> T(C) / RH(%) / P(Pa)
|
|
11
|
+
* --sliding window--> cold_chain (mean/min/max, MKT, dewpoint, excursion)
|
|
12
|
+
* -> cc_classify (OK / TEMP_EXCURSION / MKT_EXCEEDED / CONDENSATION_RISK)
|
|
13
|
+
* + <alp/inference.h> anomaly score (deterministic fallback)
|
|
14
|
+
* --> one CC record per report window.
|
|
15
|
+
*
|
|
16
|
+
* Honest scope: a reference cold-chain logger. MKT, dewpoint, and excursion
|
|
17
|
+
* time are the real metrics; thresholds are configurable per product. This is
|
|
18
|
+
* NOT a certified GxP / 21-CFR-Part-11 data logger (no validated audit trail,
|
|
19
|
+
* no calibration traceability, no tamper-proof storage). The model is a stub
|
|
20
|
+
* (see https://github.com/alplabai/alp-sdk/blob/v0.17.0/examples/ai/cold-chain-monitor/models/README.md); with no model the
|
|
21
|
+
* deterministic classifier + anomaly fallback run.
|
|
22
|
+
*
|
|
23
|
+
*
|
|
24
|
+
* ── Why the BME280 ────────────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* The Bosch BME280 is a combined temperature / humidity / barometric pressure
|
|
27
|
+
* sensor well suited to cold-chain loggers:
|
|
28
|
+
* - Temperature range -40..+85 C, accuracy ±1.0 C RMS, sufficient for
|
|
29
|
+
* the ±0.5 C inter-unit spread allowed by GxP-adjacent monitoring.
|
|
30
|
+
* - Humidity ±3% RH at 25 C enables dewpoint estimation (condensation /
|
|
31
|
+
* mould risk on packaging and metal surfaces).
|
|
32
|
+
* - Pressure is logged but not classified by this demo; it's useful for
|
|
33
|
+
* altitude compensation and for detecting seal-break events in insulated
|
|
34
|
+
* shippers where a damaged seal opens to ambient.
|
|
35
|
+
* - I2C single-chip integration keeps the BOM to a minimum; SPI arrives
|
|
36
|
+
* in the v0.3 driver.
|
|
37
|
+
* - Normal-mode continuous sampling at a user-selected standby interval
|
|
38
|
+
* eliminates the MCU-driven trigger polling needed in Forced mode.
|
|
39
|
+
*
|
|
40
|
+
*
|
|
41
|
+
* ── Measurement cadence and window sizing ─────────────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* CC_WINDOW_N (256 samples) * CC_SAMPLE_MIN (1.0 min/sample) = 256 minutes
|
|
44
|
+
* = ~4.3 hours per report window. That matches a common GDP-compliant
|
|
45
|
+
* logging interval (log every 5..15 min over a 24 h transit) while keeping
|
|
46
|
+
* the window SRAM footprint under 4 kB (256 × 12 B = 3072 B).
|
|
47
|
+
*
|
|
48
|
+
* In this demo the "1 minute" cadence is simulated: all 256 synthetic samples
|
|
49
|
+
* are pushed immediately so the pipeline exercises the framing path without
|
|
50
|
+
* sleeping. On real hardware a k_timer drives one bme280_read_raw() call per
|
|
51
|
+
* real minute (or whatever the product's logging period is).
|
|
52
|
+
*
|
|
53
|
+
*
|
|
54
|
+
* ── Cold-chain standards ──────────────────────────────────────────────────
|
|
55
|
+
*
|
|
56
|
+
* MKT (Mean Kinetic Temperature, ICH Q1A / USP <1079>):
|
|
57
|
+
* The single equivalent temperature that delivers the same cumulative
|
|
58
|
+
* Arrhenius thermal stress as the fluctuating temperature profile.
|
|
59
|
+
* A brief hot excursion contributes more than an equal-duration cool dip,
|
|
60
|
+
* so MKT >= arithmetic mean. The regulatory limit for most vaccines is
|
|
61
|
+
* MKT <= 8 C (the same as the storage upper bound), so a distribution that
|
|
62
|
+
* averaged 6 C but had a 12 C spike for 30 min would still breach.
|
|
63
|
+
* ΔH/R = 10 000 K is the ICH default (ΔH = 83.144 kJ/mol for pharma).
|
|
64
|
+
*
|
|
65
|
+
* Dewpoint (Magnus formula):
|
|
66
|
+
* When ambient temperature approaches the dewpoint, water condenses on
|
|
67
|
+
* cold surfaces -- packaging labels detach, metal contacts corrode,
|
|
68
|
+
* biological samples degrade. The dewpoint_margin_c config knob controls
|
|
69
|
+
* how close the reading must be to the dewpoint before CONDENSATION_RISK
|
|
70
|
+
* is raised.
|
|
71
|
+
*
|
|
72
|
+
*
|
|
73
|
+
* ── Classifier priority ordering ──────────────────────────────────────────
|
|
74
|
+
*
|
|
75
|
+
* The 4-state classifier (implemented in cold_chain.c) reports the HIGHEST
|
|
76
|
+
* severity condition present:
|
|
77
|
+
* CC_TEMP_EXCURSION -- product is currently out of the safe band, or
|
|
78
|
+
* the cumulative out-of-band time exceeds the limit.
|
|
79
|
+
* ACUTE: operator must act immediately.
|
|
80
|
+
* CC_MKT_EXCEEDED -- the equivalent thermal stress has crossed the
|
|
81
|
+
* regulatory limit even if the product is currently
|
|
82
|
+
* in-band. CUMULATIVE: product may be degraded.
|
|
83
|
+
* CC_CONDENSATION_RISK -- high humidity or temperature near the dewpoint;
|
|
84
|
+
* packaging and surface integrity may be compromised.
|
|
85
|
+
* CC_OK -- all metrics within limits.
|
|
86
|
+
*
|
|
87
|
+
*
|
|
88
|
+
* ── AI anomaly score ──────────────────────────────────────────────────────
|
|
89
|
+
*
|
|
90
|
+
* The threshold-based classifier catches known bad states. The AI score
|
|
91
|
+
* catches SUBTLE DRIFT that no single threshold catches: a slowly degrading
|
|
92
|
+
* compressor that nudges the mean temperature up by 0.3 C per week, or a
|
|
93
|
+
* door-seal micro-leak that correlates the humidity rise with the warming
|
|
94
|
+
* slope. The fallback (cc_anomaly_fallback) implements the same idea
|
|
95
|
+
* deterministically: it returns max(excursion depth, MKT overshoot),
|
|
96
|
+
* saturating at 1.0. A trained autoencoder running via <alp/inference.h>
|
|
97
|
+
* replaces this with a learned reconstruction error.
|
|
98
|
+
*
|
|
99
|
+
*
|
|
100
|
+
* ── native_sim / no-sensor fallback ──────────────────────────────────────
|
|
101
|
+
*
|
|
102
|
+
* On native_sim the I2C emul controller answers open() / ioctl() but has no
|
|
103
|
+
* BME280 target attached, so bme280_init() fails the CHIP_ID check. main()
|
|
104
|
+
* detects this and fills every window with synthetic environment data via
|
|
105
|
+
* synth_sample(), covering the three interesting states:
|
|
106
|
+
* window 0: stable in-band cold -> CC_OK
|
|
107
|
+
* window 1: warming ramp -> CC_TEMP_EXCURSION
|
|
108
|
+
* window 2: near-saturated air -> CC_CONDENSATION_RISK
|
|
109
|
+
* On real silicon fill the window from bme280_read_raw() instead.
|
|
110
|
+
*/
|
|
111
|
+
#include <string.h>
|
|
112
|
+
#include <zephyr/kernel.h>
|
|
113
|
+
#include <zephyr/logging/log.h>
|
|
114
|
+
|
|
115
|
+
/* Board-portable alias for the sensor I2C bus: BOARD_I2C_SENSORS resolves to
|
|
116
|
+
* the correct on-board I2C controller for whatever EVK is targeted via the
|
|
117
|
+
* CONFIG_COMPILER_OPT="-DALP_BOARD_E1M_EVK" build flag. */
|
|
118
|
+
#include "alp/board.h"
|
|
119
|
+
/* AI inference dispatch: AUTO routes to Ethos-U55 on AEN, DEEPX DX-M1 on V2N,
|
|
120
|
+
* and the reference TFLM CPU kernels on native_sim. */
|
|
121
|
+
#include "alp/inference.h"
|
|
122
|
+
/* Portable I2C open/close/read/write primitives (no vendor names). */
|
|
123
|
+
#include "alp/peripheral.h"
|
|
124
|
+
/* BME280 combined T/RH/P chip driver (I2C v0.2, paper-correct). */
|
|
125
|
+
#include "alp/chips/bme280.h"
|
|
126
|
+
|
|
127
|
+
/* Pure-C cold-chain metrics, classifier, and anomaly fallback (arch-neutral,
|
|
128
|
+
* no Zephyr deps -- identical source compiles for native_sim and the M55). */
|
|
129
|
+
#include "cold_chain.h"
|
|
130
|
+
|
|
131
|
+
LOG_MODULE_REGISTER(cc, LOG_LEVEL_INF);
|
|
132
|
+
|
|
133
|
+
/* BME280 I2C address: SDO-tied-low = 0x76 on the EVK (0x77 if SDO is high).
|
|
134
|
+
* Override at compile time if your carrier boards SDO differently. */
|
|
135
|
+
#define BME280_ADDR 0x76u
|
|
136
|
+
/* Number of report windows in the bounded demo run (3 covers OK / EXCURSION /
|
|
137
|
+
* CONDENSATION in the synthetic scenario; a real logger runs indefinitely). */
|
|
138
|
+
#define N_REPORTS 3
|
|
139
|
+
|
|
140
|
+
/* Vaccine-fridge thresholds (2..8 C, USP <1079> Annex 1 cold chain).
|
|
141
|
+
* A real deployment loads the limits for the specific product (frozen at
|
|
142
|
+
* -20 C, ambient-stable at +25 C, etc.) from NVS or a compile-time overlay. */
|
|
143
|
+
static const struct cc_config CFG = {
|
|
144
|
+
.t_lo = 2.0f, /* safe-band lower edge, Celsius */
|
|
145
|
+
.t_hi = 8.0f, /* safe-band upper edge, Celsius */
|
|
146
|
+
.mkt_limit_c = 8.0f, /* MKT above this -> cumulative damage */
|
|
147
|
+
.excursion_min_limit = 30.0f, /* out-of-band minutes before EXCURSION */
|
|
148
|
+
.dewpoint_margin_c = 2.0f, /* T within this of dewpoint -> CONDENSATION */
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/* 1-byte stub so alp_inference_open's non-NULL model-data contract is met.
|
|
152
|
+
* An unusable tensor (wrong shape from the stub backend) causes every
|
|
153
|
+
* alp_inference_invoke() to return no usable output, which anomaly_score()
|
|
154
|
+
* detects and routes to cc_anomaly_fallback(). See
|
|
155
|
+
* https://github.com/alplabai/alp-sdk/blob/v0.17.0/examples/ai/cold-chain-monitor/models/README.md for the autoencoder
|
|
156
|
+
* training recipe to replace this stub. */
|
|
157
|
+
static const uint8_t s_model[] = { 0x00 };
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
* synth_sample() -- synthetic environment data for native_sim / no sensor.
|
|
161
|
+
*
|
|
162
|
+
* Generates a deterministic (T, RH, P) triple for sample @p i of report
|
|
163
|
+
* window @p report. Three scenarios are exercised across the N_REPORTS
|
|
164
|
+
* windows so the classifier produces a meaningful state spread:
|
|
165
|
+
*
|
|
166
|
+
* report 0: steady in-band cold storage (5 C, 50 % RH) -> CC_OK
|
|
167
|
+
* report 1: warming ramp (5..12 C linear) -> CC_TEMP_EXCURSION
|
|
168
|
+
* report 2: cold but near-saturated air (5 C, 95 % RH) -> CC_CONDENSATION_RISK
|
|
169
|
+
*
|
|
170
|
+
* Pressure is fixed at 101325 Pa (sea-level standard) for all windows;
|
|
171
|
+
* it does not affect classification in this demo.
|
|
172
|
+
*/
|
|
173
|
+
static struct cc_sample synth_sample(int report, int i)
|
|
174
|
+
{
|
|
175
|
+
/* Baseline: stable cold storage in the 2..8 C vaccine fridge band. */
|
|
176
|
+
struct cc_sample s = { .temp_c = 5.0f, .rh_pct = 50.0f, .pressure_pa = 101325.0f };
|
|
177
|
+
switch (report) {
|
|
178
|
+
case 0:
|
|
179
|
+
break; /* stable cold -- all defaults are fine */
|
|
180
|
+
case 1:
|
|
181
|
+
/* Linear warming ramp from 5 C to 12 C across the window.
|
|
182
|
+
* This drives the mean above t_hi and the excursion counter
|
|
183
|
+
* past the limit, triggering CC_TEMP_EXCURSION. */
|
|
184
|
+
s.temp_c = 5.0f + 7.0f * (float)i / (float)(CC_WINDOW_N - 1);
|
|
185
|
+
s.rh_pct = 55.0f;
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
/* Near-saturated cold air: temperature stays in-band but RH
|
|
189
|
+
* is high enough that the Magnus dewpoint is within
|
|
190
|
+
* dewpoint_margin_c of the ambient temperature, raising
|
|
191
|
+
* CC_CONDENSATION_RISK. */
|
|
192
|
+
s.rh_pct = 95.0f;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
return s;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* anomaly_score() -- AI model if available, else the deterministic fallback.
|
|
200
|
+
*
|
|
201
|
+
* Packs the cc_features vector, invokes the loaded model, and reads the
|
|
202
|
+
* first float output as a 0..1 anomaly score. Falls back to
|
|
203
|
+
* cc_anomaly_fallback() when:
|
|
204
|
+
* - @p inf is NULL (inference handle failed to open),
|
|
205
|
+
* - the stub model returns an empty / wrongly-typed output tensor, or
|
|
206
|
+
* - alp_inference_invoke() returns a non-OK status.
|
|
207
|
+
* This "try model, fall back cleanly" pattern matches the sibling examples
|
|
208
|
+
* and means the demo always produces a meaningful score offline.
|
|
209
|
+
*/
|
|
210
|
+
static float anomaly_score(alp_inference_t *inf, const struct cc_features *f)
|
|
211
|
+
{
|
|
212
|
+
if (inf != NULL) {
|
|
213
|
+
/* Pack the 8 metrics into the flat float vector the model expects. */
|
|
214
|
+
float vec[CC_FEATURE_DIM];
|
|
215
|
+
(void)cc_feat_pack(f, vec, CC_FEATURE_DIM);
|
|
216
|
+
alp_inference_tensor_t in = { 0 };
|
|
217
|
+
/* Copy into the model's input tensor only if the shape matches. */
|
|
218
|
+
if (alp_inference_get_input(inf, 0, &in) == ALP_OK && in.dtype == ALP_INFERENCE_DTYPE_F32 &&
|
|
219
|
+
in.data != NULL && in.size_bytes >= sizeof(vec)) {
|
|
220
|
+
memcpy(in.data, vec, sizeof(vec));
|
|
221
|
+
if (alp_inference_invoke(inf) == ALP_OK) {
|
|
222
|
+
alp_inference_tensor_t out = { 0 };
|
|
223
|
+
/* Read the scalar anomaly score from the output tensor. */
|
|
224
|
+
if (alp_inference_get_output(inf, 0, &out) == ALP_OK &&
|
|
225
|
+
out.dtype == ALP_INFERENCE_DTYPE_F32 && out.data != NULL &&
|
|
226
|
+
out.size_bytes >= sizeof(float)) {
|
|
227
|
+
return ((const float *)out.data)[0];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/* Deterministic fallback: max(excursion depth, MKT overshoot), clamped
|
|
233
|
+
* to 1.0. Transparent, always correct, gives the demo a meaningful
|
|
234
|
+
* score without any trained model. */
|
|
235
|
+
return cc_anomaly_fallback(f, &CFG);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
int main(void)
|
|
239
|
+
{
|
|
240
|
+
/* Driver context and window state are static so they do not consume
|
|
241
|
+
* stack on the Zephyr M55 thread (the BME280 calib struct is 26 bytes;
|
|
242
|
+
* the window is 256 × 12 B = 3072 B). */
|
|
243
|
+
static bme280_t dev;
|
|
244
|
+
static struct cc_window_state win;
|
|
245
|
+
bool sensor_ok = false;
|
|
246
|
+
|
|
247
|
+
/* ── Sensor bring-up ─────────────────────────────────────────────────
|
|
248
|
+
* Open the shared sensor I2C bus (BOARD_I2C_SENSORS resolves to the
|
|
249
|
+
* correct on-board controller for the target EVK) and initialise the
|
|
250
|
+
* BME280. bme280_init() reads CHIP_ID and loads the per-die calibration
|
|
251
|
+
* coefficients; it returns ALP_ERR_IO if no BME280 responds.
|
|
252
|
+
* On native_sim the emul controller has no target attached, so init
|
|
253
|
+
* fails cleanly and we fall back to synth_sample(). */
|
|
254
|
+
alp_i2c_t *bus =
|
|
255
|
+
alp_i2c_open(&(alp_i2c_config_t){ .bus_id = BOARD_I2C_SENSORS, .bitrate_hz = 400000 });
|
|
256
|
+
if (bus != NULL && bme280_init(&dev, bus, BME280_ADDR) == ALP_OK) {
|
|
257
|
+
/* bme280_init() leaves the oversampling / mode registers untouched.
|
|
258
|
+
* Configure continuous-conversion (NORMAL) mode with X1 oversampling
|
|
259
|
+
* on all three channels (T, P, H), a 1 s standby period between
|
|
260
|
+
* measurements, and no IIR filter. bme280_read_raw() will then
|
|
261
|
+
* return a fresh reading without needing to poll the status register.
|
|
262
|
+
* For a cold-chain logger the 1 s cadence is already faster than the
|
|
263
|
+
* CC_SAMPLE_MIN (1 min) window rate, so X1/1 s is well matched. */
|
|
264
|
+
(void)bme280_set_sampling(&dev,
|
|
265
|
+
BME280_OVERSAMPLING_X1, /* temperature channel */
|
|
266
|
+
BME280_OVERSAMPLING_X1, /* pressure channel */
|
|
267
|
+
BME280_OVERSAMPLING_X1, /* humidity channel */
|
|
268
|
+
BME280_MODE_NORMAL, /* continuous conversion */
|
|
269
|
+
BME280_STANDBY_1000_MS, /* 1 s between measurements */
|
|
270
|
+
BME280_FILTER_OFF); /* no IIR smoothing */
|
|
271
|
+
sensor_ok = true;
|
|
272
|
+
} else {
|
|
273
|
+
LOG_WRN("BME280 unavailable; using synthetic environment data");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* ── Inference bring-up ──────────────────────────────────────────────
|
|
277
|
+
* Open the inference handle. BACKEND_AUTO selects Ethos-U on AEN,
|
|
278
|
+
* DEEPX DX-M1 on V2N, or the TFLM CPU kernels on native_sim.
|
|
279
|
+
* The 1-byte stub model causes every invoke() to return an unusable
|
|
280
|
+
* output, so anomaly_score() falls back to the deterministic path. */
|
|
281
|
+
alp_inference_t *inf = alp_inference_open(&(alp_inference_config_t){
|
|
282
|
+
.backend = ALP_INFERENCE_BACKEND_AUTO,
|
|
283
|
+
.format = ALP_INFERENCE_MODEL_TFLITE,
|
|
284
|
+
.model_data = s_model,
|
|
285
|
+
.model_size = sizeof(s_model),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/* CSV header: one line per report window. Field order matches the
|
|
289
|
+
* documented output schema and the README example. */
|
|
290
|
+
printk("# CC,t_s,state,temp_c,rh_pct,dewpoint_c,mkt_c,excursion_min\n");
|
|
291
|
+
|
|
292
|
+
/* ── Report loop ─────────────────────────────────────────────────────
|
|
293
|
+
* Each iteration resets the window, fills it (real or synthetic), then
|
|
294
|
+
* reduces it to metrics and emits one CC record. N_REPORTS = 3 covers
|
|
295
|
+
* the OK / EXCURSION / CONDENSATION states in the synthetic scenario. */
|
|
296
|
+
for (int r = 0; r < N_REPORTS; r++) {
|
|
297
|
+
/* Reset the window for this report period. */
|
|
298
|
+
cc_window_reset(&win);
|
|
299
|
+
|
|
300
|
+
/* Fill one window of readings (real sensor or synthetic). */
|
|
301
|
+
for (int i = 0; i < CC_WINDOW_N; i++) {
|
|
302
|
+
struct cc_sample s;
|
|
303
|
+
if (sensor_ok) {
|
|
304
|
+
/* Real sensor path: burst-read the raw 20/20/16-bit
|
|
305
|
+
* conversion result, then compensate with the per-die
|
|
306
|
+
* calibration loaded at init. The BME280 is running
|
|
307
|
+
* continuously (NORMAL mode), so the result is fresh
|
|
308
|
+
* within the 1 s standby period. */
|
|
309
|
+
bme280_raw_t raw;
|
|
310
|
+
bme280_compensated_t c;
|
|
311
|
+
if (bme280_read_raw(&dev, &raw) == ALP_OK &&
|
|
312
|
+
bme280_compensate(&dev, &raw, &c) == ALP_OK) {
|
|
313
|
+
/* Convert from fixed-point to float physical units:
|
|
314
|
+
* temperature_c100 is degrees × 100 -> C
|
|
315
|
+
* humidity_milli_pct is %RH × 1024 -> %
|
|
316
|
+
* pressure_pa is already pascals. */
|
|
317
|
+
s.temp_c = (float)c.temperature_c100 / 100.0f;
|
|
318
|
+
s.rh_pct = (float)c.humidity_milli_pct / 1024.0f;
|
|
319
|
+
s.pressure_pa = (float)c.pressure_pa;
|
|
320
|
+
} else {
|
|
321
|
+
/* I/O error mid-window: fall back to synth for
|
|
322
|
+
* this sample so the window stays complete. */
|
|
323
|
+
s = synth_sample(r, i);
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
/* No sensor: generate deterministic synthetic data. */
|
|
327
|
+
s = synth_sample(r, i);
|
|
328
|
+
}
|
|
329
|
+
cc_window_push(&win, s);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Reduce the window to metrics, classify, score the anomaly, emit. */
|
|
333
|
+
struct cc_features f;
|
|
334
|
+
cc_feat_extract(&win, &CFG, &f);
|
|
335
|
+
cc_state_t st = cc_classify(&f, &CFG);
|
|
336
|
+
/* Compute the anomaly score (model or deterministic fallback).
|
|
337
|
+
* In a real gateway deployment `an` is forwarded via MQTT / OPC-UA
|
|
338
|
+
* alongside the classifier state; here we suppress the unused-var
|
|
339
|
+
* warning with (void) since this demo only emits the state string. */
|
|
340
|
+
float an = anomaly_score(inf, &f);
|
|
341
|
+
(void)an;
|
|
342
|
+
|
|
343
|
+
/* Emit one CSV record. t_s = elapsed monitoring time in minutes
|
|
344
|
+
* = samples-completed × CC_SAMPLE_MIN (1.0 min/sample). */
|
|
345
|
+
printk("CC,%.1f,%s,%.1f,%.1f,%.1f,%.1f,%.1f\n",
|
|
346
|
+
(double)((r + 1) * CC_WINDOW_N * CC_SAMPLE_MIN),
|
|
347
|
+
cc_state_name(st),
|
|
348
|
+
(double)f.mean_temp_c,
|
|
349
|
+
(double)f.mean_rh_pct,
|
|
350
|
+
(double)f.dewpoint_c,
|
|
351
|
+
(double)f.mkt_c,
|
|
352
|
+
(double)f.excursion_min);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* ── Lifecycle teardown ──────────────────────────────────────────────
|
|
356
|
+
* Release in reverse acquisition order: model, sensor, bus.
|
|
357
|
+
* bme280_soft_reset() writes 0xB6 to the RESET register so the chip
|
|
358
|
+
* returns to its power-on state; the bus is then safe to release. */
|
|
359
|
+
if (inf != NULL) {
|
|
360
|
+
alp_inference_close(inf);
|
|
361
|
+
}
|
|
362
|
+
if (sensor_ok) {
|
|
363
|
+
/* Soft-reset the chip before releasing the bus so it stops
|
|
364
|
+
* converting and returns to sleep (lowest power state). */
|
|
365
|
+
bme280_soft_reset(&dev);
|
|
366
|
+
bme280_deinit(&dev); /* Release the driver context (no chip power-down). */
|
|
367
|
+
}
|
|
368
|
+
if (bus != NULL) {
|
|
369
|
+
alp_i2c_close(bus);
|
|
370
|
+
}
|
|
371
|
+
printk("[cc] done\n");
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
sample:
|
|
4
|
+
name: cold-chain-monitor
|
|
5
|
+
description: |
|
|
6
|
+
Cold-chain integrity monitor: BME280 T/RH/P -> sliding-window metrics
|
|
7
|
+
(MKT / dewpoint / excursion time) -> 4-state classifier (OK / TEMP_EXCURSION
|
|
8
|
+
/ MKT_EXCEEDED / CONDENSATION_RISK) + AI anomaly score (deterministic
|
|
9
|
+
fallback). native_sim runs synthetic environment data covering the states.
|
|
10
|
+
common:
|
|
11
|
+
tags: ai inference industrial environmental cold-chain marketing showcase
|
|
12
|
+
tests:
|
|
13
|
+
alp_sdk.example.cold_chain_monitor.e1m_evk:
|
|
14
|
+
extra_args: EXTRA_CONF_FILE=generated/alp.conf
|
|
15
|
+
extra_configs:
|
|
16
|
+
- 'CONFIG_COMPILER_OPT="-DALP_BOARD_E1M_EVK"'
|
|
17
|
+
platform_allow:
|
|
18
|
+
- native_sim/native/64
|
|
19
|
+
integration_platforms:
|
|
20
|
+
- native_sim/native/64
|
|
21
|
+
tags:
|
|
22
|
+
- alp-sdk
|
|
23
|
+
- example
|
|
24
|
+
- ai
|
|
25
|
+
- environmental
|
|
26
|
+
harness: console
|
|
27
|
+
harness_config:
|
|
28
|
+
type: one_line
|
|
29
|
+
regex:
|
|
30
|
+
- "\\[cc\\] done"
|
|
31
|
+
|
|
32
|
+
alp_sdk.example.cold_chain_monitor.e1m_x_evk:
|
|
33
|
+
extra_args: EXTRA_CONF_FILE=generated/alp.conf
|
|
34
|
+
extra_configs:
|
|
35
|
+
- 'CONFIG_COMPILER_OPT="-DALP_BOARD_E1M_X_EVK"'
|
|
36
|
+
platform_allow:
|
|
37
|
+
- native_sim/native/64
|
|
38
|
+
integration_platforms:
|
|
39
|
+
- native_sim/native/64
|
|
40
|
+
tags:
|
|
41
|
+
- alp-sdk
|
|
42
|
+
- example
|
|
43
|
+
- ai
|
|
44
|
+
- environmental
|
|
45
|
+
harness: console
|
|
46
|
+
harness_config:
|
|
47
|
+
type: one_line
|
|
48
|
+
regex:
|
|
49
|
+
- "\\[cc\\] done"
|
|
50
|
+
|
|
51
|
+
alp_sdk.example.cold_chain_monitor.aen_build:
|
|
52
|
+
extra_args: EXTRA_CONF_FILE=generated/alp.conf
|
|
53
|
+
extra_configs:
|
|
54
|
+
- 'CONFIG_COMPILER_OPT="-DALP_BOARD_E1M_EVK"'
|
|
55
|
+
platform_allow:
|
|
56
|
+
- alp_e1m_aen801_m55_hp/ae822fa0e5597ls0/rtss_hp
|
|
57
|
+
build_only: true
|
|
58
|
+
tags:
|
|
59
|
+
- alp-sdk
|
|
60
|
+
- example
|
|
61
|
+
- ai
|
|
62
|
+
- environmental
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
cmake_minimum_required(VERSION 3.20)
|
|
4
|
+
|
|
5
|
+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
|
6
|
+
project(mqtt_telemetry LANGUAGES C)
|
|
7
|
+
|
|
8
|
+
target_sources(app PRIVATE src/main.c)
|
|
9
|
+
# Reusable SoM bring-up helper (cc3501e_bridge_bringup): the one-call CC3501E
|
|
10
|
+
# bridge init this app uses so alp_wifi_open() can bind to a ready bridge on
|
|
11
|
+
# E1M-AEN801 (#2112). Copyable template, same file as examples/aen/aen-cc3501e-*
|
|
12
|
+
# -- see src/cc3501e_bridge.h. Built unconditionally; alp_gpio_open()/alp_spi_open()
|
|
13
|
+
# fail fast (NULL, no matching backend) on native_sim, so this is a no-op there.
|
|
14
|
+
target_sources(app PRIVATE src/cc3501e_bridge.c)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# mqtt-telemetry
|
|
2
|
+
|
|
3
|
+
The **connected device** starting point: bring up Wi-Fi, open an
|
|
4
|
+
`mqtts://` (TLS) MQTT client, and publish a telemetry reading on a
|
|
5
|
+
cadence -- the whole path on the portable [`<alp/iot.h>`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/include/alp/iot.h)
|
|
6
|
+
surface.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
alp_wifi_open() -> alp_wifi_connect() # Wi-Fi station up
|
|
10
|
+
alp_mqtt_open(mqtts://... , CA pinned) # TLS MQTT client
|
|
11
|
+
alp_mqtt_connect() -> alp_mqtt_publish() x N # publish telemetry
|
|
12
|
+
alp_mqtt_close() # clean disconnect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Supported hardware -- AEN only
|
|
16
|
+
|
|
17
|
+
This template ships for **E1M-AEN801 only**. Its transport is the
|
|
18
|
+
**CC3501E Wi-Fi6+BLE coprocessor bridge**
|
|
19
|
+
([`docs/cc3501e-bridge.md`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/docs/cc3501e-bridge.md)),
|
|
20
|
+
silicon-validated 2026-06-24. The app never names the bridge -- the
|
|
21
|
+
AEN board emit wires it as the `<alp/iot.h>` Wi-Fi backend from
|
|
22
|
+
`iot.wifi: true` in `board.yaml`.
|
|
23
|
+
|
|
24
|
+
The **E1M-V2N101 is deliberately not supported**: its Murata Wi-Fi
|
|
25
|
+
(`murata_lbee5hy2fy`) is `hil_silicon: untested` and its Linux-side
|
|
26
|
+
data path is an unmerged design, so there is no working Wi-Fi
|
|
27
|
+
transport for that family yet. Do not add it to a scaffold of this
|
|
28
|
+
template until that port lands.
|
|
29
|
+
|
|
30
|
+
## TLS status -- preview
|
|
31
|
+
|
|
32
|
+
The `mqtts://` path is configured through the portable API (broker URI + a
|
|
33
|
+
pinned CA) and **mbedTLS is now built in on every target**, native_sim
|
|
34
|
+
included. It used to be held off: with mbedTLS' PSA core disabled the pinned
|
|
35
|
+
library's own `ssl_misc.h` does not compile (`unknown type name
|
|
36
|
+
'mbedtls_error_pair_t'`), so the app turned mbedTLS off rather than fail. The
|
|
37
|
+
SDK now turns that PSA core on wherever it builds mbedTLS without TF-M
|
|
38
|
+
(`ALP_SDK_MBEDTLS_PSA_CRYPTO`, issue #2173), so this app carries no mbedTLS
|
|
39
|
+
knobs at all any more (see [`prj.conf`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/examples/connectivity/mqtt-telemetry/prj.conf), which is
|
|
40
|
+
empty by design) and the `native_sim.conf` that used to hold the workaround is
|
|
41
|
+
deleted.
|
|
42
|
+
|
|
43
|
+
AEN hardware builds seed the PSA core from the Secure Enclave TRNG entropy
|
|
44
|
+
driver (`alif,se-trng`, bench-proven on both M55 cores, issue #2192), which
|
|
45
|
+
every AEN board chooses as `zephyr,entropy` by default, so the AEN Twister
|
|
46
|
+
scenario needs no weak-RNG opt-in. Only the native_sim scenario, which has no
|
|
47
|
+
real entropy source, still sets `CONFIG_ALP_SDK_ALLOW_TEST_ENTROPY=y`.
|
|
48
|
+
|
|
49
|
+
## The "sensor reading"
|
|
50
|
+
|
|
51
|
+
To keep the focus on the transport, this template publishes a
|
|
52
|
+
**synthetic metric** (device uptime). Swap
|
|
53
|
+
`read_telemetry_value()` for a real sensor read -- e.g. compose it
|
|
54
|
+
with the [`sensor` template](https://github.com/alplabai/alp-sdk/tree/v0.17.0/examples/peripheral-io/i2c-master) (BMP581
|
|
55
|
+
over `<alp/chips/bmp581.h>`) -- and the publish path is unchanged.
|
|
56
|
+
|
|
57
|
+
## Build
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Standalone, native_sim (no radio, so the app prints the framing it
|
|
61
|
+
# would publish; mbedTLS is built in):
|
|
62
|
+
# writes ./generated/alp.conf, which west reads below (#866)
|
|
63
|
+
tan generate --target zephyr-conf --core m55_hp --sdk-root "$ALP_SDK_ROOT" --output generated/alp.conf
|
|
64
|
+
west build -b native_sim/native/64 . \
|
|
65
|
+
-- -DEXTRA_CONF_FILE=generated/alp.conf -DEXTRA_ZEPHYR_MODULES=$ALP_SDK_ROOT
|
|
66
|
+
west build -t run
|
|
67
|
+
|
|
68
|
+
# On real silicon (E1M-AEN801):
|
|
69
|
+
west build -b alp_e1m_aen801_m55_hp/ae822fa0e5597ls0/rtss_hp . -- -DEXTRA_CONF_FILE=generated/alp.conf
|
|
70
|
+
west flash
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Expected output
|
|
74
|
+
|
|
75
|
+
native_sim (no Wi-Fi/MQTT backend; framing only):
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
[mqtt] alp-sdk mqtt-telemetry demo (publish over MQTT/TLS)
|
|
79
|
+
[mqtt] transport: CC3501E Wi-Fi bridge (E1M-AEN801)
|
|
80
|
+
[mqtt] wifi: opening station
|
|
81
|
+
[mqtt] wifi: alp_wifi_open -> NULL (ALP_ERR_NOT_READY)
|
|
82
|
+
[mqtt] no transport on this build -- printing framing only
|
|
83
|
+
[mqtt] would publish to alp/telemetry/e1m-aen801: {"device":"e1m-aen801-demo","metric":"uptime_s","value":0}
|
|
84
|
+
[mqtt] would publish to alp/telemetry/e1m-aen801: {"device":"e1m-aen801-demo","metric":"uptime_s","value":5}
|
|
85
|
+
[mqtt] would publish to alp/telemetry/e1m-aen801: {"device":"e1m-aen801-demo","metric":"uptime_s","value":10}
|
|
86
|
+
[mqtt] done
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Real hardware (E1M-AEN801, associated + broker reachable):
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
[mqtt] wifi: associated to "alp-demo-ap"
|
|
93
|
+
[mqtt] broker: connected
|
|
94
|
+
[mqtt] publish 0 -> ALP_OK: {"device":"e1m-aen801-demo","metric":"uptime_s","value":0}
|
|
95
|
+
...
|
|
96
|
+
[mqtt] done
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Reference
|
|
100
|
+
|
|
101
|
+
- [`<alp/iot.h>`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/include/alp/iot.h) -- Wi-Fi station + MQTT client surface.
|
|
102
|
+
- [`docs/cc3501e-bridge.md`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/docs/cc3501e-bridge.md) -- the AEN Wi-Fi transport.
|
|
103
|
+
- [`examples/peripheral-io/i2c-master/`](https://github.com/alplabai/alp-sdk/tree/v0.17.0/examples/peripheral-io/i2c-master) -- the `sensor` template, for a real reading to publish.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# board.yaml -- per-app project configuration for the
|
|
2
|
+
# mqtt-telemetry example (publish a sensor reading over MQTT/TLS).
|
|
3
|
+
#
|
|
4
|
+
# The "connected device" starting point: bring up the Wi-Fi
|
|
5
|
+
# station, open an `mqtts://` (TLS) MQTT client via the portable
|
|
6
|
+
# <alp/iot.h> surface, and publish a telemetry reading on a
|
|
7
|
+
# cadence. All app code stays on <alp/iot.h> -- the transport
|
|
8
|
+
# underneath is the CC3501E Wi-Fi6+BLE coprocessor bridge on the
|
|
9
|
+
# E1M-AEN801 (see https://github.com/alplabai/alp-sdk/blob/v0.17.0/docs/cc3501e-bridge.md), selected automatically
|
|
10
|
+
# by the AEN board emit; no app change is needed to target it.
|
|
11
|
+
#
|
|
12
|
+
# AEN-only by design: the E1M-V2N101's Murata Wi-Fi
|
|
13
|
+
# (murata_lbee5hy2fy) is `hil_silicon: untested` and its Linux-side
|
|
14
|
+
# data path is an unmerged design, so there is no working Wi-Fi
|
|
15
|
+
# transport for that family yet. Shipping this template for the one
|
|
16
|
+
# family whose transport is silicon-proven (AEN, validated
|
|
17
|
+
# 2026-06-24) is deliberate -- see the catalog record's `preview`
|
|
18
|
+
# note. Do not add E1M-V2N101 to a scaffold of this template until
|
|
19
|
+
# that port lands.
|
|
20
|
+
#
|
|
21
|
+
# Customer workflow: `tan init --from-example connectivity/mqtt-telemetry`,
|
|
22
|
+
# set your SSID/PSK + broker URI in src/main.c (or wire them from
|
|
23
|
+
# provisioning), and `tan build` for the AEN board target.
|
|
24
|
+
libraries:
|
|
25
|
+
# mbedtls backs the `mqtts://` TLS path. The v0.6 board.yaml
|
|
26
|
+
# cross-field validator requires `mbedtls` (or `bearssl`) in
|
|
27
|
+
# `libraries:` whenever any core sets `iot.tls: true`.
|
|
28
|
+
- name: mbedtls
|
|
29
|
+
cores: [m55_hp]
|
|
30
|
+
|
|
31
|
+
som:
|
|
32
|
+
sku: E1M-AEN801 # Alif Ensemble E8 + on-module CC3501E
|
|
33
|
+
# Wi-Fi6+BLE bridge (the transport).
|
|
34
|
+
|
|
35
|
+
preset: e1m-evk
|
|
36
|
+
|
|
37
|
+
cores:
|
|
38
|
+
m55_hp:
|
|
39
|
+
app: ./src
|
|
40
|
+
# IoT toggles. wifi:true wires the CC3501E bridge as the
|
|
41
|
+
# <alp/iot.h> Wi-Fi backend on this AEN board; tls:true selects
|
|
42
|
+
# the mqtts:// (TLS) MQTT path. CONFIG_MBEDTLS is no longer held
|
|
43
|
+
# off: the ssl_misc.h break that forced that is fixed at its root
|
|
44
|
+
# (ALP_SDK_MBEDTLS_PSA_CRYPTO, #2173), so every target builds the
|
|
45
|
+
# TLS-enabled configuration. The remaining limit is the RNG, not
|
|
46
|
+
# the library -- see the README's TLS status section and #2192.
|
|
47
|
+
iot:
|
|
48
|
+
wifi: true
|
|
49
|
+
tls: true
|
|
50
|
+
# Portable peripheral subsystems this app opens to bring the CC3501E
|
|
51
|
+
# bridge up (cc3501e_bridge_bringup(), #2112): SPI1 (inter-chip link)
|
|
52
|
+
# + the WIFI_EN/nRESET LP-GPIO control pins. Same as the
|
|
53
|
+
# https://github.com/alplabai/alp-sdk/tree/v0.17.0/examples/aen/aen-cc3501e-* board.yaml. The loader translates these
|
|
54
|
+
# to CONFIG_SPI=y / CONFIG_GPIO=y in alp.conf.
|
|
55
|
+
peripherals:
|
|
56
|
+
- spi
|
|
57
|
+
- gpio
|
|
58
|
+
|
|
59
|
+
# The bridge helper this app now carries (src/cc3501e_bridge.h, #2112)
|
|
60
|
+
# includes <alp/chips/cc3501e.h> directly, so the chip has to be declared
|
|
61
|
+
# here for check_example_portability.py's family-compatibility check and
|
|
62
|
+
# the portability ring to account for it -- same as
|
|
63
|
+
# https://github.com/alplabai/alp-sdk/blob/v0.17.0/examples/aen/aen-cc3501e-bringup/board.yaml. Wi-Fi itself still reaches
|
|
64
|
+
# the app through the portable <alp/iot.h> surface, not this entry.
|
|
65
|
+
chips:
|
|
66
|
+
- cc3501e
|
|
67
|
+
|
|
68
|
+
diagnostics:
|
|
69
|
+
log_level: info
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# Empty-by-design Kconfig. The effective build config is the
|
|
4
|
+
# overlay alp.conf emitted from board.yaml by `tan build`
|
|
5
|
+
# at build time (see README.md: `tan generate --target zephyr-conf`) -- it turns the
|
|
6
|
+
# `iot: {wifi, tls}` toggles + `libraries: [mbedtls]` into the
|
|
7
|
+
# CONFIG_* the app needs (Wi-Fi, the MQTT client, mbedtls).
|
|
8
|
+
#
|
|
9
|
+
# mbedtls used to be forced OFF here and in a native_sim.conf, because
|
|
10
|
+
# the pinned mbedtls' ssl_misc.h uses `mbedtls_error_pair_t` with PSA
|
|
11
|
+
# crypto off and does not compile. That is fixed at its root -- the SDK
|
|
12
|
+
# now turns mbedtls' PSA core on with mbedtls itself, see
|
|
13
|
+
# ALP_SDK_MBEDTLS_PSA_CRYPTO in zephyr/Kconfig.alp-libraries (#2173) --
|
|
14
|
+
# so every target, native_sim included, builds the same TLS-enabled
|
|
15
|
+
# configuration the SoM does.
|