Skip to main content

elytra_cli/
lib.rs

1use std::{error::Error, io::Write};
2
3use color_eyre::eyre::{eyre};
4use elytra_conf::{command::CommandKey, config::{EntryType, QueryTargetKey}, entry::ExtraFlags};
5
6pub mod wasm;
7pub mod tcp;
8pub mod tui;
9
10pub trait ElytraDevice: Send {
11    fn send_command_raw(&mut self, bytes: [u8; 64]) -> Result<[u8; 64], Box<dyn Error>>;
12    fn log_chat(&mut self, bytes_out: [u8; 64], bytes_in: [u8; 64]);
13    fn get_log(&mut self) -> Vec<([u8; 64], [u8; 64])>;
14}
15
16pub struct Section {
17    pub entry: Entry,
18    pub layout: Vec<(LayoutEntry, Entry)>
19}
20
21#[derive(Clone)]
22pub enum LayoutEntry {
23    Info(u8),
24    Prop(u8),
25}
26
27#[derive(Clone)]
28pub struct Entry {
29    pub name: String,
30    pub flags: ExtraFlags,
31    pub variant: u8,
32    pub constraints: [u8; 8],
33    pub icon: Option<String>,
34    pub help: Option<String>,
35    pub entry_type: u8,
36    pub layout: Option<Vec<LayoutEntry>>
37}
38
39pub struct Info {
40    pub proto_version: u8,
41    pub prop_count: u8,
42    pub info_count: u8,
43    pub section_count: u8,
44    pub action_count: u8,
45}
46
47fn err_msg(bytes: &[u8]) -> String {
48    String::from_utf8_lossy(&bytes[2..]).trim_end_matches('\0').to_owned()
49}
50
51impl dyn ElytraDevice {
52    pub fn get_entry(&mut self, entry_type: u8, index: u8) -> Result<Entry, Box<dyn Error>> {
53        let res = self.send_command( &[
54            CommandKey::Query as u8, 
55            entry_type, index, 
56            QueryTargetKey::Field as u8
57        ])?;
58        if res[0] != 1 { return Err(eyre!("Got error response: {} ({:02x?}) ", err_msg(&res), &res[1]))? }
59        let flags = ExtraFlags::from_bits_truncate(res[1]);
60        let variant = res[2];
61        let mut constraints = [0u8; 8];
62        constraints.copy_from_slice(&res[3..11]);
63        let name = str::from_utf8(&res[11..])?.trim_end_matches('\0').to_owned();
64
65        Ok(Entry {
66            name,
67            flags,
68            variant,
69            constraints,
70            entry_type,
71            help: None,
72            icon: None,
73            layout: None,
74        })
75    }
76
77    pub fn get_entries(&mut self, entry_type: u8, count: usize) -> Result<Vec<Entry>, Box<dyn Error>> {
78        (0..count).map(|index| self.get_entry(entry_type, index as u8)).collect()
79    }
80
81
82    pub fn get_info(&mut self) -> Result<Info, Box<dyn Error>>  {
83        let mut res = self.send_command(&[CommandKey::Meta as u8])?.into_iter();
84        if res.next() != Some(1) {
85            Err(eyre!("Got fail response from device!"))?;
86        }
87        
88        let proto_version = res.next().unwrap();
89        let section_count = res.next().unwrap();
90        let prop_count = res.next().unwrap();
91        let info_count = res.next().unwrap();
92        let action_count = res.next().unwrap();
93        Ok(Info {
94            proto_version,
95            prop_count,
96            info_count,
97            section_count,
98            action_count,
99        })
100    }
101
102    pub fn get_extra(&mut self, vt: u8, index: u8, q: u8) -> Result<String, Box<dyn Error>>  {
103        let res = self.send_command(&[b'q', vt, index, q])?;
104        Ok(String::from_utf8_lossy(&res[1..]).trim_end_matches('\0').to_string())
105    }
106
107    pub fn get_value(&mut self, entry_type: EntryType, index: u8) -> Result<[u8; 64], Box<dyn Error>>  {
108        let res = match entry_type {
109            EntryType::Info => self.send_command(&[CommandKey::ReadInfo as u8, index])?,
110            EntryType::Prop => self.send_command(&[CommandKey::ReadProp as u8, index])?,
111            et => Err(eyre!("Invalid entry type {et:?}"))?,
112        };
113        Ok(res)
114    }
115
116    pub fn get_layout(&mut self, index: u8) -> Result<Vec<LayoutEntry>, Box<dyn Error>>  {
117        let mut res = self.send_command(&[b'q', b's', index, b'l'])?.into_iter();
118        assert_eq!(1, res.next().unwrap());
119        let mut entries = Vec::new();
120        loop {
121            let Some(ft) = res.next() else {
122                break;
123            };
124            let Some(ix) = res.next() else {
125                break;
126            };
127            if ft == 0 {
128                break;
129            }
130            entries.push(match ft {
131                b'c' => LayoutEntry::Prop(ix),
132                b'i' => LayoutEntry::Info(ix),
133                ft => panic!("Unknown field type: {:02x}", ft)
134            });
135        }
136        Ok(entries)
137    }
138
139    pub fn send_command(&mut self, bytes: &[u8]) -> Result<[u8; 64], Box<dyn Error>> {
140        let mut out_bytes= [0u8; 64];
141        let _ = out_bytes.as_mut_slice().write(bytes)?;
142
143        let in_bytes = self.send_command_raw(out_bytes)?;
144        self.log_chat(out_bytes, in_bytes);
145
146        Ok(in_bytes)
147    }
148}