Skip to main content

mailang_stdlib/
hal.rs

1//! Minimal hardware abstraction for IoT hosts.
2//!
3//! Host firmware implements these traits and registers matching FFI callbacks.
4//! The simulated implementation below is used for desktop demos and tests.
5
6use core::cell::RefCell;
7use mailang_bytecode::Value;
8use std::collections::HashMap;
9use std::rc::Rc;
10
11pub type Pin = u8;
12pub type Port = u8;
13pub type I2cAddr = u8;
14
15pub trait Gpio {
16    fn set_mode_output(&mut self, pin: Pin);
17    fn set_mode_input(&mut self, pin: Pin);
18    fn write(&mut self, pin: Pin, high: bool) -> Result<(), String>;
19    fn read(&self, pin: Pin) -> Result<bool, String>;
20}
21
22pub trait Delay {
23    fn delay_ms(&mut self, ms: u32);
24}
25
26pub trait Adc {
27    fn read_raw(&mut self, channel: u8) -> Result<u16, String>;
28}
29
30pub trait Pwm {
31    fn set_duty(&mut self, pin: Pin, duty_16: u16) -> Result<(), String>;
32    fn set_freq(&mut self, pin: Pin, hz: u32) -> Result<(), String>;
33}
34
35pub trait Uart {
36    fn write_bytes(&mut self, port: Port, data: &[u8]) -> Result<(), String>;
37    fn read_bytes(&mut self, port: Port, max: usize) -> Result<Vec<u8>, String>;
38}
39
40pub trait I2c {
41    fn write_read(&mut self, addr: I2cAddr, w: &[u8], rlen: usize) -> Result<Vec<u8>, String>;
42}
43
44pub trait Spi {
45    fn transfer(&mut self, addr: I2cAddr, tx: &[u8], rx_len: usize) -> Result<Vec<u8>, String>;
46}
47
48/// Desktop/simulator HAL: 32 virtual GPIO pins + a tick counter for delay.
49/// PWM duty/freq tables, UART loopback buffers, and I2C/SPI echo devices.
50#[derive(Debug, Clone)]
51pub struct SimulatedHal {
52    pins: Rc<RefCell<[bool; 32]>>,
53    outputs: Rc<RefCell<[bool; 32]>>,
54    ticks_ms: Rc<RefCell<u64>>,
55    adc: Rc<RefCell<[u16; 8]>>,
56    pwm_duty: Rc<RefCell<[u16; 32]>>,
57    pwm_freq: Rc<RefCell<[u32; 32]>>,
58    /// Per-port UART RX queue (TX is looped back into RX).
59    uart_rx: Rc<RefCell<HashMap<Port, Vec<u8>>>>,
60    /// Last I2C write per device address.
61    i2c_writes: Rc<RefCell<HashMap<I2cAddr, Vec<u8>>>>,
62}
63
64impl Default for SimulatedHal {
65    fn default() -> Self {
66        Self::new()
67    }
68}
69
70impl SimulatedHal {
71    pub fn new() -> Self {
72        Self {
73            pins: Rc::new(RefCell::new([false; 32])),
74            outputs: Rc::new(RefCell::new([false; 32])),
75            ticks_ms: Rc::new(RefCell::new(0)),
76            adc: Rc::new(RefCell::new([0; 8])),
77            pwm_duty: Rc::new(RefCell::new([0; 32])),
78            pwm_freq: Rc::new(RefCell::new([0; 32])),
79            uart_rx: Rc::new(RefCell::new(HashMap::new())),
80            i2c_writes: Rc::new(RefCell::new(HashMap::new())),
81        }
82    }
83
84    /// Simulate an external sensor value on an ADC channel.
85    pub fn set_adc(&mut self, channel: u8, raw: u16) {
86        if (channel as usize) < 8 {
87            self.adc.borrow_mut()[channel as usize] = raw;
88        }
89    }
90
91    /// Externally drive a pin level (as if a button/sensor pulled it).
92    pub fn drive_pin(&mut self, pin: Pin, high: bool) {
93        if (pin as usize) < 32 {
94            self.pins.borrow_mut()[pin as usize] = high;
95        }
96    }
97
98    pub fn elapsed_ms(&self) -> u64 {
99        *self.ticks_ms.borrow()
100    }
101
102    pub fn pwm_duty(&self, pin: Pin) -> Result<u16, String> {
103        let i = Self::check_pin(pin)?;
104        Ok(self.pwm_duty.borrow()[i])
105    }
106
107    pub fn pwm_freq(&self, pin: Pin) -> Result<u32, String> {
108        let i = Self::check_pin(pin)?;
109        Ok(self.pwm_freq.borrow()[i])
110    }
111
112    pub fn i2c_last_write(&self, addr: I2cAddr) -> Option<Vec<u8>> {
113        self.i2c_writes.borrow().get(&addr).cloned()
114    }
115
116    fn check_pin(pin: Pin) -> Result<usize, String> {
117        if (pin as usize) < 32 {
118            Ok(pin as usize)
119        } else {
120            Err(format!("pin {} out of range (0..31)", pin))
121        }
122    }
123}
124
125impl Gpio for SimulatedHal {
126    fn set_mode_output(&mut self, _pin: Pin) {}
127    fn set_mode_input(&mut self, _pin: Pin) {}
128
129    fn write(&mut self, pin: Pin, high: bool) -> Result<(), String> {
130        let i = Self::check_pin(pin)?;
131        self.outputs.borrow_mut()[i] = high;
132        self.pins.borrow_mut()[i] = high;
133        Ok(())
134    }
135
136    fn read(&self, pin: Pin) -> Result<bool, String> {
137        let i = Self::check_pin(pin)?;
138        Ok(self.pins.borrow()[i])
139    }
140}
141
142impl Delay for SimulatedHal {
143    fn delay_ms(&mut self, ms: u32) {
144        *self.ticks_ms.borrow_mut() += ms as u64;
145    }
146}
147
148impl Adc for SimulatedHal {
149    fn read_raw(&mut self, channel: u8) -> Result<u16, String> {
150        if (channel as usize) < 8 {
151            Ok(self.adc.borrow()[channel as usize])
152        } else {
153            Err(format!("adc channel {} out of range (0..7)", channel))
154        }
155    }
156}
157
158impl Pwm for SimulatedHal {
159    fn set_duty(&mut self, pin: Pin, duty_16: u16) -> Result<(), String> {
160        let i = Self::check_pin(pin)?;
161        self.pwm_duty.borrow_mut()[i] = duty_16;
162        Ok(())
163    }
164
165    fn set_freq(&mut self, pin: Pin, hz: u32) -> Result<(), String> {
166        let i = Self::check_pin(pin)?;
167        self.pwm_freq.borrow_mut()[i] = hz;
168        Ok(())
169    }
170}
171
172impl Uart for SimulatedHal {
173    fn write_bytes(&mut self, port: Port, data: &[u8]) -> Result<(), String> {
174        self.uart_rx
175            .borrow_mut()
176            .entry(port)
177            .or_default()
178            .extend_from_slice(data);
179        Ok(())
180    }
181
182    fn read_bytes(&mut self, port: Port, max: usize) -> Result<Vec<u8>, String> {
183        let mut map = self.uart_rx.borrow_mut();
184        let buf = map.entry(port).or_default();
185        let n = max.min(buf.len());
186        Ok(buf.drain(..n).collect())
187    }
188}
189
190impl I2c for SimulatedHal {
191    fn write_read(&mut self, addr: I2cAddr, w: &[u8], rlen: usize) -> Result<Vec<u8>, String> {
192        self.i2c_writes.borrow_mut().insert(addr, w.to_vec());
193        // Deterministic echo device: response[i] = addr.wrapping_add(i).
194        Ok((0..rlen).map(|i| addr.wrapping_add(i as u8)).collect())
195    }
196}
197
198impl Spi for SimulatedHal {
199    fn transfer(&mut self, _addr: I2cAddr, tx: &[u8], rx_len: usize) -> Result<Vec<u8>, String> {
200        // MOSI鈫扢ISO loopback, padded/truncated to `rx_len`.
201        let mut rx = tx.to_vec();
202        rx.resize(rx_len, 0);
203        Ok(rx)
204    }
205}
206
207thread_local! {
208    static HAL: RefCell<SimulatedHal> = RefCell::new(SimulatedHal::new());
209}
210
211fn pin_arg(args: &[Value], idx: usize, who: &str) -> Result<Pin, String> {
212    match args.get(idx) {
213        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => Ok(*n as u8),
214        _ => Err(format!("{}: pin must be int 0..255", who)),
215    }
216}
217
218fn parse_hex(s: &str) -> Result<Vec<u8>, String> {
219    let s = s
220        .strip_prefix("0x")
221        .or_else(|| s.strip_prefix("0X"))
222        .unwrap_or(s);
223    if !s.len().is_multiple_of(2) {
224        return Err("hex data must have even length".into());
225    }
226    (0..s.len())
227        .step_by(2)
228        .map(|i| {
229            u8::from_str_radix(&s[i..i + 2], 16)
230                .map_err(|_| format!("invalid hex at byte {}", i / 2))
231        })
232        .collect()
233}
234
235fn to_hex(data: &[u8]) -> String {
236    data.iter().map(|b| format!("{:02x}", b)).collect()
237}
238
239/// `gpio_write(pin, high)` 鈥?pin: int, high: bool
240pub fn builtin_gpio_write(args: &[Value]) -> Result<Value, String> {
241    let pin = pin_arg(args, 0, "gpio_write(pin, high)")?;
242    let high = match args.get(1) {
243        Some(Value::Bool(b)) => *b,
244        Some(Value::Int(n)) => *n != 0,
245        _ => return Err("gpio_write(pin, high): high must be bool or int".into()),
246    };
247    HAL.with(|h| {
248        let mut h = h.borrow_mut();
249        h.set_mode_output(pin);
250        h.write(pin, high)
251    })?;
252    Ok(Value::Null)
253}
254
255/// `gpio_read(pin)` 鈫?bool
256pub fn builtin_gpio_read(args: &[Value]) -> Result<Value, String> {
257    let pin = pin_arg(args, 0, "gpio_read(pin)")?;
258    let v = HAL.with(|h| h.borrow().read(pin))?;
259    Ok(Value::Bool(v))
260}
261
262/// `delay_ms(ms)` 鈥?simulated; advances the HAL clock only.
263pub fn builtin_delay_ms(args: &[Value]) -> Result<Value, String> {
264    let ms = match args.first() {
265        Some(Value::Int(n)) if *n >= 0 => *n as u32,
266        _ => return Err("delay_ms(ms): ms must be non-negative int".into()),
267    };
268    HAL.with(|h| h.borrow_mut().delay_ms(ms));
269    Ok(Value::Null)
270}
271
272/// `adc_read(channel)` 鈫?int raw
273pub fn builtin_adc_read(args: &[Value]) -> Result<Value, String> {
274    let ch = match args.first() {
275        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
276        _ => return Err("adc_read(channel): channel must be int 0..255".into()),
277    };
278    let v = HAL.with(|h| h.borrow_mut().read_raw(ch))?;
279    Ok(Value::Int(v as i64))
280}
281
282/// `pwm_write(pin, duty)` 鈥?duty is 16-bit (0..=65535).
283pub fn builtin_pwm_write(args: &[Value]) -> Result<Value, String> {
284    let pin = pin_arg(args, 0, "pwm_write(pin, duty)")?;
285    let duty = match args.get(1) {
286        Some(Value::Int(n)) if *n >= 0 && *n <= 65535 => *n as u16,
287        _ => return Err("pwm_write(pin, duty): duty must be int 0..65535".into()),
288    };
289    HAL.with(|h| h.borrow_mut().set_duty(pin, duty))?;
290    Ok(Value::Null)
291}
292
293/// `pwm_freq(pin, hz)`
294pub fn builtin_pwm_freq(args: &[Value]) -> Result<Value, String> {
295    let pin = pin_arg(args, 0, "pwm_freq(pin, hz)")?;
296    let hz = match args.get(1) {
297        Some(Value::Int(n)) if *n >= 0 && *n <= u32::MAX as i64 => *n as u32,
298        _ => return Err("pwm_freq(pin, hz): hz must be non-negative int".into()),
299    };
300    HAL.with(|h| h.borrow_mut().set_freq(pin, hz))?;
301    Ok(Value::Null)
302}
303
304/// `uart_write(port, s)` 鈥?writes UTF-8 bytes of `s` (looped back in sim).
305pub fn builtin_uart_write(args: &[Value]) -> Result<Value, String> {
306    let port = match args.first() {
307        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
308        _ => return Err("uart_write(port, s): port must be int 0..255".into()),
309    };
310    let s = match args.get(1) {
311        Some(Value::Str(s)) => s.to_string(),
312        _ => return Err("uart_write(port, s): s must be string".into()),
313    };
314    HAL.with(|h| h.borrow_mut().write_bytes(port, s.as_bytes()))?;
315    Ok(Value::Null)
316}
317
318/// `uart_read(port, n)` 鈫?string of up to `n` bytes (UTF-8 lossy).
319pub fn builtin_uart_read(args: &[Value]) -> Result<Value, String> {
320    let port = match args.first() {
321        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
322        _ => return Err("uart_read(port, n): port must be int 0..255".into()),
323    };
324    let n = match args.get(1) {
325        Some(Value::Int(n)) if *n >= 0 => *n as usize,
326        _ => return Err("uart_read(port, n): n must be non-negative int".into()),
327    };
328    let bytes = HAL.with(|h| h.borrow_mut().read_bytes(port, n))?;
329    Ok(Value::Str(
330        String::from_utf8_lossy(&bytes).into_owned().into(),
331    ))
332}
333
334/// `i2c_xfer(addr, hexdata, rlen)` 鈫?hex string of `rlen` response bytes.
335pub fn builtin_i2c_xfer(args: &[Value]) -> Result<Value, String> {
336    let addr = match args.first() {
337        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
338        _ => return Err("i2c_xfer(addr, hexdata, rlen): addr must be int 0..255".into()),
339    };
340    let hex = match args.get(1) {
341        Some(Value::Str(s)) => parse_hex(s)?,
342        _ => return Err("i2c_xfer(addr, hexdata, rlen): hexdata must be string".into()),
343    };
344    let rlen = match args.get(2) {
345        Some(Value::Int(n)) if *n >= 0 => *n as usize,
346        _ => return Err("i2c_xfer(addr, hexdata, rlen): rlen must be non-negative int".into()),
347    };
348    let rx = HAL.with(|h| h.borrow_mut().write_read(addr, &hex, rlen))?;
349    Ok(Value::Str(to_hex(&rx).into()))
350}
351
352/// `spi_xfer(addr, hexdata, rxlen)` 鈫?hex string of `rxlen` response bytes.
353pub fn builtin_spi_xfer(args: &[Value]) -> Result<Value, String> {
354    let addr = match args.first() {
355        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
356        _ => return Err("spi_xfer(addr, hexdata, rxlen): addr must be int 0..255".into()),
357    };
358    let hex = match args.get(1) {
359        Some(Value::Str(s)) => parse_hex(s)?,
360        _ => return Err("spi_xfer(addr, hexdata, rxlen): hexdata must be string".into()),
361    };
362    let rxlen = match args.get(2) {
363        Some(Value::Int(n)) if *n >= 0 => *n as usize,
364        _ => return Err("spi_xfer(addr, hexdata, rxlen): rxlen must be non-negative int".into()),
365    };
366    let rx = HAL.with(|h| h.borrow_mut().transfer(addr, &hex, rxlen))?;
367    Ok(Value::Str(to_hex(&rx).into()))
368}
369
370/// Test/demo helper: inject a simulated ADC reading.
371pub fn sim_set_adc(channel: u8, raw: u16) {
372    HAL.with(|h| h.borrow_mut().set_adc(channel, raw));
373}
374
375/// Test/demo helper: force an external pin level.
376pub fn sim_drive_pin(pin: Pin, high: bool) {
377    HAL.with(|h| h.borrow_mut().drive_pin(pin, high));
378}
379
380/// Test/demo helper: read simulated elapsed ms.
381pub fn sim_elapsed_ms() -> u64 {
382    HAL.with(|h| h.borrow().elapsed_ms())
383}
384
385/// Test/demo helper: PWM duty currently programmed on `pin`.
386pub fn sim_pwm_duty(pin: Pin) -> Result<u16, String> {
387    HAL.with(|h| h.borrow().pwm_duty(pin))
388}
389
390/// Test/demo helper: PWM frequency currently programmed on `pin`.
391pub fn sim_pwm_freq(pin: Pin) -> Result<u32, String> {
392    HAL.with(|h| h.borrow().pwm_freq(pin))
393}
394
395/// Test/demo helper: last I2C write payload for `addr`.
396pub fn sim_i2c_last_write(addr: I2cAddr) -> Option<Vec<u8>> {
397    HAL.with(|h| h.borrow().i2c_last_write(addr))
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn gpio_roundtrip() {
406        builtin_gpio_write(&[Value::Int(3), Value::Bool(true)]).unwrap();
407        assert_eq!(
408            builtin_gpio_read(&[Value::Int(3)]).unwrap(),
409            Value::Bool(true)
410        );
411    }
412
413    #[test]
414    fn delay_advances_sim_clock() {
415        let before = sim_elapsed_ms();
416        builtin_delay_ms(&[Value::Int(50)]).unwrap();
417        assert_eq!(sim_elapsed_ms(), before + 50);
418    }
419
420    #[test]
421    fn adc_sim_inject() {
422        sim_set_adc(2, 1234);
423        assert_eq!(
424            builtin_adc_read(&[Value::Int(2)]).unwrap(),
425            Value::Int(1234)
426        );
427    }
428
429    #[test]
430    fn pwm_duty_and_freq() {
431        builtin_pwm_write(&[Value::Int(1), Value::Int(30000)]).unwrap();
432        builtin_pwm_freq(&[Value::Int(1), Value::Int(1000)]).unwrap();
433        assert_eq!(sim_pwm_duty(1).unwrap(), 30000);
434        assert_eq!(sim_pwm_freq(1).unwrap(), 1000);
435    }
436
437    #[test]
438    fn uart_loopback() {
439        builtin_uart_write(&[Value::Int(0), Value::Str("hi".into())]).unwrap();
440        let got = builtin_uart_read(&[Value::Int(0), Value::Int(16)]).unwrap();
441        assert_eq!(got, Value::Str("hi".into()));
442    }
443
444    #[test]
445    fn i2c_xfer_pattern_and_write_log() {
446        let rx = builtin_i2c_xfer(&[
447            Value::Int(0x50),
448            Value::Str("deadbeef".into()),
449            Value::Int(3),
450        ])
451        .unwrap();
452        // Pattern response: addr, addr+1, addr+2 鈫?50 51 52
453        assert_eq!(rx, Value::Str("505152".into()));
454        assert_eq!(sim_i2c_last_write(0x50), Some(vec![0xde, 0xad, 0xbe, 0xef]));
455    }
456
457    #[test]
458    fn spi_xfer_loopback_padded() {
459        let rx =
460            builtin_spi_xfer(&[Value::Int(0), Value::Str("a1b2".into()), Value::Int(4)]).unwrap();
461        assert_eq!(rx, Value::Str("a1b20000".into()));
462    }
463}