etp 0.0.1-alpha

Embedded Tester Library (ETP). Control embedded devices from host!
Documentation
//! # Embedded Tester Protocol (ETP) library
//!
//! - ETP allows you to test/automate low-level functionality on your embedded device from your host PC.
//! - Note that ETP requires a target firmware client that will accept ETP requests.
//! - You can download prebuilt target ETP firmware for the following boards:
//!    - Arduino Uno
//!    - ESP32
//!
//! ## Arduino UNO
//! - Download firmware from [here.](https://www.rust-lang.org) TODO: FIXME
//! - Program `arduino_uno.hex`
//!
//!   `avrdude -v -p atmega328p -c arduino -P /dev/ttyUSB0 -b 115200 -D -U flash:w:arduino_uno.hex:i`
//!
//! ## ESP32
//! Coming soon
//!

pub mod constants;
pub mod error;
pub mod peripherals;
pub mod protocol;
pub mod transport;
pub mod util;
pub mod version;

use crate::error::{EtpError, EtpResult};
use constants::{MIN_RESPONSE_SIZE, TRANSPORT_QUEUE_SIZE};
use peripherals::gpio::Gpio;
use protocol::{
    ETP_MESSAGE_HEADER_SIZE, EtpFirmwareInfoCmd, EtpMessageHeader, EtpOperations, EtpPayloadType,
    ResetType,
};
use std::sync::{Arc, Mutex, mpsc};
use tracing::{debug, info};
use transport::common::TransportLayer;
use version::EtpFirmwareInfo;

pub struct Etp {
    sink: mpsc::SyncSender<Vec<u8>>,
    source: mpsc::Receiver<Vec<u8>>,

    pub gpio: Gpio,
}

fn hex_string(data: &Vec<u8>) -> String {
    let mut hex_string = String::new();
    for byte in data {
        hex_string.push_str(&format!("0x{:02X} ", byte));
    }
    hex_string.trim_end().to_string()
}

impl Etp {
    pub fn new<T: TransportLayer + Send + 'static>(transport: T) -> EtpResult<Self> {
        let (sender_queue_sender, sender_queue_receiver) = mpsc::sync_channel(TRANSPORT_QUEUE_SIZE);
        let (receiver_queue_sender, receiver_queue_receiver) =
            mpsc::sync_channel(TRANSPORT_QUEUE_SIZE);
        let transport = Arc::new(Mutex::new(transport));

        // Spawn threads to handle sending and receiving
        let transport_sender = Arc::clone(&transport);
        // Sender thread
        std::thread::spawn(move || {
            loop {
                let recv_result = sender_queue_receiver.recv();
                match recv_result {
                    Ok(command) => {
                        // Sleep for a bit to allow the receiver to receive
                        std::thread::sleep(std::time::Duration::from_millis(1));

                        let mut transport = transport_sender.lock().unwrap();
                        // Debug print as hex
                        debug!("[ETP] Sending command: {:?}", hex_string(&command));
                        transport.send(command).unwrap();
                    }
                    Err(_) => {
                        info!("Sender queue closed, exiting thread.");
                        break;
                    }
                }
            }
        });

        // Receiver thread
        let transport_receiver = Arc::clone(&transport);
        std::thread::spawn(move || {
            loop {
                // Sleep for a bit to allow the sender to send
                std::thread::sleep(std::time::Duration::from_millis(1));

                let mut transport = transport_receiver.lock().unwrap();
                let response = transport.receive(MIN_RESPONSE_SIZE);
                match response {
                    Ok(response) => {
                        if response.is_empty() {
                            continue;
                        } else {
                            debug!("[ETP] Received response: {:?}", hex_string(&response));
                            receiver_queue_sender.send(response).unwrap();
                        }
                    }
                    Err(e) => {
                        debug!("Receiver error, transport probably closed {:?}", e);
                        break;
                    }
                };
            }
        });

        let mut etp = Etp {
            sink: sender_queue_sender,
            source: receiver_queue_receiver,

            gpio: Gpio::new(),
        };
        etp.reset()?;

