1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! A collection of typical RIOT shell commmands, implemented in Rust
//!
//! They serve three purposes:
//!
//! 1. They are examples of how to use particular functions of RIOT
//! 2. They are a test case for comparing string formatting overhead (both C vs. standard Rust and
//!    standard Rust vs. potentially upcoming ufmt based implementations)
//! 3. In Rust applications, they can be included instead of the operating system provided shell
//!    function, when 2. comes out net positive (especially if no other printfs are around and
//!    Rust's formatting is used for other reasons anyway), possibly also with a Rust based shell
//!    prompt.
#![no_std]

use core::fmt::Write;

#[cfg(feature = "static")]
mod statics {
    use super::*;

    #[cfg(feature = "saul")]
    riot_wrappers::static_command!(static_saul, "saul.rs", "interact with sensors and actuators using SAUL via Rust", saul);
    #[cfg(feature = "ztimer")]
    riot_wrappers::static_command!(static_sleep, "sleep", "Pause the shell prompt", sleep);
    riot_wrappers::static_command!(static_ps, "ps.rs", "List the processes", ps);

    pub fn ps<'a>(w: &mut impl Write, _args: impl IntoIterator<Item=&'a str>) {
        super::ps(w)
    }
}

/// A command list including all the commands defined in this crate
///
/// This is available unless the default `shell` feature is disabled, which makes sense when using
/// individual commands outside a shell environment (as they render to a generic Writer).
///
/// If the static feature is enabled, the commands that are exposes as static commands (currently,
/// all) are not exposed here, and the function degenerates into [riot_wrappers::shell::new()].
#[cfg(feature = "shell")]
pub fn all() -> impl riot_wrappers::shell::CommandList {
    use riot_wrappers::shell::{self, CommandList};
    use riot_wrappers::cstr::cstr;

    let r = shell::new();
    #[cfg(not(feature = "static"))]
    let r = r.and(cstr!("ps.rs"), cstr!("List the processes"),
            |stdio: &mut _, _args: shell::Args| ps(stdio));
    #[cfg(not(feature = "static"))]
    #[cfg(feature = "ztimer")]
    let r = r.and(cstr!("sleep"), cstr!("Pause the shell prompt"),
            |stdio: &mut _, args: shell::Args| sleep(stdio, args));
    #[cfg(not(feature = "static"))]
    #[cfg(feature = "saul")]
    let r = r.and(cstr!("saul.rs"), cstr!("interact with sensors and actuators using SAUL via Rust"),
            |stdio: &mut _, args: shell::Args| saul(stdio, args));

    r
}

/// An implementation of a ps command in Rust
///
/// Compared to the built-in command, it lacks access to the interrupt thread and the summary line;
/// both should be straightforward to add.
pub fn ps(w: &mut impl Write)
{
    use riot_wrappers::thread;

    for i in thread::KernelPID::all_pids() {
        let i_number: riot_sys::kernel_pid_t = i.into();

        let stats = i.stack_stats();

        if let Err(thread::StackStatsError::NoSuchThread) = stats {
            continue;
        }

        if let Ok(stats) = stats {
            writeln!(w,
                     "{:>3} ({:<16}): pri {:>2}. {:>6}/{:>6} ({:>3}%). {:.16?}.",
                    i_number, i.get_name().unwrap_or("(unnamed)"),
                    i.priority().expect("Thread exists"),
                    stats.used(), stats.size(), 100 * stats.used() / stats.size(),
                    i.status()).unwrap();
        } else {
            writeln!(w,
                     "{:>3}: pri {:>2}. {:.16?}.",
                    i_number,
                    i.priority().expect("Thread exists"),
                    i.status()).unwrap();
        }
    }
}

