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}