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
This commit is contained in:
əlemi 2022-08-10 23:02:04 +02:00
parent e400459168
commit def620665e
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
5 changed files with 246 additions and 79 deletions

View file

@ -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"

View file

@ -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])

View file

@ -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<PwmOutput<Timer2Pwm>, PD3>,
led2: Pin<PwmOutput<Timer1Pwm>, PB1>,
led3: Pin<PwmOutput<Timer1Pwm>, PB2>,
led4: Pin<PwmOutput<Timer2Pwm>, PB3>,
button: Pin<Input<PullUp>, PD2>,
counter: u8,
}
mod packet;
mod utils;
impl FourLedDisplay {
fn new(
led1: Pin<PwmOutput<Timer2Pwm>, PD3>,
led2: Pin<PwmOutput<Timer1Pwm>, PB1>,
led3: Pin<PwmOutput<Timer1Pwm>, PB2>,
led4: Pin<PwmOutput<Timer2Pwm>, PB3>,
button: Pin<Input<PullUp>, 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();
},
}
}
}

92
src/packet.rs Normal file
View file

@ -0,0 +1,92 @@
const PACKET_BUFFER : usize = 32;
#[derive(Copy, Clone)]
pub enum PacketId {
Invalid = 0xFF,
Reset = 0x00,
SetLedsPacket = 0x01,
}
impl From<u8> 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<Packet> {
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
}
}

53
src/utils.rs Normal file
View file

@ -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<PwmOutput<Timer2Pwm>, PD3>,
led2: Pin<PwmOutput<Timer1Pwm>, PB1>,
led3: Pin<PwmOutput<Timer1Pwm>, PB2>,
led4: Pin<PwmOutput<Timer2Pwm>, PB3>,
}
impl FourLedDisplay {
pub fn new(
mut led1: Pin<PwmOutput<Timer2Pwm>, PD3>,
mut led2: Pin<PwmOutput<Timer1Pwm>, PB1>,
mut led3: Pin<PwmOutput<Timer1Pwm>, PB2>,
mut led4: Pin<PwmOutput<Timer2Pwm>, 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
}
}