Skip to main content

lager/nets/
gpio.rs

1//! GPIO nets: digital input, output, and hardware-accelerated level waits.
2
3use super::net_handle;
4
5/// A digital logic level.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Level {
8    /// Logic high (1).
9    High,
10    /// Logic low (0).
11    Low,
12}
13
14impl Level {
15    /// Wire encoding understood by the box's gpio adapter.
16    pub fn as_str(&self) -> &'static str {
17        match self {
18            Level::High => "high",
19            Level::Low => "low",
20        }
21    }
22
23    /// True for [`Level::High`].
24    pub fn is_high(&self) -> bool {
25        matches!(self, Level::High)
26    }
27}
28
29/// Advanced options for [`Gpio::wait_for_level_with`]. All fields are
30/// optional; `None` uses the box-side defaults.
31#[derive(Debug, Clone, Copy, Default)]
32pub struct WaitForLevelOptions {
33    /// Give up after this many seconds. `None` waits forever (the client
34    /// HTTP timeout is dropped entirely, matching `lager gpi --wait-for`
35    /// without `--timeout`).
36    pub timeout: Option<f64>,
37    /// LabJack streaming sample rate in Hz (advanced).
38    pub scan_rate: Option<u32>,
39    /// LabJack scans per read batch (advanced).
40    pub scans_per_read: Option<u32>,
41    /// Poll interval in seconds for non-streaming drivers (advanced).
42    pub poll_interval: Option<f64>,
43}
44
45pub(crate) mod ops {
46    use std::time::Duration;
47
48    use serde_json::{json, Map, Value};
49
50    use super::{Level, WaitForLevelOptions};
51    use crate::error::Result;
52    use crate::wire::{net_command, unit, value_i64, CommandResponse, Op, Timeout};
53
54    const ROLE: &str = "gpio";
55
56    fn parse_level(resp: CommandResponse) -> Result<Level> {
57        value_i64(resp).map(|v| if v != 0 { Level::High } else { Level::Low })
58    }
59
60    pub(crate) fn input(name: &str) -> Op<Level> {
61        Op {
62            req: net_command(name, ROLE, "input", json!({}), Timeout::Default),
63            parse: parse_level,
64        }
65    }
66
67    pub(crate) fn output(name: &str, level: Level) -> Op<()> {
68        Op {
69            req: net_command(
70                name,
71                ROLE,
72                "output",
73                json!({ "level": level.as_str() }),
74                Timeout::Default,
75            ),
76            parse: unit,
77        }
78    }
79
80    pub(crate) fn output_high(name: &str) -> Op<()> {
81        Op {
82            req: net_command(name, ROLE, "output_high", json!({}), Timeout::Default),
83            parse: unit,
84        }
85    }
86
87    pub(crate) fn output_low(name: &str) -> Op<()> {
88        Op {
89            req: net_command(name, ROLE, "output_low", json!({}), Timeout::Default),
90            parse: unit,
91        }
92    }
93
94    pub(crate) fn toggle(name: &str) -> Op<Level> {
95        Op {
96            req: net_command(
97                name,
98                ROLE,
99                "output",
100                json!({ "level": "toggle" }),
101                Timeout::Default,
102            ),
103            parse: parse_level,
104        }
105    }
106
107    fn wait_params(level: Level, opts: &WaitForLevelOptions) -> Value {
108        let mut params = Map::new();
109        params.insert("level".to_string(), json!(level.as_str()));
110        if let Some(t) = opts.timeout {
111            params.insert("timeout".to_string(), json!(t));
112        }
113        if let Some(r) = opts.scan_rate {
114            params.insert("scan_rate".to_string(), json!(r));
115        }
116        if let Some(s) = opts.scans_per_read {
117            params.insert("scans_per_read".to_string(), json!(s));
118        }
119        if let Some(p) = opts.poll_interval {
120            params.insert("poll_interval".to_string(), json!(p));
121        }
122        Value::Object(params)
123    }
124
125    pub(crate) fn wait_for_level_with(
126        name: &str,
127        level: Level,
128        opts: &WaitForLevelOptions,
129    ) -> Op<f64> {
130        // Widen the client timeout past the device-side wait budget (+20s
131        // margin, like `lager gpi --wait-for`) so the device method — not
132        // the transport — decides when to give up. With no wait timeout the
133        // client waits unbounded, matching the CLI.
134        let timeout = match opts.timeout {
135            Some(t) => Timeout::After(Duration::from_secs_f64(t + 20.0)),
136            None => Timeout::Unbounded,
137        };
138        Op {
139            req: net_command(name, ROLE, "wait_for_level", wait_params(level, opts), timeout),
140            parse: crate::wire::value_f64,
141        }
142    }
143
144    pub(crate) fn wait_for_level(name: &str, level: Level, timeout_s: f64) -> Op<f64> {
145        wait_for_level_with(
146            name,
147            level,
148            &WaitForLevelOptions {
149                timeout: Some(timeout_s),
150                ..WaitForLevelOptions::default()
151            },
152        )
153    }
154}
155
156net_handle! {
157    /// Handle for a GPIO net.
158    sync: Gpio,
159    async: AsyncGpio,
160    methods: {
161        /// Read the current input level.
162        fn input() -> Level = ops::input;
163        /// Drive the output to `level`.
164        fn output(level: Level) -> () = ops::output;
165        /// Drive the output high.
166        fn output_high() -> () = ops::output_high;
167        /// Drive the output low.
168        fn output_low() -> () = ops::output_low;
169        /// Toggle the output and return the new level.
170        fn toggle() -> Level = ops::toggle;
171        /// Block until the input reaches `level` or `timeout_s` seconds
172        /// elapse. Returns the elapsed time in seconds. Times out box-side;
173        /// the HTTP timeout is automatically widened past `timeout_s`.
174        fn wait_for_level(level: Level, timeout_s: f64) -> f64 = ops::wait_for_level;
175        /// [`Gpio::wait_for_level`] with full control over the wait
176        /// parameters, including an unbounded wait (`timeout: None`).
177        fn wait_for_level_with(level: Level, opts: &WaitForLevelOptions) -> f64 = ops::wait_for_level_with;
178    }
179}