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,75 @@
|
|
|
1
|
+
# cold-chain-monitor
|
|
2
|
+
|
|
3
|
+
> **`[UNTESTED]` on hardware -- v0.9 paper-correct.** The `cold_chain` core
|
|
4
|
+
> is host-unit-tested on `native_sim/native/64`; the full app runs end-to-end on
|
|
5
|
+
> native_sim with synthetic environment data covering the states. HiL with a
|
|
6
|
+
> real BME280 + a trained model is bench-gated.
|
|
7
|
+
|
|
8
|
+
A pharma/food **cold-chain integrity monitor**: sample a BME280 T/RH/P sensor on
|
|
9
|
+
a slow logging cadence, compute the standards-backed metrics (temperature
|
|
10
|
+
excursion time, Mean Kinetic Temperature, dewpoint), classify the integrity
|
|
11
|
+
state, and emit an AI anomaly score. The *environmental* edge-AI vertical.
|
|
12
|
+
|
|
13
|
+
## Honest scope
|
|
14
|
+
|
|
15
|
+
A reference cold-chain logger. MKT, dewpoint, and excursion-time are the real,
|
|
16
|
+
recognized metrics; thresholds are configurable per product. **NOT** a certified
|
|
17
|
+
GxP / 21-CFR-Part-11 data logger -- no validated audit trail, no calibration
|
|
18
|
+
traceability, no tamper-proof storage.
|
|
19
|
+
|
|
20
|
+
## Standards-backed metrics
|
|
21
|
+
|
|
22
|
+
- **MKT** (Mean Kinetic Temperature, ICH Q1A / USP <1079>): the single
|
|
23
|
+
temperature delivering the same cumulative thermal stress as the fluctuating
|
|
24
|
+
profile. `MKT >= arithmetic mean`, so a brief hot spike counts for more than
|
|
25
|
+
its duration -- which is the whole point.
|
|
26
|
+
- **Dewpoint** (Magnus): condensation / mould risk when ambient T nears it.
|
|
27
|
+
|
|
28
|
+
## Pipeline
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
BME280 (I2C) --window--> cold_chain (mean/min/max, MKT, dewpoint, excursion)
|
|
32
|
+
-> cc_classify (OK / TEMP_EXCURSION / MKT_EXCEEDED / CONDENSATION_RISK)
|
|
33
|
+
-> <alp/inference.h> anomaly score (deterministic fallback) -> CC record
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Output
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# CC,t_s,state,temp_c,rh_pct,dewpoint_c,mkt_c,excursion_min
|
|
40
|
+
CC,256.0,OK,5.0,50.0,-4.6,5.0,0.0
|
|
41
|
+
CC,768.0,CONDENSATION_RISK,5.0,95.0,4.3,5.0,0.0
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Build
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
# writes ./generated/alp.conf, which west reads below (#866)
|
|
48
|
+
tan generate --target zephyr-conf --core m33_sm --sdk-root "$ALP_SDK_ROOT" --output generated/alp.conf
|
|
49
|
+
west build -b alp_e1m_v2n101_m33_sm/r9a09g056n48gbg/cm33 . -- -DEXTRA_CONF_FILE=generated/alp.conf
|
|
50
|
+
west flash --host <board-ip>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The DEEPX DX-M1 NPU is populated on `E1M-V2M101`/`E1M-V2M102`/`E1M-V2M103`
|
|
54
|
+
-- not on `E1M-V2N101`/`E1M-V2N102`/`E1M-V2N103`, the same PCB without
|
|
55
|
+
it. Pick either via
|
|
56
|
+
`som.sku` in `board.yaml`.
|
|
57
|
+
|
|
58
|
+
## Model
|
|
59
|
+
|
|
60
|
+
<!-- The ../cold-chain-monitor/ detour is deliberate: the scaffold
|
|
61
|
+
rewriter's _RELATIVE_LINK_RE only matches `../`-prefixed links, and
|
|
62
|
+
models/ is a child of this dir, not a sibling -- don't "fix" this. -->
|
|
63
|
+
|
|
64
|
+
No model is shipped (stub + deterministic classifier/fallback). See
|
|
65
|
+
[`models/README.md`](https://github.com/alplabai/alp-sdk/blob/v0.17.0/examples/ai/cold-chain-monitor/models/README.md) for the
|
|
66
|
+
autoencoder training recipe.
|
|
67
|
+
|
|
68
|
+
## Tests
|
|
69
|
+
|
|
70
|
+
In the alp-sdk tree, unit tests live in
|
|
71
|
+
[`tests/unit/cold_chain`](https://github.com/alplabai/alp-sdk/tree/v0.17.0/tests/unit/cold_chain) and run with:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
twister -p native_sim/native/64 -T tests/unit/cold_chain
|
|
75
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# board.yaml -- cold-chain integrity monitor.
|
|
2
|
+
#
|
|
3
|
+
# A low-power M55 node reads the on-board BME280 temperature/humidity/pressure
|
|
4
|
+
# sensor on a slow logging cadence, computes the cold-chain metrics (MKT,
|
|
5
|
+
# dewpoint, excursion time), and classifies the integrity state. Same source
|
|
6
|
+
# targets the V2N DEEPX path when som.sku is flipped.
|
|
7
|
+
|
|
8
|
+
libraries:
|
|
9
|
+
- name: tflite-micro
|
|
10
|
+
cores: [m33_sm]
|
|
11
|
+
|
|
12
|
+
som:
|
|
13
|
+
sku: E1M-V2N101
|
|
14
|
+
|
|
15
|
+
preset: e1m-x-evk
|
|
16
|
+
supported_boards:
|
|
17
|
+
- e1m-evk
|
|
18
|
+
- e1m-x-evk
|
|
19
|
+
|
|
20
|
+
pins:
|
|
21
|
+
- { e1m: E1M_X_I2C0, macro: XEVK_I2C_BUS_SENSORS, doc: "On-board sensor + IO-expander + INA236 bus (ICM-42670, BMI323, BMP581, TCAL9538, INA236)." }
|
|
22
|
+
|
|
23
|
+
cores:
|
|
24
|
+
a55_cluster:
|
|
25
|
+
os: "off"
|
|
26
|
+
m33_sm:
|
|
27
|
+
app: ./src
|
|
28
|
+
inference:
|
|
29
|
+
default_arena_kib: 64
|
|
30
|
+
peripherals:
|
|
31
|
+
- i2c # BME280 link.
|
|
32
|
+
|
|
33
|
+
chips:
|
|
34
|
+
- bme280 # temperature / humidity / pressure.
|
|
35
|
+
|
|
36
|
+
diagnostics:
|
|
37
|
+
log_level: info
|