Init repo
This commit is contained in:
105
src/improv.rs
Normal file
105
src/improv.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
pub const IMPROV_HEADER: [u8; 6] = [
|
||||
'I' as u8,
|
||||
'M' as u8,
|
||||
'P' as u8,
|
||||
'R' as u8,
|
||||
'O' as u8,
|
||||
'V' as u8,
|
||||
];
|
||||
|
||||
pub const IMPROV_VERSION: u8 = 0x01;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum PacketType {
|
||||
CurrentState = 0x01,
|
||||
ErrorState = 0x02,
|
||||
RPCCommand = 0x03,
|
||||
RPCResult = 0x04,
|
||||
}
|
||||
|
||||
impl TryFrom<&u8> for PacketType {
|
||||
type Error= &'static str;
|
||||
|
||||
fn try_from(b: &u8) -> Result<Self, Self::Error> {
|
||||
match b {
|
||||
0x01 => Ok(Self::CurrentState),
|
||||
0x02 => Ok(Self::ErrorState),
|
||||
0x03 => Ok(Self::RPCCommand),
|
||||
0x04 => Ok(Self::RPCResult),
|
||||
_ => Err("Cannot convert to packet type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PacketType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CurrentState => write!(f, "Current State"),
|
||||
Self::ErrorState => write!(f, "Error State"),
|
||||
Self::RPCCommand => write!(f, "RPC Command"),
|
||||
Self::RPCResult => write!(f, "RPC Result"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum CurrentState {
|
||||
Ready = 0x02,
|
||||
Provisioning = 0x03,
|
||||
Provisioned = 0x04,
|
||||
}
|
||||
|
||||
|
||||
impl TryFrom<&u8> for CurrentState {
|
||||
type Error= &'static str;
|
||||
|
||||
fn try_from(b: &u8) -> Result<Self, Self::Error> {
|
||||
match b {
|
||||
0x02 => Ok(Self::Ready),
|
||||
0x03 => Ok(Self::Provisioning),
|
||||
0x04 => Ok(Self::Provisioned),
|
||||
_ => Err("Cannot convert to current state"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CurrentState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Ready => write!(f, "Ready"),
|
||||
Self::Provisioning => write!(f, "Provisioning"),
|
||||
Self::Provisioned => write!(f, "Provisioned"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum ErrorState {
|
||||
NoError = 0x00,
|
||||
InvalidRPCPacket = 0x01,
|
||||
UnknownRPCCommand = 0x02,
|
||||
UnableToConnect = 0x03,
|
||||
UnknownError = 0xFF,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[repr(u8)]
|
||||
pub enum RPCCommand {
|
||||
SendWifiSettings = 0x01,
|
||||
RequestCurrentState = 0x02,
|
||||
}
|
||||
|
||||
pub fn calculate_checksum(data: &Vec<u8>) -> u8 {
|
||||
// Pass data as full packet, with header, but without checksum byte
|
||||
|
||||
let mut checksum: u8 = 0x00;
|
||||
|
||||
for e in data {
|
||||
checksum = checksum.wrapping_add(e.clone());
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod improv;
|
179
src/main.rs
Normal file
179
src/main.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use anyhow::{
|
||||
Context,
|
||||
Result,
|
||||
};
|
||||
use clap::{
|
||||
Parser,
|
||||
Subcommand,
|
||||
};
|
||||
use env_logger;
|
||||
use hex;
|
||||
use improv_setup::improv::{
|
||||
IMPROV_HEADER,
|
||||
IMPROV_VERSION,
|
||||
PacketType,
|
||||
RPCCommand,
|
||||
CurrentState,
|
||||
calculate_checksum,
|
||||
};
|
||||
use log::{
|
||||
debug,
|
||||
log_enabled,
|
||||
info,
|
||||
Level,
|
||||
};
|
||||
use tokio_serial;
|
||||
|
||||
#[derive(Subcommand, Clone)]
|
||||
enum DeviceCommands {
|
||||
/// Set wifi credentials
|
||||
SetWifi {
|
||||
/// SSID of the network
|
||||
ssid: String,
|
||||
/// Password for the SSID
|
||||
password: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone)]
|
||||
enum Commands {
|
||||
/// List available serial devices
|
||||
ListDevices,
|
||||
/// Device
|
||||
Device {
|
||||
/// Path to the serial device
|
||||
path: String,
|
||||
/// Baud rate used for connecting to the serial device
|
||||
#[arg(long, default_value_t = 115200)]
|
||||
baud_rate: u32,
|
||||
#[command(subcommand)]
|
||||
device_command: Option<DeviceCommands>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
|
||||
fn to_ascii_debug(bytes: &Vec<u8>) -> String {
|
||||
let mut out = String::new();
|
||||
|
||||
for b in bytes {
|
||||
if b.is_ascii_graphic() {
|
||||
out += &b.escape_ascii().to_string();
|
||||
}
|
||||
else {
|
||||
out.push_str(".");
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
fn find_begin_of_improv_packet(buffer: &Vec<u8>) -> Result<usize, String> {
|
||||
|
||||
let mut improv_header_char: usize = 0;
|
||||
|
||||
for (i, b) in buffer.iter().enumerate() {
|
||||
if b == &IMPROV_HEADER[improv_header_char] {
|
||||
improv_header_char += 1;
|
||||
|
||||
if improv_header_char >= IMPROV_HEADER.len() {
|
||||
return Ok(i - (IMPROV_HEADER.len() - 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
improv_header_char = 0;
|
||||
if b == &IMPROV_HEADER[improv_header_char] {
|
||||
improv_header_char += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(String::from("Improv header not found"));
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()>{
|
||||
env_logger::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let command: Commands = match &cli.command {
|
||||
Some(command) => command.clone(),
|
||||
None => Commands::ListDevices,
|
||||
};
|
||||
|
||||
match &command {
|
||||
Commands::ListDevices => {
|
||||
println!("{}", tokio_serial::available_ports()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|serialport| serialport.port_name.clone())
|
||||
.fold(String::new(), |a, b| a + &b + &String::from("\n")));
|
||||
},
|
||||
Commands::Device {path, baud_rate, device_command} => {
|
||||
let mut rpc_command_request_current_state: Vec<u8> = Vec::new();
|
||||
rpc_command_request_current_state.extend(IMPROV_HEADER);
|
||||
rpc_command_request_current_state.push(IMPROV_VERSION);
|
||||
rpc_command_request_current_state.push(PacketType::RPCCommand as u8);
|
||||
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
data.push(RPCCommand::RequestCurrentState as u8); // command
|
||||
data.push(0x00); // command data length
|
||||
|
||||
rpc_command_request_current_state.push(data.len().try_into().unwrap()); // length
|
||||
rpc_command_request_current_state.extend(data.clone()); // data
|
||||
|
||||
let checksum: u8 = calculate_checksum(&rpc_command_request_current_state);
|
||||
println!("{}", hex::encode([checksum.clone()]));
|
||||
rpc_command_request_current_state.push(checksum);
|
||||
|
||||
println!("{}", hex::encode(&rpc_command_request_current_state));
|
||||
println!("{}", to_ascii_debug(&rpc_command_request_current_state));
|
||||
|
||||
let mut serial_interface = tokio_serial::new(path, *baud_rate).open().unwrap();
|
||||
|
||||
serial_interface.write(&rpc_command_request_current_state).unwrap();
|
||||
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
serial_interface.read_to_end(&mut buffer);
|
||||
|
||||
println!("{}", hex::encode(&buffer));
|
||||
println!("{}", to_ascii_debug(&buffer));
|
||||
println!("{}", std::str::from_utf8(&buffer).unwrap_or(""));
|
||||
|
||||
let improv_packet_offset = find_begin_of_improv_packet(&buffer).unwrap();
|
||||
println!("{}", improv_packet_offset);
|
||||
|
||||
let mut packet_index = improv_packet_offset;
|
||||
|
||||
// skip header
|
||||
packet_index += 6;
|
||||
|
||||
// version
|
||||
println!("Version: {}", &buffer[packet_index]);
|
||||
packet_index += 1;
|
||||
|
||||
// type
|
||||
let packet_type = PacketType::try_from(&buffer[packet_index]).unwrap();
|
||||
println!("Type: {}", &packet_type);
|
||||
packet_index += 1;
|
||||
|
||||
if packet_type == PacketType::CurrentState {
|
||||
// skip length
|
||||
packet_index +=1;
|
||||
|
||||
let current_state = CurrentState::try_from(&buffer[packet_index]).unwrap();
|
||||
println!("Current state: {}", ¤t_state);
|
||||
packet_index += 1;
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user