/// A sleep in seconds implemented on the milliseconds ztimer
#[cfg(feature = "ztimer")]
pub fn sleep<'a>(w: &mut impl Write, args: impl IntoIterator<Item=&'a str>) {
    let mut args = args.into_iter();
    let commandname = args.next().expect("How was this started without an argv[0]?");

    let time: Option<u32> = args.next().map(|t| t.parse().ok()).flatten();

    let time = match (time, args.next()) {
        (Some(t), None) => t,
        _ => {
            writeln!(w, "Usage: {} seconds", commandname).unwrap();
            return;
        },
    };

    let in_thread = riot_wrappers::thread::InThread::new().unwrap();
    let msec_clock = in_thread.promote( riot_wrappers::ztimer::Clock::msec());
    msec_clock.sleep(core::time::Duration::new(time.into(), 0));
}

/// Expose the read side of the SAUL registry in a command that allows listing, querying and
/// setting SAUL entries.
///
/// This formally depends on (ie. is unavailable without) the `saul` feature, and practically
/// depends on the availability of (ie. will fail without) the [riot_wrappers::saul] module, which
/// is only present if RIOT is built with SAUL.
#[cfg(feature = "saul")]
pub fn saul<'a, A>(w: &mut impl Write, args: A)
where
    A: IntoIterator<Item=&'a str>,
    <A as IntoIterator>::IntoIter: ExactSizeIterator,
{
    use riot_wrappers::saul::RegistryEntry;

    let mut args = args.into_iter();

    let commandname = args.next().expect("How was this started without an argv[0]?");

    fn reg_from_arg(arg: Option<&str>) -> Result<(usize, RegistryEntry), &'static str> {
        let n = arg
            .ok_or("Missing index")?
            .parse()
            .map_err(|_| "Index: Not a number")?;
        Ok((n, RegistryEntry::nth(n)
            .ok_or("No such entry")?))
    }

    fn enumerate(w: &mut impl Write) {
        let mut all = RegistryEntry::all().enumerate().peekable();
        writeln!(w, "{}", match all.peek() {
            Some(_) => " ID Class        Name",
            None => "No devices found",
        }).unwrap();
        for (id, item) in all {
            writeln!(w, "{:3} {:12} {}",
                    id,
                    item.type_()
                        .map(|c| c.name().unwrap_or("(unnamed)"))
                        .unwrap_or("(undefined)"),
                    item.name().unwrap_or("(unnamed)")
                ).unwrap();
        }
    }

    fn read(w: &mut impl Write, id: usize, entry: RegistryEntry) {
        let named_entry = entry.name().unwrap_or("(unnamed)");
        if let Ok(phydat) = entry.read() {
            writeln!(w, "Reading from {} ({}): {}", id, named_entry, phydat).unwrap();
        } else {
            writeln!(w, "Read error from {} ({})", id, named_entry).unwrap();
        }
    }

    fn write<'a>(w: &mut impl Write, mut args: impl ExactSizeIterator<Item=&'a str>) -> Result<(), &'static str> {
        let (n, entry) = reg_from_arg(args.next())?;
        if args.len() > 3 {
            return Err("Too many parameters");
        }
        let args: Result<heapless::Vec<i16, 3>, _> = args
            .map(|x| x.parse().map_err(|_| "Bad numeric argument"))
            .collect();
        let phydat = riot_wrappers::saul::Phydat::new(&args?, None, 0);
        writeln!(w, "Writing to {}: {}", n, phydat).unwrap();
        entry.write(phydat)
            .map_err(|_| "Write failed")
    }

    let result = match args.next() {
        None => Ok(enumerate(w)),
        Some("read") => match (args.next(), args.len()) {
            (Some("all"), 0) => {
                for (id, item) in RegistryEntry::all().enumerate() {
                    read(w, id, item);
                }
                Ok(())
            }
            (Some(x), 0) => {
                reg_from_arg(Some(x))
                    .map(|(n, entry)| read(w, n, entry))
            }
            _ => Err("Wrong number of arguments")
        }
        Some("write") => write(w, args),
        _ => Err("No such subcommand"),
    };

    if let Err(e) = result {
        writeln!(w, "Error: {}\nUsage: {} [read {{all|idx}}|write idx val1 [val2] [val3]]", e, commandname).unwrap();
    }
}