1use std::sync::mpsc::Sender;
16use std::sync::{Arc, Mutex};
17
18use async_trait::async_trait;
19
20use arora_types::data::{Key, State, StateChange, Subscription};
21use arora_types::value::Value;
22
23#[derive(Debug, Clone, Default, PartialEq, Eq)]
25pub struct HalDescription {
26 pub model_family: Option<String>,
27 pub hardware_version: Option<String>,
28 pub software_version: Option<String>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum HalError {
34 Broken(String),
36 NoSuchKey(String),
38 Other(String),
40}
41
42impl std::fmt::Display for HalError {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 match self {
45 HalError::Broken(m) => write!(f, "hardware link broken: {m}"),
46 HalError::NoSuchKey(k) => write!(f, "no such key: {k}"),
47 HalError::Other(m) => write!(f, "{m}"),
48 }
49 }
50}
51
52impl std::error::Error for HalError {}
53
54pub type HalResult<T> = Result<T, HalError>;
55
56#[async_trait]
61pub trait Hal: Send + Sync {
62 async fn describe(&self) -> HalDescription;
64
65 async fn read(&self, keys: &[Key]) -> HalResult<Vec<Option<Value>>>;
68
69 async fn read_all(&self) -> HalResult<State>;
71
72 async fn write(&self, changes: StateChange) -> HalResult<()>;
75
76 fn updates(&self) -> Subscription;
78}
79
80#[async_trait]
82pub trait HalAssets: Send + Sync {
83 async fn model_glb(&self) -> HalResult<Option<Vec<u8>>>;
84}
85
86#[derive(Default)]
87struct FakeInner {
88 description: HalDescription,
89 model_glb: Option<Vec<u8>>,
90 state: State,
91 subscribers: Vec<Sender<StateChange>>,
92}
93
94impl FakeInner {
95 fn notify(&mut self, change: &StateChange) {
96 if change.is_empty() {
97 return;
98 }
99 self.subscribers
100 .retain(|tx| tx.send(change.clone()).is_ok());
101 }
102}
103
104#[derive(Clone, Default)]
112pub struct FakeHal {
113 inner: Arc<Mutex<FakeInner>>,
114}
115
116impl FakeHal {
117 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn with_description(description: HalDescription) -> Self {
122 Self {
123 inner: Arc::new(Mutex::new(FakeInner {
124 description,
125 ..Default::default()
126 })),
127 }
128 }
129
130 pub fn set_model_glb(&self, glb: Vec<u8>) {
131 self.inner.lock().unwrap().model_glb = Some(glb);
132 }
133}
134
135#[async_trait]
136impl Hal for FakeHal {
137 async fn describe(&self) -> HalDescription {
138 self.inner.lock().unwrap().description.clone()
139 }
140
141 async fn read(&self, keys: &[Key]) -> HalResult<Vec<Option<Value>>> {
142 let inner = self.inner.lock().unwrap();
143 Ok(keys
144 .iter()
145 .map(|k| inner.state.get(k).cloned().flatten())
146 .collect())
147 }
148
149 async fn read_all(&self) -> HalResult<State> {
150 Ok(self.inner.lock().unwrap().state.clone())
151 }
152
153 async fn write(&self, changes: StateChange) -> HalResult<()> {
154 if changes.is_empty() {
155 return Ok(());
156 }
157 let mut inner = self.inner.lock().unwrap();
158 inner.state.apply(changes.clone());
159 inner.notify(&changes);
160
161 let mut mirrored = StateChange::new();
163 for (key, value) in &changes.set {
164 if key.get_component() == Some("target_position") {
165 mirrored
166 .set
167 .insert(key.clone().with_component("position"), value.clone());
168 }
169 }
170 if !mirrored.is_empty() {
171 inner.state.apply(mirrored.clone());
172 inner.notify(&mirrored);
173 }
174 Ok(())
175 }
176
177 fn updates(&self) -> Subscription {
178 let (tx, rx) = std::sync::mpsc::channel();
179 self.inner.lock().unwrap().subscribers.push(tx);
180 Subscription::new(rx)
181 }
182}
183
184#[async_trait]
185impl HalAssets for FakeHal {
186 async fn model_glb(&self) -> HalResult<Option<Vec<u8>>> {
187 Ok(self.inner.lock().unwrap().model_glb.clone())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[tokio::test]
196 async fn fake_mirrors_target_position() {
197 let hal = FakeHal::new();
198 let sub = hal.updates();
199 hal.write(StateChange::set("joint1.target_position", Value::from(1.0)))
200 .await
201 .unwrap();
202 assert_eq!(
204 hal.read(&[Key::from("joint1.position")]).await.unwrap(),
205 vec![Some(Value::from(1.0))]
206 );
207 let saw_position = sub
209 .try_iter()
210 .any(|c| c.contains(&Key::from("joint1.position")));
211 assert!(saw_position);
212 }
213
214 #[tokio::test]
215 async fn read_absent_is_none() {
216 let hal = FakeHal::new();
217 assert_eq!(hal.read(&[Key::from("nope")]).await.unwrap(), vec![None]);
218 }
219
220 #[tokio::test]
221 async fn describe_and_glb() {
222 let hal = FakeHal::with_description(HalDescription {
223 model_family: Some("test".into()),
224 ..Default::default()
225 });
226 assert_eq!(hal.describe().await.model_family.as_deref(), Some("test"));
227 hal.set_model_glb(vec![1, 2, 3]);
228 assert_eq!(hal.model_glb().await.unwrap(), Some(vec![1, 2, 3]));
229 }
230}