Skip to main content

alp_core/
model.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Board configuration model — Rust mirror of the TypeScript
3//! `@alp-sdk/core` board/configurator model.
4//!
5//! The contract (shared JSON schema + fixtures) is the single source of
6//! truth across the TS and Rust implementations.
7
8use serde::{Deserialize, Serialize};
9use std::collections::BTreeMap;
10
11/// Parsed `board.yaml` document. Unknown fields are ignored so that the
12/// first Rust phases can expand safely while YAML keeps richer data.
13#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
14pub struct BoardModel {
15    /// Schema revision. Absent in YAML is treated as v1 (matches TS, where
16    /// `model.schema_version >= 2` is false for `undefined`).
17    #[serde(default)]
18    pub schema_version: Option<u32>,
19
20    /// System-on-module selection.
21    #[serde(default)]
22    pub som: Option<Som>,
23
24    /// Carrier board selection and its populated-component flags.
25    #[serde(default)]
26    pub carrier: Option<Carrier>,
27
28    /// v1 only. In v2 this moves into a per-core `cores:` block.
29    #[serde(default)]
30    pub os: Option<String>,
31
32    /// Named board preset applied to this configuration.
33    #[serde(default)]
34    pub preset: Option<String>,
35
36    /// v2 per-core configuration, keyed by core name.
37    #[serde(default)]
38    pub cores: Option<BTreeMap<String, CoreEntry>>,
39
40    /// Inter-processor communication shared-memory carve-outs.
41    #[serde(default)]
42    pub ipc: Option<Vec<IpcCarveOut>>,
43
44    /// Inference backend/arena configuration (v1 top-level).
45    #[serde(default)]
46    pub inference: Option<Inference>,
47
48    /// Enabled library identifiers (v1 top-level).
49    #[serde(default)]
50    pub libraries: Option<Vec<String>>,
51
52    /// IoT connectivity toggles (v1 top-level).
53    #[serde(default)]
54    pub iot: Option<Iot>,
55
56    /// Diagnostics/logging configuration.
57    #[serde(default)]
58    pub diagnostics: Option<Diagnostics>,
59
60    /// Populated-component flags keyed by component id.
61    #[serde(default)]
62    pub populated: Option<BTreeMap<String, bool>>,
63
64    /// Selected chip identifiers.
65    #[serde(default)]
66    pub chips: Option<Vec<String>>,
67
68    /// E1M routing entries, preserved as opaque YAML values.
69    #[serde(default)]
70    pub e1m_routes: Option<BTreeMap<String, serde_yaml::Value>>,
71}
72
73/// System-on-module identification.
74#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
75pub struct Som {
76    /// SoM part number / SKU.
77    #[serde(default)]
78    pub sku: Option<String>,
79}
80
81/// Carrier board selection.
82#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
83pub struct Carrier {
84    /// Carrier board name.
85    #[serde(default)]
86    pub name: Option<String>,
87
88    /// Populated-component flags keyed by component id.
89    #[serde(default)]
90    pub populated: Option<BTreeMap<String, bool>>,
91}
92
93/// Per-core configuration block (schema v2).
94#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
95pub struct CoreEntry {
96    /// Operating system / runtime for this core.
97    #[serde(default)]
98    pub os: Option<String>,
99
100    /// Application identifier built for this core.
101    #[serde(default)]
102    pub app: Option<String>,
103
104    /// Image type / variant for this core.
105    #[serde(default)]
106    pub image: Option<String>,
107
108    /// Peripheral identifiers assigned to this core.
109    #[serde(default)]
110    pub peripherals: Option<Vec<String>>,
111
112    /// Enabled library identifiers for this core.
113    #[serde(default)]
114    pub libraries: Option<Vec<String>>,
115
116    /// Inference configuration for this core.
117    #[serde(default)]
118    pub inference: Option<Inference>,
119
120    /// IoT connectivity toggles for this core.
121    #[serde(default)]
122    pub iot: Option<Iot>,
123}
124
125/// One inter-processor shared-memory carve-out.
126#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
127pub struct IpcCarveOut {
128    /// Carve-out name.
129    pub name: String,
130    /// Endpoint identifiers attached to this carve-out.
131    pub endpoints: Vec<String>,
132    /// Size of the carve-out in KiB.
133    pub size_kib: u32,
134}
135
136/// Inference backend and arena configuration.
137#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
138pub struct Inference {
139    /// Inference backend identifier.
140    #[serde(default)]
141    pub backend: Option<String>,
142
143    /// Default tensor arena size in KiB.
144    #[serde(default)]
145    pub default_arena_kib: Option<u32>,
146}
147
148impl Inference {
149    /// True when no backend is set (empty/absent) and no arena size is given.
150    pub fn is_empty(&self) -> bool {
151        self.backend.as_deref().unwrap_or_default().is_empty() && self.default_arena_kib.is_none()
152    }
153}
154
155/// IoT connectivity feature toggles.
156#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
157pub struct Iot {
158    /// Wi-Fi enabled.
159    #[serde(default)]
160    pub wifi: Option<bool>,
161
162    /// MQTT enabled.
163    #[serde(default)]
164    pub mqtt: Option<bool>,
165
166    /// Bluetooth Low Energy enabled.
167    #[serde(default)]
168    pub ble: Option<bool>,
169
170    /// TLS enabled.
171    #[serde(default)]
172    pub tls: Option<bool>,
173}
174
175impl Iot {
176    /// True when any connectivity toggle is explicitly set to `true`.
177    pub fn any_enabled(&self) -> bool {
178        matches!(self.wifi, Some(true))
179            || matches!(self.mqtt, Some(true))
180            || matches!(self.ble, Some(true))
181            || matches!(self.tls, Some(true))
182    }
183}
184
185/// Diagnostics and logging configuration.
186#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
187pub struct Diagnostics {
188    /// Capture last-error reporting.
189    #[serde(default)]
190    pub last_error: Option<bool>,
191
192    /// Log verbosity level.
193    #[serde(default)]
194    pub log_level: Option<String>,
195}
196
197impl BoardModel {
198    /// Effective schema version, defaulting to 1 when absent.
199    pub fn effective_schema_version(&self) -> u32 {
200        self.schema_version.unwrap_or(1)
201    }
202}
203
204/// Normalize a parsed board model using the same cleanup rules as
205/// TypeScript `normalizeBoardModel`.
206pub fn normalize_board_model(mut model: BoardModel) -> BoardModel {
207    if model.effective_schema_version() < 2 {
208        if model.libraries.as_ref().is_some_and(Vec::is_empty) {
209            model.libraries = None;
210        }
211
212        if model.iot.as_ref().is_some_and(|iot| !iot.any_enabled()) {
213            model.iot = None;
214        }
215
216        if model.inference.as_ref().is_some_and(Inference::is_empty) {
217            model.inference = None;
218        }
219
220        if let Some(carrier) = &mut model.carrier {
221            if carrier.populated.as_ref().is_some_and(BTreeMap::is_empty) {
222                carrier.populated = None;
223            }
224        }
225    }
226
227    if model.effective_schema_version() >= 2 {
228        model.os = None;
229    }
230
231    model
232}