        Ok(etp)
    }

    fn send(&self, command: Vec<u8>) -> EtpResult<()> {
        self.sink
            .send(command)
            .map_err(|_| EtpError::TransportSendError("Failed to send command".to_string()))?;
        Ok(())
    }

    fn receive(&self, expected_bytes: usize) -> EtpResult<Vec<u8>> {
        let mut message: Vec<u8> = Vec::new();
        let mut bytes_received = 0;
        while bytes_received < expected_bytes {
            let response = self.source.recv().map_err(|_| {
                EtpError::TransportReceiveError("Failed to receive response".to_string())
            })?;
            bytes_received += response.len();
            message.extend(response);
        }

        Ok(message)
    }

    fn frame_cmd_packet(&self, command: EtpOperations, payload: Vec<u8>) -> Vec<u8> {
        let header = EtpMessageHeader::new(
            payload.len() as u16,
            protocol::EtpStatusCodes::Success,
            EtpPayloadType::Command,
            command,
        );

        let mut packet = Vec::new();
        packet.extend_from_slice(Into::<Vec<u8>>::into(header).as_slice());
        packet.extend_from_slice(&payload);

        packet
    }

    pub fn get_fw_info(&mut self) -> EtpResult<EtpFirmwareInfo> {
        let request = self.frame_cmd_packet(
            EtpOperations::GetFirmwareInfo,
            vec![EtpFirmwareInfoCmd::ProtocolVersion as u8],
        );
        self.send(request)?;
        let bytes = self.receive(ETP_MESSAGE_HEADER_SIZE + 4)?;

        // Received bytes are little endian, parse as ascii-string
        let version = format!(
            "{}.{}.{}",
            bytes[ETP_MESSAGE_HEADER_SIZE + 1],
            bytes[ETP_MESSAGE_HEADER_SIZE + 2],
            bytes[ETP_MESSAGE_HEADER_SIZE + 3]
        );

        let request = self.frame_cmd_packet(
            EtpOperations::GetFirmwareInfo,
            vec![EtpFirmwareInfoCmd::FirmwareVersion as u8],
        );
        self.send(request)?;
        let bytes = self.receive(ETP_MESSAGE_HEADER_SIZE + 4)?;
        let fw_version = format!(
            "{}.{}.{}",
            bytes[ETP_MESSAGE_HEADER_SIZE + 1],
            bytes[ETP_MESSAGE_HEADER_SIZE + 2],
            bytes[ETP_MESSAGE_HEADER_SIZE + 3]
        );

        let request = self.frame_cmd_packet(
            EtpOperations::GetFirmwareInfo,
            vec![EtpFirmwareInfoCmd::BuildDate as u8],
        );
        self.send(request)?;
        let bytes = self.receive(ETP_MESSAGE_HEADER_SIZE + 4)?;

        let payload = bytes[ETP_MESSAGE_HEADER_SIZE..].to_vec();
        let months = [
            "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
        ];
        let month = months[payload[3] as usize - 1];
        let day = payload[4];
        let year: u16 = payload[1] as u16 | 0xFF00 & (payload[2] as u16) << 8;
        let build_date = format!("{}-{}-{}", year, month, day);

        let build_time = format!("{:02}:{:02}:{:02}", payload[5], payload[6], payload[7]);

        let request = self.frame_cmd_packet(
            EtpOperations::GetFirmwareInfo,
            vec![EtpFirmwareInfoCmd::HardwareType as u8],
        );

        self.send(request)?;
        let bytes = self.receive(ETP_MESSAGE_HEADER_SIZE + 4)?;
        let hardware_type =
            String::from_utf8_lossy(&bytes[ETP_MESSAGE_HEADER_SIZE + 1..]).to_string();
        let hardware_type = hardware_type.trim_end_matches(char::from(0));

        Ok(EtpFirmwareInfo {
            protocol_version: version,
            fw_version: fw_version,
            build_date: build_date + " " + &build_time,
            hardware_type: hardware_type.to_string(),
        })
    }

    pub fn reset(&mut self) -> EtpResult<()> {
        let request = self.frame_cmd_packet(
            // TODO: u8 or u16?
            EtpOperations::Reset,
            vec![ResetType::ReInit as u8],
        );
        self.send(request)?;

        // TODO: Acknowlege?
        let _ = self.receive(0)?;

        // Wait for the device to be ready - sleep
        std::thread::sleep(std::time::Duration::from_secs(3));
        Ok(())
    }
}