use std::path::{Path, PathBuf};
use consortium_cfg_common::config::{
CSTI_MANIFEST_PATH_ENV, CSTI_MCU_PATH_ENV, Hmi, HmiEngine, MemoryRegion, Workspace,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CstiWorkspace {
pub layout: Workspace,
pub manifest_dir: PathBuf,
pub mcu_dir: Option<PathBuf>,
}
impl CstiWorkspace {
pub fn env_pairs(&self) -> Vec<(&'static str, &Path)> {
let mut pairs = vec![(CSTI_MANIFEST_PATH_ENV, self.manifest_dir.as_path())];
if let Some(mcu) = &self.mcu_dir {
pairs.push((CSTI_MCU_PATH_ENV, mcu.as_path()));
}
pairs
}
pub fn target_base<'a>(&'a self, crate_dir: &'a Path) -> &'a Path {
match self.layout {
Workspace::Unified => &self.manifest_dir,
Workspace::Nested => crate_dir,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub struct SharedMemConfig {
pub length: u64,
pub uio: u32,
pub mtu: u32,
}
impl SharedMemConfig {
pub fn from_region(region: MemoryRegion, uio: u32, mtu: u32) -> Self {
Self {
length: u64::from(region.length),
uio,
mtu,
}
}
pub fn env_pairs(&self, prefix: &str) -> [(String, String); 3] {
[
(
format!("CONSORTIUM_{prefix}_LENGTH"),
self.length.to_string(),
),
(format!("CONSORTIUM_{prefix}_UIO"), self.uio.to_string()),
(format!("CONSORTIUM_{prefix}_MTU"), self.mtu.to_string()),
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub struct DbgConfig {
pub base: u64,
pub length: u64,
}
impl DbgConfig {
pub fn new(base: u64, length: u64) -> Self {
Self { base, length }
}
pub fn env_pairs(&self) -> [(String, String); 2] {
[
("CONSORTIUM_DBG_BASE".to_string(), self.base.to_string()),
("CONSORTIUM_DBG_LENGTH".to_string(), self.length.to_string()),
]
}
}
const WWW_SUBDIR: &str = "www";
const POCKET_SUBDIR: &str = "hmi";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "engine")]
pub enum HmiConfig {
Cog {
dist: String,
#[serde(skip_serializing_if = "Option::is_none")]
debug_url: Option<String>,
},
Pocket {
bundle: String,
pak: String,
logical_width: u32,
logical_height: u32,
},
}
impl HmiConfig {
pub fn from_hmi(hmi: &Hmi, deploy_root: &str) -> Self {
match hmi.engine {
HmiEngine::Cog => Self::Cog {
dist: on_target(deploy_root, WWW_SUBDIR)
.to_string_lossy()
.into_owned(),
debug_url: hmi.debug.clone(),
},
HmiEngine::Pocket => {
let app = hmi.app.as_deref().unwrap_or_default();
let base = on_target(deploy_root, POCKET_SUBDIR);
Self::Pocket {
bundle: base
.join(format!("{app}.js"))
.to_string_lossy()
.into_owned(),
pak: base
.join(format!("{app}.pak"))
.to_string_lossy()
.into_owned(),
logical_width: hmi.logical.width,
logical_height: hmi.logical.height,
}
}
}
}
pub fn env_pairs(&self) -> Vec<(String, String)> {
match self {
Self::Cog { dist, debug_url } => {
let mut pairs = vec![("CONSORTIUM_HMI_DIST".to_string(), dist.clone())];
if let Some(url) = debug_url {
pairs.push(("CONSORTIUM_HMI_DEBUG_URL".to_string(), url.clone()));
}
pairs
}
Self::Pocket {
bundle,
pak,
logical_width,
logical_height,
} => vec![
("CONSORTIUM_HMI_POCKET_BUNDLE".to_string(), bundle.clone()),
("CONSORTIUM_HMI_POCKET_PAK".to_string(), pak.clone()),
(
"CONSORTIUM_HMI_LOGICAL_WIDTH".to_string(),
logical_width.to_string(),
),
(
"CONSORTIUM_HMI_LOGICAL_HEIGHT".to_string(),
logical_height.to_string(),
),
],
}
}
}
fn on_target(deploy_root: &str, subdir: &str) -> PathBuf {
Path::new(deploy_root).join(subdir)
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CortexMcuCore {
CortexM0p,
CortexM33,
CortexM7,
CortexM4,
CortexM4f,
}
impl CortexMcuCore {
pub fn as_key(s: &str) -> Option<Self> {
match s {
"cortex-m0p" => Some(Self::CortexM0p),
"cortex-m33" => Some(Self::CortexM33),
"cortex-m7" => Some(Self::CortexM7),
"cortex-m4" => Some(Self::CortexM4),
"cortex-m4f" => Some(Self::CortexM4f),
_ => None,
}
}
pub fn abbr(&self) -> &'static str {
match self {
Self::CortexM0p => "cm0p",
Self::CortexM33 => "cm33",
Self::CortexM7 => "cm7",
Self::CortexM4 => "cm4",
Self::CortexM4f => "cm4f",
}
}
pub fn from_abbr(s: &str) -> Option<Self> {
match s {
"cm0p" => Some(Self::CortexM0p),
"cm33" => Some(Self::CortexM33),
"cm7" => Some(Self::CortexM7),
"cm4" => Some(Self::CortexM4),
"cm4f" => Some(Self::CortexM4f),
_ => None,
}
}
pub fn to_rust_target(&self) -> &'static str {
match self {
Self::CortexM0p => "thumbv6m-none-eabi",
Self::CortexM33 => "thumbv8m.main-none-eabihf",
Self::CortexM7 | Self::CortexM4f => "thumbv7em-none-eabihf",
Self::CortexM4 => "thumbv7em-none-eabi",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: SharedMemConfig = SharedMemConfig {
length: 0x1000,
uio: 0,
mtu: 64,
};
#[test]
fn env_pairs_emit_the_ipc_layout_the_app_reads() {
let pairs = SAMPLE.env_pairs("IPC");
assert_eq!(
pairs,
[
("CONSORTIUM_IPC_LENGTH".to_string(), "4096".to_string()),
("CONSORTIUM_IPC_UIO".to_string(), "0".to_string()),
("CONSORTIUM_IPC_MTU".to_string(), "64".to_string()),
]
);
assert!(!pairs.iter().any(|(k, _)| k == "CONSORTIUM_IPC_BASE"));
}
#[test]
fn dbg_config_emits_the_base_and_length_the_logger_reads() {
let dbg = DbgConfig::new(0x2040_8000, 0x2000);
assert_eq!(
dbg.env_pairs(),
[
("CONSORTIUM_DBG_BASE".to_string(), "541097984".to_string()),
("CONSORTIUM_DBG_LENGTH".to_string(), "8192".to_string()),
]
);
}
#[test]
fn hmi_env_pairs_emit_dist_and_optional_debug_url() {
let with_url = HmiConfig::Cog {
dist: "ui/dist".to_string(),
debug_url: Some("http://localhost:5173".to_string()),
};
assert_eq!(
with_url.env_pairs(),
[
("CONSORTIUM_HMI_DIST".to_string(), "ui/dist".to_string()),
(
"CONSORTIUM_HMI_DEBUG_URL".to_string(),
"http://localhost:5173".to_string()
),
]
);
let no_url = HmiConfig::Cog {
dist: "ui/dist".to_string(),
debug_url: None,
};
assert_eq!(
no_url.env_pairs(),
[("CONSORTIUM_HMI_DIST".to_string(), "ui/dist".to_string())]
);
}
#[test]
fn pocket_env_pairs_emit_bundle_and_pak_paths() {
let cfg = HmiConfig::Pocket {
bundle: "/opt/consortium/hmi/hero-main.js".to_string(),
pak: "/opt/consortium/hmi/hero-main.pak".to_string(),
logical_width: 800,
logical_height: 480,
};
assert_eq!(
cfg.env_pairs(),
[
(
"CONSORTIUM_HMI_POCKET_BUNDLE".to_string(),
"/opt/consortium/hmi/hero-main.js".to_string()
),
(
"CONSORTIUM_HMI_POCKET_PAK".to_string(),
"/opt/consortium/hmi/hero-main.pak".to_string()
),
(
"CONSORTIUM_HMI_LOGICAL_WIDTH".to_string(),
"800".to_string()
),
(
"CONSORTIUM_HMI_LOGICAL_HEIGHT".to_string(),
"480".to_string()
),
]
);
}
#[test]
fn from_region_widens_schema_region_into_injectable_config() {
let region = MemoryRegion {
base: 0x2040_0000,
length: 0x4000,
};
let cfg = SharedMemConfig::from_region(region, 0, 128);
assert_eq!(
cfg,
SharedMemConfig {
length: 0x4000,
uio: 0,
mtu: 128,
}
);
let pairs = cfg.env_pairs("IPC");
assert_eq!(
pairs[0],
("CONSORTIUM_IPC_LENGTH".to_string(), "16384".to_string())
);
}
#[test]
fn from_hmi_places_cog_dist_under_deploy_root() {
use consortium_cfg_common::config::{Hmi, HmiBackend, HmiEngine};
let hmi = Hmi {
engine: HmiEngine::Cog,
backend: HmiBackend::Wayland,
debug: Some("http://127.0.0.1:5173/".to_string()),
source: "website".to_string(),
build: "website/dist".to_string(),
app: None,
logical: Default::default(),
};
let cfg = HmiConfig::from_hmi(&hmi, "/opt/consortium");
assert_eq!(
cfg,
HmiConfig::Cog {
dist: "/opt/consortium/www".to_string(),
debug_url: Some("http://127.0.0.1:5173/".to_string()),
}
);
}
#[test]
fn from_hmi_places_pocket_bundle_and_pak_under_deploy_root() {
use consortium_cfg_common::config::{Hmi, HmiBackend, HmiEngine};
let hmi = Hmi {
engine: HmiEngine::Pocket,
backend: HmiBackend::Wayland,
debug: None,
source: "submodules/pocketjs".to_string(),
build: "dist".to_string(),
app: Some("hero-main".to_string()),
logical: consortium_cfg_common::config::HmiLogical {
width: 800,
height: 480,
},
};
let cfg = HmiConfig::from_hmi(&hmi, "/opt/consortium");
assert_eq!(
cfg,
HmiConfig::Pocket {
bundle: "/opt/consortium/hmi/hero-main.js".to_string(),
pak: "/opt/consortium/hmi/hero-main.pak".to_string(),
logical_width: 800,
logical_height: 480,
}
);
}
#[test]
fn mcu_core_resolves_rust_target_triples() {
assert_eq!(
CortexMcuCore::CortexM33.to_rust_target(),
"thumbv8m.main-none-eabihf"
);
assert_eq!(
CortexMcuCore::CortexM7.to_rust_target(),
"thumbv7em-none-eabihf"
);
assert_eq!(
CortexMcuCore::as_key("cortex-m33"),
Some(CortexMcuCore::CortexM33)
);
assert_eq!(
CortexMcuCore::from_abbr("cm0p"),
Some(CortexMcuCore::CortexM0p)
);
assert_eq!(CortexMcuCore::CortexM4f.abbr(), "cm4f");
}
}