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::rc::Rc;
9
10pub type Pin = u8;
11
12pub trait Gpio {
13    fn set_mode_output(&mut self, pin: Pin);
14    fn set_mode_input(&mut self, pin: Pin);
15    fn write(&mut self, pin: Pin, high: bool) -> Result<(), String>;
16    fn read(&self, pin: Pin) -> Result<bool, String>;
17}
18
19pub trait Delay {
20    fn delay_ms(&mut self, ms: u32);
21}
22
23pub trait Adc {
24    fn read_raw(&mut self, channel: u8) -> Result<u16, String>;
25}
26
27/// Desktop/simulator HAL: 32 virtual GPIO pins + a tick counter for delay.
28#[derive(Debug, Clone)]
29pub struct SimulatedHal {
30    pins: Rc<RefCell<[bool; 32]>>,
31    outputs: Rc<RefCell<[bool; 32]>>,
32    ticks_ms: Rc<RefCell<u64>>,
33    adc: Rc<RefCell<[u16; 8]>>,
34}
35
36impl Default for SimulatedHal {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl SimulatedHal {
43    pub fn new() -> Self {
44        Self {
45            pins: Rc::new(RefCell::new([false; 32])),
46            outputs: Rc::new(RefCell::new([false; 32])),
47            ticks_ms: Rc::new(RefCell::new(0)),
48            adc: Rc::new(RefCell::new([0; 8])),
49        }
50    }
51
52    /// Simulate an external sensor value on an ADC channel.
53    pub fn set_adc(&mut self, channel: u8, raw: u16) {
54        if (channel as usize) < 8 {
55            self.adc.borrow_mut()[channel as usize] = raw;
56        }
57    }
58
59    /// Externally drive a pin level (as if a button/sensor pulled it).
60    pub fn drive_pin(&mut self, pin: Pin, high: bool) {
61        if (pin as usize) < 32 {
62            self.pins.borrow_mut()[pin as usize] = high;
63        }
64    }
65
66    pub fn elapsed_ms(&self) -> u64 {
67        *self.ticks_ms.borrow()
68    }
69
70    fn check_pin(pin: Pin) -> Result<usize, String> {
71        if (pin as usize) < 32 {
72            Ok(pin as usize)
73        } else {
74            Err(format!("pin {} out of range (0..31)", pin))
75        }
76    }
77}
78
79impl Gpio for SimulatedHal {
80    fn set_mode_output(&mut self, _pin: Pin) {}
81    fn set_mode_input(&mut self, _pin: Pin) {}
82
83    fn write(&mut self, pin: Pin, high: bool) -> Result<(), String> {
84        let i = Self::check_pin(pin)?;
85        self.outputs.borrow_mut()[i] = high;
86        self.pins.borrow_mut()[i] = high;
87        Ok(())
88    }
89
90    fn read(&self, pin: Pin) -> Result<bool, String> {
91        let i = Self::check_pin(pin)?;
92        Ok(self.pins.borrow()[i])
93    }
94}
95
96impl Delay for SimulatedHal {
97    fn delay_ms(&mut self, ms: u32) {
98        *self.ticks_ms.borrow_mut() += ms as u64;
99    }
100}
101
102impl Adc for SimulatedHal {
103    fn read_raw(&mut self, channel: u8) -> Result<u16, String> {
104        if (channel as usize) < 8 {
105            Ok(self.adc.borrow()[channel as usize])
106        } else {
107            Err(format!("adc channel {} out of range (0..7)", channel))
108        }
109    }
110}
111
112thread_local! {
113    static HAL: RefCell<SimulatedHal> = RefCell::new(SimulatedHal::new());
114}
115
116/// `gpio_write(pin, high)` — pin: int, high: bool
117pub fn builtin_gpio_write(args: &[Value]) -> Result<Value, String> {
118    let pin = match args.first() {
119        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
120        _ => return Err("gpio_write(pin, high): pin must be int 0..255".into()),
121    };
122    let high = match args.get(1) {
123        Some(Value::Bool(b)) => *b,
124        Some(Value::Int(n)) => *n != 0,
125        _ => return Err("gpio_write(pin, high): high must be bool or int".into()),
126    };
127    HAL.with(|h| {
128        let mut h = h.borrow_mut();
129        h.set_mode_output(pin);
130        h.write(pin, high)
131    })?;
132    Ok(Value::Null)
133}
134
135/// `gpio_read(pin)` → bool
136pub fn builtin_gpio_read(args: &[Value]) -> Result<Value, String> {
137    let pin = match args.first() {
138        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
139        _ => return Err("gpio_read(pin): pin must be int 0..255".into()),
140    };
141    let v = HAL.with(|h| h.borrow().read(pin))?;
142    Ok(Value::Bool(v))
143}
144
145/// `delay_ms(ms)` — simulated; advances the HAL clock only.
146pub fn builtin_delay_ms(args: &[Value]) -> Result<Value, String> {
147    let ms = match args.first() {
148        Some(Value::Int(n)) if *n >= 0 => *n as u32,
149        _ => return Err("delay_ms(ms): ms must be non-negative int".into()),
150    };
151    HAL.with(|h| h.borrow_mut().delay_ms(ms));
152    Ok(Value::Null)
153}
154
155/// `adc_read(channel)` → int raw
156pub fn builtin_adc_read(args: &[Value]) -> Result<Value, String> {
157    let ch = match args.first() {
158        Some(Value::Int(n)) if *n >= 0 && *n <= 255 => *n as u8,
159        _ => return Err("adc_read(channel): channel must be int 0..255".into()),
160    };
161    let v = HAL.with(|h| h.borrow_mut().read_raw(ch))?;
162    Ok(Value::Int(v as i64))
163}
164
165/// Test/demo helper: inject a simulated ADC reading.
166pub fn sim_set_adc(channel: u8, raw: u16) {
167    HAL.with(|h| h.borrow_mut().set_adc(channel, raw));
168}
169
170/// Test/demo helper: force an external pin level.
171pub fn sim_drive_pin(pin: Pin, high: bool) {
172    HAL.with(|h| h.borrow_mut().drive_pin(pin, high));
173}
174
175/// Test/demo helper: read simulated elapsed ms.
176pub fn sim_elapsed_ms() -> u64 {
177    HAL.with(|h| h.borrow().elapsed_ms())
178}