From def620665ec84ebe545e2c2609926c7c142049a5 Mon Sep 17 00:00:00 2001 From: alemidev Date: Wed, 10 Aug 2022 23:02:04 +0200 Subject: [PATCH] feat: added display code, basic packet handling added a super basic packet handling mechanism with a tiny buffer, allowing for easier expansion later on. Added display code, waiting for actual display to test, improved the cpu load script --- Cargo.toml | 3 +- script/cpu-usage.py | 36 +++++++---- src/main.rs | 141 +++++++++++++++++++++++--------------------- src/packet.rs | 92 +++++++++++++++++++++++++++++ src/utils.rs | 53 +++++++++++++++++ 5 files changed, 246 insertions(+), 79 deletions(-) create mode 100644 src/packet.rs create mode 100644 src/utils.rs diff --git a/Cargo.toml b/Cargo.toml index 38af430..60b0ad2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ panic-halt = "0.2.0" ufmt = "0.1.0" nb = "1" embedded-hal = "0.2.3" -embedded-msgpack = "0.2" +sh1106 = "0.4" +embedded-graphics = "0.7" [dependencies.arduino-hal] git = "https://github.com/rahix/avr-hal" diff --git a/script/cpu-usage.py b/script/cpu-usage.py index e532abf..c3e1cba 100755 --- a/script/cpu-usage.py +++ b/script/cpu-usage.py @@ -1,22 +1,38 @@ #!/usr/bin/env python -from time import sleep +import sys import struct -import serial +from time import sleep +import serial import psutil -def avg_usage_to_serial(dev:str): - port = serial.Serial(dev, baudrate=57600) +def cpu_load_serial_driver(device:str, retry_interval:float=5.0): + while True: + try: + port = serial.Serial(device, baudrate=57600) + avg_usage_to_serial(port) + except serial.SerialException as e: + print(f"[!] Could not connect to device: {str(e)}", file=sys.stderr) + sleep(retry_interval) + +def avg_usage_to_serial(port:serial.Serial): + port.write(struct.pack("BB", 0, 0)) + port.flush() while True: # Map float [0:100] to int [0:255], square it to put more values in the lower end, where led is more sensible - load = [ int(((x/100) **2) * 255) for x in psutil.cpu_percent(0.1, percpu=True) ] # mypy whines but percpu returns a list - port.write(struct.pack("BBBB", *load)) - port.flush() + load = [ int(((x/100) **2) * 255) for x in psutil.cpu_percent(0.05, percpu=True) ] # mypy whines but percpu returns a list + try: + port.write(struct.pack("BBBBBB", 1, 4, *load)) + port.flush() + except serial.SerialException as e: + print(f"[!] Failed writing payload to device: {str(e)}", file=sys.stderr) + break + if __name__ == "__main__": - import sys if len(sys.argv) < 2: print("[!] No device specified") - else: - avg_usage_to_serial(sys.argv[1]) + exit(-1) + + cpu_load_serial_driver(sys.argv[1]) diff --git a/src/main.rs b/src/main.rs index 3111fa0..632b49a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,91 +1,96 @@ #![no_std] #![no_main] +use packet::PacketId; use panic_halt as _; use embedded_hal::serial::Read; -use arduino_hal::{simple_pwm::*, port::{Pin, mode::{PwmOutput, PullUp, Input}}, hal::port::{PD3, PB1, PB2, PB3, PD2}}; +use arduino_hal::simple_pwm::{IntoPwmPin, Prescaler, Timer1Pwm, Timer2Pwm}; +use sh1106::{prelude::GraphicsMode, Builder}; -// TODO can I make it a generic "Pin" and use a slice? -struct FourLedDisplay { - led1: Pin, PD3>, - led2: Pin, PB1>, - led3: Pin, PB2>, - led4: Pin, PB3>, - button: Pin, PD2>, - counter: u8, -} +mod packet; +mod utils; -impl FourLedDisplay { - fn new( - led1: Pin, PD3>, - led2: Pin, PB1>, - led3: Pin, PB2>, - led4: Pin, PB3>, - button: Pin, PD2>, - ) -> Self { - FourLedDisplay{ - led1, led2, led3, led4, button, - counter: 0, - } - } - - fn update(&mut self, value: u8) { - match self.counter { - 0 => self.led1.set_duty(value), - 1 => self.led2.set_duty(value), - 2 => self.led3.set_duty(value), - 3 => self.led4.set_duty(value), - _ => {}, - } - self.counter = (self.counter + 1) % 4; - } - - fn init(mut self) -> Self { - self.led1.enable(); - self.led2.enable(); - self.led3.enable(); - self.led4.enable(); - self - } - - fn should_reset(&self) -> bool { self.button.is_low() } - - fn reset(&mut self) { - self.counter = 0; - self.led1.set_duty(0); - self.led2.set_duty(0); - self.led3.set_duty(0); - self.led4.set_duty(0); - } -} +use crate::packet::PacketBuilder; +use crate::utils::FourLedDisplay; #[arduino_hal::entry] fn main() -> ! { // init board peripherals let dp = arduino_hal::Peripherals::take().unwrap(); - let timer1 = Timer1Pwm::new(dp.TC1, Prescaler::Prescale8); - let timer2 = Timer2Pwm::new(dp.TC2, Prescaler::Prescale8); + let timer1 = Timer1Pwm::new(dp.TC1, Prescaler::Direct); + let timer2 = Timer2Pwm::new(dp.TC2, Prescaler::Direct); let pins = arduino_hal::pins!(dp); - let led1 = pins.d3.into_output().into_pwm(&timer2); - let led2 = pins.d9.into_output().into_pwm(&timer1); - let led3 = pins.d10.into_output().into_pwm(&timer1); - let led4 = pins.d11.into_output().into_pwm(&timer2); + let mut led_load = pins.d6.into_output(); let button = pins.d2.into_pull_up_input(); + let mut cpu_leds = FourLedDisplay::new( + pins.d3.into_output().into_pwm(&timer2), + pins.d9.into_output().into_pwm(&timer1), + pins.d10.into_output().into_pwm(&timer1), + pins.d11.into_output().into_pwm(&timer2), + ); + let i2c = arduino_hal::i2c::I2c::new( + dp.TWI, pins.a4.into_pull_up_input(), pins.a5.into_pull_up_input(), 100000 + ); let mut serial = arduino_hal::default_serial!(dp, pins, 57600); - // prepare display struct - let mut display = FourLedDisplay::new(led1, led2, led3, led4, button).init(); + led_load.set_high(); + + // prepare display + let mut display: GraphicsMode<_> = Builder::new().with_size(sh1106::prelude::DisplaySize::Display128x64).connect_i2c(i2c).into(); + cpu_leds.set(1, 255); + display.init().unwrap(); + cpu_leds.set(2, 255); + + let mut flip : u8 = 0; + for x in 0..128 { + for y in 0..64 { + cpu_leds.set(3, if flip == 0 { 0 } else { 255 }); + display.set_pixel(x, y, flip); + flip = !flip; + } + } + cpu_leds.set(3, 255); + + display.flush().unwrap(); + cpu_leds.set(4, 255); + + led_load.set_low(); + + let mut pkt_builder = PacketBuilder::new(); + + arduino_hal::delay_ms(25); + cpu_leds.set_all(0); loop { // main loop - if display.should_reset() { - display.reset(); - } else { - match serial.read() { - Ok(value) => display.update(value), - Err(_) => {}, - } + if button.is_low() { // If reset button is pressed, don't process serial bus + cpu_leds.set_all(0); + led_load.set_high(); + continue; + } + + match serial.read() { // if there's a byte available + Ok(value) => { + led_load.set_high(); + if let Some(pkt) = pkt_builder.update(value) { // update packet builder + match pkt.id { // if a packet is ready, match against its id + PacketId::Reset => { + cpu_leds.set_all(0); + }, + PacketId::SetLedsPacket => { + if let Some(payload) = pkt.payload && payload.len() == 4 { + cpu_leds.set_many(payload[0], payload[1], payload[2], payload[3]); + } + }, + _ => {}, // TODO log it? + } + } + }, + Err(_) => { + led_load.set_low(); + }, } } } + diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..1f9a5ae --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,92 @@ +const PACKET_BUFFER : usize = 32; + +#[derive(Copy, Clone)] +pub enum PacketId { + Invalid = 0xFF, + Reset = 0x00, + SetLedsPacket = 0x01, +} + +impl From for PacketId { + fn from(x: u8) -> Self { + match x { + 0 => PacketId::Reset, + 1 => PacketId::SetLedsPacket, + _ => PacketId::Invalid, + } + } +} + +pub struct Packet<'a> { + pub id: PacketId, + pub payload: Option<&'a[u8]>, +} + +enum PacketBuilderStep { + ID, + SIZE, + PAYLOAD, +} + +pub struct PacketBuilder { + step: PacketBuilderStep, + id: PacketId, + size: u8, + index: usize, + buffer: [u8; PACKET_BUFFER], +} + +impl PacketBuilder { + pub fn new() -> Self { + PacketBuilder { + step: PacketBuilderStep::ID, + id: PacketId::Invalid, + size: 0, + index: 0, + buffer: [0;PACKET_BUFFER], + } + } + + pub fn update(&mut self, byte:u8) -> Option { + let mut ret = None; + match self.step { + PacketBuilderStep::ID => { + let id = PacketId::from(byte); + match id { + PacketId::SetLedsPacket | PacketId::Reset => { + self.id = id; + self.step = PacketBuilderStep::SIZE; + }, + _ => { + // TODO log it somehow? + }, + } + }, + PacketBuilderStep::SIZE => { + if byte as usize > PACKET_BUFFER { + self.step = PacketBuilderStep::ID; + // TODO log it somehow? + } else if byte == 0 { + // packet without payload + ret = Some(Packet{id: self.id, payload: None}); // jank zero size slice + self.step = PacketBuilderStep::ID; + } else { + self.size = byte; + self.index = 0; + self.step = PacketBuilderStep::PAYLOAD; + } + }, + PacketBuilderStep::PAYLOAD => { + self.buffer[self.index] = byte; + self.index += 1; + if self.index >= self.size as usize { + ret = Some(Packet{id: self.id, payload: Some(&self.buffer[0..self.size as usize])}); + self.step = PacketBuilderStep::ID; + } + }, + } + ret + } +} + + diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..1db7f1e --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,53 @@ +use arduino_hal::{simple_pwm::*, port::{Pin, mode::PwmOutput}, hal::port::{PD3, PB1, PB2, PB3}}; + +// TODO can I make it a generic "Pin" and use a slice? +pub struct FourLedDisplay { + led1: Pin, PD3>, + led2: Pin, PB1>, + led3: Pin, PB2>, + led4: Pin, PB3>, +} + +impl FourLedDisplay { + pub fn new( + mut led1: Pin, PD3>, + mut led2: Pin, PB1>, + mut led3: Pin, PB2>, + mut led4: Pin, PB3>, + ) -> Self { + led1.enable(); + led2.enable(); + led3.enable(); + led4.enable(); + FourLedDisplay{ + led1, led2, led3, led4, + } + } + + pub fn set(&mut self, index:u8, value:u8) -> &mut Self { + match index { + 1 => self.led1.set_duty(value), + 2 => self.led2.set_duty(value), + 3 => self.led3.set_duty(value), + 4 => self.led4.set_duty(value), + _ => {}, + } + self + } + + pub fn set_all(&mut self, value:u8) -> &mut Self { + self.led1.set_duty(value); + self.led2.set_duty(value); + self.led3.set_duty(value); + self.led4.set_duty(value); + self + } + + pub fn set_many(&mut self, first:u8, second:u8, third:u8, fourth:u8) -> &mut Self { + self.led1.set_duty(first); + self.led2.set_duty(second); + self.led3.set_duty(third); + self.led4.set_duty(fourth); + self + } +}