ev3_dc/
lib.rs

1//! Low-level EV3 Direct command library.
2//! Library for direct command bytecode generation and basic direct reply parsing.
3//!
4//! By itself, It can allocate memory, create bytecode packet, parsing reply and other utility
5//! functions. Only some ad-hoc functions are available in `funcs`. Other OpCode is documented at Firmware Developer Kit.
6//! This library cannot prevent value overflow for any specific OpCode, only the parameter and command itself.
7//!
8//! More information about direct commands and bytecode is available at
9//! [LEGO MINDSTORMS Firmware Developer Kit](https://assets.education.lego.com/v3/assets/blt293eea581807678a/blt09ac3101d9df2051/5f88037a69efd81ab4debf2e/lego-mindstorms-ev3-communication-developer-kit.pdf?locale=en-us)
10//!
11//! # Example
12//! ## Show blinking green LED
13//! ```
14//! use ev3_dc::{ Command, Encoding::*, encode };
15//!
16//! let mut cmd = Command::new();
17//! let mut byte = vec![0x84, 0x1B]; // OpUI_Write, LED
18//! byte.extend(encode(LC0(0x04)).unwrap()); // Green flashing
19//! cmd.bytecode = byte;
20//! println!("SENT: {:02X?}", cmd.gen_bytes());
21//! // and send actual bytes via HID, or Bluetooth, etc.
22//! ```
23
24use std::error::Error;
25use displaystr::display;
26pub mod utils;
27pub mod parser;
28pub mod funcs;
29
30/// EV3 DataType.
31///
32/// DATAN is for custom array
33pub enum DataType {
34    /// 8-bits value
35    DATA8,
36    /// 16-bits value
37    DATA16,
38    /// 32-bits value
39    DATA32,
40    /// IEEE-754 single precision float i.e. [`f32`]
41    DATAF,
42    /// Array
43    DATAN(usize), // Custom length allocation
44    /// Zero-terminated string
45    DATAS(usize)
46}
47
48/// Parameter encoding
49///
50/// LCx: Local constant value
51///
52/// LVx: Local variable address
53///
54/// GVx: Global variable address
55///
56/// GV4 & LV4 & LV2 are unusable in direct command
57/// 
58/// There is [`utils::auto_const`] for automatic integer encoding
59pub enum Encoding<'a> {
60    /// 5-bits constant (-32 - 31)
61    LC0(i8),
62    /// 7-bits constant (-127 - 127)
63    LC1(i8),
64    /// 15-bits constant (-32_767 - 32_767)
65    LC2(i16),
66    /// 31-bits constant (-2_147_483_647 - 2_147_483_647)
67    LC4(i32),
68    /// IEEE-754 single precision constant
69    LCF(f32),
70    /// 5-bits local address
71    LV0(u8),
72    /// 7-bits local address
73    LV1(u8),
74    /// 5-bits global address (-32 - 31)
75    GV0(u8),
76    /// 7-bits global address (-127 - 127)
77    GV1(u8),
78    /// 15-bits global address (-32_767 - 32_767)
79    GV2(u16),
80    /// String (auto zero-terminated)
81    LCS(&'a str),
82}
83
84/// The packets that get sent to EV3. 
85/// Can contain more than 1 OpCode
86/// # Example
87/// ```
88/// let mut cmd = Command::new();
89/// let mut byte = vec![];
90/// // Add bytecode to byte
91/// cmd.bytecode = byte;
92/// println!("SENT: {:02X?}", cmd.gen_bytes());
93/// ```
94pub struct Command {
95    /// Command ID
96    pub id: u16,
97    /// Reply to direct command
98    pub reply: bool,
99    allocation: u16,
100    /// Bytes containing OpCodes and Parameter 
101    pub bytecode: Vec<u8>,
102}
103
104// Constants
105/// USB VendorId of EV3
106pub const VID: u16 = 0x0694;
107/// USB ProductId of EV3
108pub const PID: u16 = 0x0005;
109
110#[allow(non_snake_case)]
111/// Port struct. Use defined [`PORT`] constant instead.
112pub struct Port {
113    pub A: i8,
114    pub B: i8,
115    pub C: i8,
116    pub D: i8,
117    pub ALL: i8
118}
119/// PORT Constants. Add them together to use multiple ports
120pub const PORT: Port = Port { A: 1, B: 2, C: 4, D: 8, ALL: 15};
121
122#[derive(Debug)]
123#[display]
124/// ev3_dc Error type
125pub enum ValError {
126    // Error for encoding
127    /// [`encode`] failed to encode overflowed value
128    PosOverflow(u32, u32) = "Encode Error: Value {_0} overflowed {_1}",
129    /// [`encode`] failed to encode underflowed value
130    NegOverflow(i32, i32) = "Encode Error: Value {_0} overflowed {_1}",
131    // Error for functions
132    /// [`Command::allocate`] failed to allocated variable in memory
133    MemOverflow(u16, u16, u16, String) = "Allocation Error: Cannot allocate data with size {_0}. Allocated {_3} byte(s). Memory: {_1}/{_2}",
134    /// Value isn't in valid range
135    InvalidRange(i32, i32, i32) = "Invalid Range: Expect {_1} - {_2} got {_0}",
136    /// Value isn't validi
137    InvalidValue(i32, i32) = "Invalid Value: Expect {_1} got {_0}"
138}
139
140impl Error for ValError {}
141
142impl Command {
143    pub fn new() -> Self { Command::default() }
144    /// Generate direct command bytecode
145    pub fn gen_bytes(&self) -> Vec<u8> {
146        let mut packet: Vec<u8> = vec![0x00, 0x00];
147        packet.extend(self.id.to_le_bytes());
148        packet.push(match self.reply {
149            true => 0x00,
150            false => 0x80,
151        });
152        packet.extend(self.allocation.to_le_bytes());
153        packet.extend(&self.bytecode);
154        let ln = ((5 + self.bytecode.len()) as u16).to_le_bytes();
155        packet[0] = ln[0];
156        packet[1] = ln[1];
157        packet
158    }
159    /// Get reserved bytes
160    /// Can be use to initialize response buffer, with length of `5 + reserved_bytes()`.
161    pub fn reserved_bytes(&self) -> usize {
162        ((self.allocation >> 10) + (self.allocation & ((1 << 10) - 1))) as usize
163    }
164    /// Free all allocated memory
165    /// **Causing any variables in bytecode to not work**
166    pub fn mem_free(&mut self) {
167        self.allocation = 0;
168    }
169}
170
171/// Encode value to parameter encoding.
172/// For encoding constant value or encoding address to variable directly.
173/// Use [`Command::allocate`] to encode variable without specifying pointer directly
174/// # Example
175/// Encode local constant as LC1 bytecode with value of 42
176/// ```
177/// let byte: Vec<u8> = encode(LC1(42)).unwrap();
178/// println("Bytecode: {:02X?}", byte);
179/// ```
180pub fn encode(encoding: Encoding) -> Result<Vec<u8>, ValError> {
181let mut bytes: Vec<u8> = vec![];
182    let mut head: u8 = 0;
183    match encoding {
184        Encoding::LC0(val) => {
185            if val > 31 { return Err(ValError::PosOverflow(val as u32, 31)) }
186            if val < -31 { return Err(ValError::NegOverflow(val as i32, -31)) }
187            if val < 0 { head += 1 << 5;}
188            head += (val.abs() & 0b11111) as u8
189        }
190        Encoding::GV0(val) | Encoding::LV0(val) => {
191            if val > 31 { return Err(ValError::PosOverflow(val as u32, 31)) }
192            head += 1 << 6;
193            if let Encoding::GV0(_) = encoding { head += 1 << 5; }
194            head += val & 0b11111;
195        }
196        Encoding::LCS(_) => {
197            head += 0b10000100;
198        }
199        _ => { head += 1 << 7; }
200    }
201    bytes.push(head);
202    let res: Result<Vec<u8>, ValError> = match encoding {
203        Encoding::LC1(val) => { 
204            head += 1;
205            if val == i8::MIN { return Err(ValError::NegOverflow(val as i32, i8::MIN as i32)) }
206            if val < 0 { head += 1 << 5; }
207            Ok((val.abs() & i8::MAX).to_le_bytes().to_vec())
208        }
209        Encoding::LC2(val) => {
210            head += 2;
211            if val == i16::MIN { return Err(ValError::NegOverflow(val as i32, i16::MIN as i32)) }
212            if val < 0 { head += 1 << 5; }
213            Ok((val.abs() & i16::MAX).to_le_bytes().to_vec())
214        }
215        Encoding::LC4(val) => {
216            head += 3;
217            if val == i32::MIN { return Err(ValError::NegOverflow(val, i32::MIN)) }
218            if val < 0 { head += 1 << 5; }
219            Ok((val.abs() & i32::MAX).to_le_bytes().to_vec())
220        }
221        Encoding::LV1(val) | Encoding::GV1(val) => {
222            head += (1 << 5) + 1;
223            Ok(val.to_le_bytes().to_vec())
224        }
225        Encoding::GV2(val) => {
226            head += (1 << 5) + 2;
227            Ok(val.to_le_bytes().to_vec())
228        }
229        Encoding::LCS(val) => {
230            let mut tmp = val.as_bytes().to_vec();
231            tmp.push(0);
232            Ok(tmp)
233        }
234        Encoding::LCF(val) => {
235            head += 3;
236            Ok(val.to_le_bytes().to_vec())
237        }
238        _ => Ok(vec![]),
239    };
240    bytes.extend(res?);
241    bytes[0] = head;
242    Ok(bytes)
243}
244
245impl Command {
246    /// Create variable bytecode and allocate space in [`Command`]
247    /// Allocating global variables allow the values to be read in reply
248    pub fn allocate(&mut self, data: DataType, global: bool) -> Result<Vec<u8>, ValError> {
249        let local: u8 = (self.allocation >> 10) as u8;
250        let glob: u16 = self.allocation & ((1 << 10) - 1);
251        let address: u16 = if global { local.into() } else { glob };
252        let mem: u16 = match data {
253            DataType::DATA8 => 1,
254            DataType::DATA16 => 2,
255            DataType::DATA32 | DataType::DATAF => 4,
256            DataType::DATAN(length) => u16::try_from(length).unwrap(),
257            DataType::DATAS(length) => u16::try_from(length + 1).unwrap()
258        };
259        if local + (mem as u8) > (1 << 7) - 1 { return Err(ValError::MemOverflow(mem, local as u16, (1 << 7) - 1, "local".to_string())); }
260        if glob + mem > (1 << 10) - 1 { return Err(ValError::MemOverflow(mem, glob, (1 << 10) - 1, "global".to_string())); }
261        self.allocation += if global { mem } else { mem << 10 };
262        match address {
263            0..=31 => {
264                if global {
265                    encode(Encoding::GV0(address as u8))
266                } else {
267                    encode(Encoding::LV0(address as u8))
268                }
269            }
270            32..=254 => {
271                if global {
272                    encode(Encoding::GV1(address as u8))
273                } else {
274                    encode(Encoding::LV1(address as u8))
275                }
276            }
277            _ => {
278                if global {
279                    encode(Encoding::GV2(address))
280                }else {
281                    Err(ValError::PosOverflow(mem.into(), (1 << 7) - 1)) // Shouldn't be called
282                }
283            }
284        }
285    }
286}
287
288impl Default for Command {
289    fn default() -> Self {
290        Command {
291            id: 170,
292            reply: true, 
293            allocation: 0,
294            bytecode: vec![],
295        }
296    }
297}