From c31624af0cc7f98a4d94e0ba9fcc4cbc52994221 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Thu, 9 Mar 2023 11:19:17 +0800 Subject: [PATCH 01/13] Rust CLI: Add --wait-for-device Allows to try connecting to the device until it appears. Useful for starting the command before the module is plugged in. Signed-off-by: Daniel Schaefer --- inputmodule-control/src/inputmodule.rs | 56 ++++++++++++++++---------- inputmodule-control/src/main.rs | 4 ++ 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 595b78fa..d5570924 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -81,29 +81,43 @@ fn find_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec< /// Commands that interact with serial devices pub fn serial_commands(args: &crate::ClapCli) { - let ports = serialport::available_ports().expect("No ports found!"); - if args.list || args.verbose { - for p in &ports { - //println!("{}", p.port_name); - println!("{p:?}"); - } - } - let serialdevs = match &args.command { - Some(crate::Commands::LedMatrix(ledmatrix_args)) => { - find_serialdevs(&ports, &ledmatrix_args.serial_dev) - } - Some(crate::Commands::B1Display(ledmatrix_args)) => { - find_serialdevs(&ports, &ledmatrix_args.serial_dev) + let mut serialdevs: Vec; + loop { + let ports = serialport::available_ports().expect("No ports found!"); + if args.list || args.verbose { + for p in &ports { + // TODO: Print prettier + //println!("{}", p.port_name); + println!("{p:?}"); + } } - Some(crate::Commands::C1Minimal(c1minimal_args)) => { - find_serialdevs(&ports, &c1minimal_args.serial_dev) + serialdevs = match &args.command { + Some(crate::Commands::LedMatrix(ledmatrix_args)) => { + find_serialdevs(&ports, &ledmatrix_args.serial_dev) + } + Some(crate::Commands::B1Display(ledmatrix_args)) => { + find_serialdevs(&ports, &ledmatrix_args.serial_dev) + } + Some(crate::Commands::C1Minimal(c1minimal_args)) => { + find_serialdevs(&ports, &c1minimal_args.serial_dev) + } + None => vec![], + }; + if serialdevs.is_empty() { + if args.wait_for_device { + // Try again after short wait + thread::sleep(Duration::from_millis(100)); + continue; + } else { + println!( + "Failed to find serial devivce. Please manually specify with --serial-dev" + ); + return; + } + } else { + break; } - None => vec![], - }; - if serialdevs.is_empty() { - println!("Failed to find serial device. Please manually specify with --serial-dev"); - return; - }; + } match &args.command { // TODO: Handle generic commands without code deduplication diff --git a/inputmodule-control/src/main.rs b/inputmodule-control/src/main.rs index cabbd0de..5448f23a 100644 --- a/inputmodule-control/src/main.rs +++ b/inputmodule-control/src/main.rs @@ -41,6 +41,10 @@ pub struct ClapCli { /// PID (Product ID) in hex digits #[arg(long)] pid: Option, + + /// Retry connecting to the device until it works + #[arg(long)] + wait_for_device: bool, } fn main() { From 2ee799dd0726668a1b3601bc75d75da948583c4c Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 10:34:35 +0800 Subject: [PATCH 02/13] ctrl: Improve B1 image sending Open serialport only once. Instead of 400x per image. Sending a single row went from 2ms to 0.5ms. Still pretty slow. Signed-off-by: Daniel Schaefer --- inputmodule-control/src/inputmodule.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index d5570924..3417f2c6 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -325,6 +325,17 @@ fn simple_cmd(serialdev: &str, command: Command, args: &[u8]) { simple_cmd_port(&mut port, command, args); } +fn open_serialport(serialdev: &str) -> Box { + serialport::new(serialdev, 115_200) + .timeout(SERIAL_TIMEOUT) + .open() + .expect("Failed to open port") +} + +fn simple_open_cmd(serialport: &mut Box, command: Command, args: &[u8]) { + simple_cmd_port(serialport, command, args); +} + fn simple_cmd_port(port: &mut Box, command: Command, args: &[u8]) { let mut buffer: [u8; 64] = [0; 64]; buffer[..2].copy_from_slice(FWK_MAGIC); @@ -676,6 +687,7 @@ fn set_color_cmd(serialdev: &str, color: Color) { /// Must be 300x400 in size. /// Sends one 400px column in a single commands and a flush at the end fn b1display_bw_image_cmd(serialdev: &str, image_path: &str) { + let mut serialport = open_serialport(serialdev); let img = ImageReader::open(image_path) .unwrap() .decode() @@ -710,8 +722,8 @@ fn b1display_bw_image_cmd(serialdev: &str, image_path: &str) { } } - simple_cmd(serialdev, Command::SetPixelColumn, &vals); + simple_open_cmd(&mut serialport, Command::SetPixelColumn, &vals); } - simple_cmd(serialdev, Command::FlushFramebuffer, &[]); + simple_open_cmd(&mut serialport, Command::FlushFramebuffer, &[]); } From 7e8aa428959bacd405f41249b6491f533a1c2484 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Wed, 8 Mar 2023 04:54:21 +0800 Subject: [PATCH 03/13] Add API command documentation Signed-off-by: Daniel Schaefer --- README.md | 2 ++ commands.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 commands.md diff --git a/README.md b/README.md index 93edb36f..b7ecce7c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Future features: ## Control from the host +See: [API command documentation](commands.md) + Requirements: Python, [PySimpleGUI](https://www.pysimplegui.org) and optionally [pillow](https://pillow.readthedocs.io/en/stable/index.html) Use `control.py`. Either the commandline, see `control.py --help` or the graphical version: `control.py --gui` diff --git a/commands.md b/commands.md new file mode 100644 index 00000000..00ddfb66 --- /dev/null +++ b/commands.md @@ -0,0 +1,100 @@ +# Commands + +The input modules can be controlled by sending commands via the USB CDC-ACM +serial port. To send a command, write the two magic bytes, command ID and +parameters. Most commands don't return anything. + +Simple example in Python: + +```python +import serial + +def send_command(command_id, parameters, with_response=False) + with serial.Serial(/dev/ttyACM0, 115200) as s: + s.write([0x32, 0xAC, command_id] + parameters) + + if with_response: + res = s.read(32) + return res + +# Go to sleep and check the status +send_command(0x03, [True]) +res = send_command(0x03, [], with_response=True) +print(f"Is currently sleeping: {bool(res[0])}) +``` + +Many commands support setting and writing a value, with the same command ID. +When no parameters are given, the current value is queried and returned. + +###### Modules: + +- L = LED Matrix +- D = B1 Display +- M = C1 Minimal Module + +## Command overview + +| Command | ID | Modules | Response | Parameters | Behavior | +| ------------ | ---- | ------- | -------- | ---------- | ------------------------ | +| Brightness | 0x00 | `L M` | | | Set LED brightness | +| Pattern | 0x01 | `L ` | | | Display a pattern | +| Bootloader | 0x02 | `LDM` | | | Jump to the bootloader | +| Sleep | 0x03 | `LDM` | | bool | Go to sleep or wake up | +| GetSleep | 0x03 | `LDM` | bool | | Check sleep state | +| Animate | 0x04 | `L ` | | bool | Scroll current pattern | +| GetAnimate | 0x04 | `L ` | bool | | Check whether animating | +| Panic | 0x05 | `LDM` | | | Cause a FW panic/crash | +| DrawBW | 0x06 | `L ` | | 39 Bytes | Draw a black/white image | +| StageCol | 0x07 | `L ` | | 1+34 Bytes | Send a greyscale column | +| FlushCols | 0x08 | `L ` | | | Flush/draw all columns | +| SetText | 0x09 | ` D ` | | | TODO: Remove | +| StartGame | 0x10 | `L ` | | 1B Game ID | Start an embeded game | +| GameCtrl | 0x11 | `L ` | | 1B Control | Send a game command | +| GameStatus | 0x12 | `L ` | WIP | | Check the game status | +| SetColor | 0x13 | ` M` | | 3B: RGB | Set the LED's color | +| DisplayOn | 0x14 | ` D ` | | bool | Turn the display on/off | +| InvertScreen | 0x15 | ` D ` | | bool | Invert scren on/off | +| SetPxCol | 0x16 | ` D ` | | 50 Bytes | Send a column of pixels | +| FlushFB | 0x17 | ` D ` | | | Flush all columns | +| Version | 0x20 | ` D ` | 3 Bytes | | Get firmware version | + +#### Pattern (0x01) + +The following patterns are defined + +- 0x00 - Percentage (See below, needs another parameter) +- 0x01 - Gradient (Brightness gradient from top to bottom) +- 0x02 - DoubleGradient (Brightness gradient from the middle to top and bottom) +- 0x03 - DisplayLotusHorizontal (Display "LOTUS" 90 degree rotated) +- 0x04 - ZigZag (Display a zigzag pattern) +- 0x05 - FullBrightness (Turn every LED on and set the brightness to 100%) +- 0x06 - DisplayPanic (Display the string "PANIC") +- 0x07 - DisplayLotusVertical (Display the string "LOTUS") + +Pattern 0x00 is special. It needs another parameter to specify the percentage. +It will fill a percentage of the screen. It can serve as a progress indicator. + +#### DrawBW (0x06) +TODO + +#### StageCol (0x07) +TODO + +#### FlushCols (0x08) +TODO + +#### SetPxCol (0x16) +TODO + +#### FlushFB (0x17) +TODO + +#### Version (0x20) + +Response: + +``` +Byte 0: USB bcdDevice MSB +Byte 1: USB bcdDevice LSB +Byte 2: 1 if pre-release version, 0 otherwise +``` From bb9c4ae7592c15de3b4b3e0997c947b0529020cc Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 12:17:20 +0800 Subject: [PATCH 04/13] Allow listing modules without specifying type Example: ```sh > inputmodule-control --list /dev/ttyACM0 VID 0x32AC PID 0x0021 SN FRAKDEAM0000000000 Product Lotus_B1_Display ``` Signed-off-by: Daniel Schaefer --- inputmodule-control/src/inputmodule.rs | 56 ++++++++++++++++++-------- inputmodule-control/src/main.rs | 7 +++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 3417f2c6..157d0344 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -4,7 +4,7 @@ use std::time::Duration; use chrono::Local; use image::{io::Reader as ImageReader, Luma}; use rand::prelude::*; -use serialport::{SerialPort, SerialPortInfo}; +use serialport::{SerialPort, SerialPortInfo, SerialPortType}; use crate::c1minimal::Color; use crate::font::{convert_font, convert_symbol}; @@ -55,7 +55,7 @@ const HEIGHT: usize = 34; const SERIAL_TIMEOUT: Duration = Duration::from_millis(20); -fn find_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec { +fn match_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec { if let Some(requested) = requested { for p in ports { if requested == &p.port_name { @@ -65,9 +65,9 @@ fn find_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec< vec![] } else { let mut compatible_devs = vec![]; - // If nothing requested, fall back to a generic one or the first supported Framework USB device + // Find all supported Framework devices for p in ports { - if let serialport::SerialPortType::UsbPort(usbinfo) = &p.port_type { + if let SerialPortType::UsbPort(usbinfo) = &p.port_type { if usbinfo.vid == FRAMEWORK_VID && [LED_MATRIX_PID, B1_LCD_PID].contains(&usbinfo.pid) { @@ -79,45 +79,67 @@ fn find_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec< } } -/// Commands that interact with serial devices -pub fn serial_commands(args: &crate::ClapCli) { +pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> Vec { let mut serialdevs: Vec; loop { let ports = serialport::available_ports().expect("No ports found!"); if args.list || args.verbose { for p in &ports { - // TODO: Print prettier - //println!("{}", p.port_name); - println!("{p:?}"); + match &p.port_type { + SerialPortType::UsbPort(usbinfo) => { + println!("{}", p.port_name); + println!(" VID {:#06X}", usbinfo.vid); + println!(" PID {:#06X}", usbinfo.pid); + if let Some(sn) = &usbinfo.serial_number { + println!(" SN {}", sn); + } + if let Some(product) = &usbinfo.product { + // TODO: Seems to replace the spaces with underscore, not sure why + println!(" Product {}", product); + } + } + _ => { + //println!("{}", p.port_name); + //println!(" Unknown (PCI Port)"); + } + } } } serialdevs = match &args.command { + // TODO: Must be the correct device type Some(crate::Commands::LedMatrix(ledmatrix_args)) => { - find_serialdevs(&ports, &ledmatrix_args.serial_dev) + match_serialdevs(&ports, &ledmatrix_args.serial_dev) } Some(crate::Commands::B1Display(ledmatrix_args)) => { - find_serialdevs(&ports, &ledmatrix_args.serial_dev) + match_serialdevs(&ports, &ledmatrix_args.serial_dev) } Some(crate::Commands::C1Minimal(c1minimal_args)) => { - find_serialdevs(&ports, &c1minimal_args.serial_dev) + match_serialdevs(&ports, &c1minimal_args.serial_dev) } None => vec![], }; if serialdevs.is_empty() { - if args.wait_for_device { + if wait_for_device { // Try again after short wait thread::sleep(Duration::from_millis(100)); continue; } else { - println!( - "Failed to find serial devivce. Please manually specify with --serial-dev" - ); - return; + return Vec::new(); } } else { break; } } + serialdevs +} + +/// Commands that interact with serial devices +pub fn serial_commands(args: &crate::ClapCli) { + let serialdevs: Vec = find_serialdevs(args, args.wait_for_device); + if serialdevs.is_empty() { + println!("Failed to find serial devivce. Please manually specify with --serial-dev"); + return; + } match &args.command { // TODO: Handle generic commands without code deduplication diff --git a/inputmodule-control/src/main.rs b/inputmodule-control/src/main.rs index 5448f23a..b0a256a8 100644 --- a/inputmodule-control/src/main.rs +++ b/inputmodule-control/src/main.rs @@ -6,6 +6,7 @@ mod inputmodule; mod ledmatrix; use clap::{Parser, Subcommand}; +use inputmodule::find_serialdevs; use crate::b1display::B1DisplaySubcommand; use crate::c1minimal::C1MinimalSubcommand; @@ -55,6 +56,10 @@ fn main() { Some(Commands::B1Display(_)) => serial_commands(&args), Some(Commands::LedMatrix(_)) => serial_commands(&args), Some(Commands::C1Minimal(_)) => serial_commands(&args), - None => panic!("Not allowed"), + None => { + if args.list { + find_serialdevs(&args, false); + } + } } } From bf1e9dc499708ddd07321ceb0930a8e88c916a34 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 16:53:02 +0800 Subject: [PATCH 05/13] Allow getting more B1 state Signed-off-by: Daniel Schaefer --- b1display/src/main.rs | 2 ++ inputmodule-control/src/b1display.rs | 5 ++-- inputmodule-control/src/inputmodule.rs | 40 +++++++++++++++++++++++--- lotus-inputmodules/src/control.rs | 28 ++++++++++++++++-- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/b1display/src/main.rs b/b1display/src/main.rs index 1086f865..e3d02808 100644 --- a/b1display/src/main.rs +++ b/b1display/src/main.rs @@ -182,6 +182,8 @@ fn main() -> ! { let mut state = B1DIsplayState { sleeping: SimpleSleepState::Awake, + screen_inverted: false, + screen_on: true, }; let mut said_hello = false; diff --git a/inputmodule-control/src/b1display.rs b/inputmodule-control/src/b1display.rs index 6b641f3c..9ad5a056 100644 --- a/inputmodule-control/src/b1display.rs +++ b/inputmodule-control/src/b1display.rs @@ -27,12 +27,11 @@ pub struct B1DisplaySubcommand { /// Turn display on/off // TODO: Allow getting current state #[arg(long)] - pub display_on: Option, + pub display_on: Option>, /// Invert screen on/off - // TODO: Allow getting current state #[arg(long)] - pub invert_screen: Option, + pub invert_screen: Option>, /// Display black&white image (300x400px) #[arg(long)] diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 157d0344..08292ae7 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -682,12 +682,44 @@ fn show_symbols(serialdev: &str, symbols: &Vec) { show_font(serialdev, &font_items); } -fn display_on_cmd(serialdev: &str, display_on: bool) { - simple_cmd(serialdev, Command::DisplayOn, &[display_on as u8]); +fn display_on_cmd(serialdev: &str, arg: Option) { + let mut port = serialport::new(serialdev, 115_200) + .timeout(SERIAL_TIMEOUT) + .open() + .expect("Failed to open port"); + + if let Some(display_on) = arg { + simple_cmd_port(&mut port, Command::DisplayOn, &[display_on as u8]); + } else { + simple_cmd_port(&mut port, Command::DisplayOn, &[]); + + let mut response: Vec = vec![0; 32]; + port.read_exact(response.as_mut_slice()) + .expect("Found no data!"); + + let on = response[0] == 1; + println!("Currently on: {on}"); + } } -fn invert_screen_cmd(serialdev: &str, invert_on: bool) { - simple_cmd(serialdev, Command::InvertScreen, &[invert_on as u8]); +fn invert_screen_cmd(serialdev: &str, arg: Option) { + let mut port = serialport::new(serialdev, 115_200) + .timeout(SERIAL_TIMEOUT) + .open() + .expect("Failed to open port"); + + if let Some(invert_on) = arg { + simple_cmd_port(&mut port, Command::InvertScreen, &[invert_on as u8]); + } else { + simple_cmd_port(&mut port, Command::InvertScreen, &[]); + + let mut response: Vec = vec![0; 32]; + port.read_exact(response.as_mut_slice()) + .expect("Found no data!"); + + let inverted = response[0] == 1; + println!("Currently inverted: {inverted}"); + } } fn set_color_cmd(serialdev: &str, color: Color) { diff --git a/lotus-inputmodules/src/control.rs b/lotus-inputmodules/src/control.rs index 560cd8f1..55887dda 100644 --- a/lotus-inputmodules/src/control.rs +++ b/lotus-inputmodules/src/control.rs @@ -144,7 +144,9 @@ pub enum Command { #[cfg(feature = "c1minimal")] SetColor(RGB8), DisplayOn(bool), + GetDisplayOn, InvertScreen(bool), + GetInvertScreen, SetPixelColumn(usize, [u8; 50]), FlushFramebuffer, _Unknown, @@ -167,6 +169,8 @@ pub struct C1MinimalState { #[cfg(feature = "b1display")] pub struct B1DIsplayState { pub sleeping: SimpleSleepState, + pub screen_inverted: bool, + pub screen_on: bool, } pub fn parse_command(count: usize, buf: &[u8]) -> Option { @@ -324,8 +328,16 @@ pub fn parse_module_command(count: usize, buf: &[u8]) -> Option { None } } - Some(CommandVals::DisplayOn) => Some(Command::DisplayOn(arg == Some(1))), - Some(CommandVals::InvertScreen) => Some(Command::InvertScreen(arg == Some(1))), + Some(CommandVals::DisplayOn) => Some(if let Some(on) = arg { + Command::DisplayOn(on == 1) + } else { + Command::GetDisplayOn + }), + Some(CommandVals::InvertScreen) => Some(if let Some(invert) = arg { + Command::InvertScreen(invert == 1) + } else { + Command::GetInvertScreen + }), Some(CommandVals::SetPixelColumn) => { // 3B for magic and command // 2B for column (u16) @@ -519,10 +531,17 @@ where None } Command::DisplayOn(on) => { + state.screen_on = *on; disp.on_off(*on).unwrap(); None } + Command::GetDisplayOn => { + let mut response: [u8; 32] = [0; 32]; + response[0] = state.screen_on as u8; + Some(response) + } Command::InvertScreen(invert) => { + state.screen_inverted = *invert; if *invert { disp.write_command(Instruction::INVON, &[]).unwrap(); } else { @@ -530,6 +549,11 @@ where } None } + Command::GetInvertScreen => { + let mut response: [u8; 32] = [0; 32]; + response[0] = state.screen_inverted as u8; + Some(response) + } Command::SetPixelColumn(column, pixel_bytes) => { let mut pixels: [bool; 400] = [false; 400]; for (i, byte) in pixel_bytes.iter().enumerate() { From f1973e4c281d177b06441969a013693140c66ff4 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 17:37:45 +0800 Subject: [PATCH 06/13] Print debug msg AFTER returning message response Signed-off-by: Daniel Schaefer --- b1display/src/main.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/b1display/src/main.rs b/b1display/src/main.rs index e3d02808..a0343a34 100644 --- a/b1display/src/main.rs +++ b/b1display/src/main.rs @@ -242,21 +242,22 @@ fn main() -> ! { }; } (Some(command), SimpleSleepState::Awake) => { + // While sleeping no command is handled, except waking up + if let Some(response) = + handle_command(&command, &mut state, logo_rect, &mut disp) + { + let _ = serial.write(&response); + }; + // Must write AFTER writing response, otherwise the + // client interprets this debug message as the response let mut text: String<64> = String::new(); write!( &mut text, - "Handling command {}:{}:{}:{}\r\n", + "Handled command {}:{}:{}:{}\r\n", buf[0], buf[1], buf[2], buf[3] ) .unwrap(); let _ = serial.write(text.as_bytes()); - - // While sleeping no command is handled, except waking up - if let Some(response) = - handle_command(&command, &mut state, logo_rect, &mut disp) - { - let _ = serial.write(&response); - }; } _ => {} } From 3d3621b0e45042dcda55f3bbeda8b8c258427933 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 17:56:24 +0800 Subject: [PATCH 07/13] B1Display: Display all black or white Signed-off-by: Daniel Schaefer --- inputmodule-control/src/b1display.rs | 11 +++++++++++ inputmodule-control/src/inputmodule.rs | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/inputmodule-control/src/b1display.rs b/inputmodule-control/src/b1display.rs index 9ad5a056..df2a5fee 100644 --- a/inputmodule-control/src/b1display.rs +++ b/inputmodule-control/src/b1display.rs @@ -1,5 +1,11 @@ use clap::Parser; +#[derive(Copy, Clone, Debug, PartialEq, clap::ValueEnum)] +pub enum B1Pattern { + White, + Black, +} + /// B1 Display #[derive(Parser, Debug)] #[command(arg_required_else_help = true)] @@ -29,6 +35,11 @@ pub struct B1DisplaySubcommand { #[arg(long)] pub display_on: Option>, + /// Display a simple pattern + #[arg(long)] + #[clap(value_enum)] + pub pattern: Option, + /// Invert screen on/off #[arg(long)] pub invert_screen: Option>, diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 08292ae7..67a8c890 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -6,6 +6,7 @@ use image::{io::Reader as ImageReader, Luma}; use rand::prelude::*; use serialport::{SerialPort, SerialPortInfo, SerialPortType}; +use crate::b1display::B1Pattern; use crate::c1minimal::Color; use crate::font::{convert_font, convert_symbol}; use crate::ledmatrix::{Game, GameOfLifeStartParam, Pattern}; @@ -252,6 +253,9 @@ pub fn serial_commands(args: &crate::ClapCli) { if let Some(image_path) = &b1display_args.image_bw { b1display_bw_image_cmd(serialdev, image_path); } + if let Some(pattern) = b1display_args.pattern { + b1_display_pattern(serialdev, pattern); + } } } Some(crate::Commands::C1Minimal(c1minimal_args)) => { @@ -781,3 +785,23 @@ fn b1display_bw_image_cmd(serialdev: &str, image_path: &str) { simple_open_cmd(&mut serialport, Command::FlushFramebuffer, &[]); } + +fn b1_display_color(serialdev: &str, black: bool) { + let mut serialport = open_serialport(serialdev); + for x in 0..300 { + let byte = if black { 0xFF } else { 0x00 }; + let mut vals: [u8; 2 + 50] = [byte; 2 + 50]; + let column = (x as u16).to_le_bytes(); + vals[0] = column[0]; + vals[1] = column[1]; + simple_open_cmd(&mut serialport, Command::SetPixelColumn, &vals); + } + simple_open_cmd(&mut serialport, Command::FlushFramebuffer, &[]); +} + +fn b1_display_pattern(serialdev: &str, pattern: B1Pattern) { + match pattern { + B1Pattern::Black => b1_display_color(serialdev, true), + B1Pattern::White => b1_display_color(serialdev, false), + } +} From f5aede9a4dee359380925c3115f2ccaac00ac449 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 19:12:51 +0800 Subject: [PATCH 08/13] Extract --serial-dev option out of module specific Signed-off-by: Daniel Schaefer --- inputmodule-control/src/b1display.rs | 4 ---- inputmodule-control/src/c1minimal.rs | 4 ---- inputmodule-control/src/inputmodule.rs | 14 +------------- inputmodule-control/src/ledmatrix.rs | 4 ---- inputmodule-control/src/main.rs | 12 +++--------- 5 files changed, 4 insertions(+), 34 deletions(-) diff --git a/inputmodule-control/src/b1display.rs b/inputmodule-control/src/b1display.rs index df2a5fee..f5293dea 100644 --- a/inputmodule-control/src/b1display.rs +++ b/inputmodule-control/src/b1display.rs @@ -22,10 +22,6 @@ pub struct B1DisplaySubcommand { #[arg(long)] pub panic: bool, - /// Serial device, like /dev/ttyACM0 or COM0 - #[arg(long)] - pub serial_dev: Option, - /// Get the device version #[arg(short, long)] pub version: bool, diff --git a/inputmodule-control/src/c1minimal.rs b/inputmodule-control/src/c1minimal.rs index b71e29e6..a050bb37 100644 --- a/inputmodule-control/src/c1minimal.rs +++ b/inputmodule-control/src/c1minimal.rs @@ -27,10 +27,6 @@ pub struct C1MinimalSubcommand { #[arg(long)] pub panic: bool, - /// Serial device, like /dev/ttyACM0 or COM0 - #[arg(long)] - pub serial_dev: Option, - /// Get the device version #[arg(short, long)] pub version: bool, diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 67a8c890..e68ac4dc 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -106,19 +106,7 @@ pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> Vec { - match_serialdevs(&ports, &ledmatrix_args.serial_dev) - } - Some(crate::Commands::B1Display(ledmatrix_args)) => { - match_serialdevs(&ports, &ledmatrix_args.serial_dev) - } - Some(crate::Commands::C1Minimal(c1minimal_args)) => { - match_serialdevs(&ports, &c1minimal_args.serial_dev) - } - None => vec![], - }; + serialdevs = match_serialdevs(&ports, &args.serial_dev); if serialdevs.is_empty() { if wait_for_device { // Try again after short wait diff --git a/inputmodule-control/src/ledmatrix.rs b/inputmodule-control/src/ledmatrix.rs index 08f08b82..54b6c54d 100644 --- a/inputmodule-control/src/ledmatrix.rs +++ b/inputmodule-control/src/ledmatrix.rs @@ -121,10 +121,6 @@ pub struct LedMatrixSubcommand { #[arg(long)] pub panic: bool, - /// Serial device, like /dev/ttyACM0 or COM0 - #[arg(long)] - pub serial_dev: Option, - /// Get the device version #[arg(short, long)] pub version: bool, diff --git a/inputmodule-control/src/main.rs b/inputmodule-control/src/main.rs index b0a256a8..64e93809 100644 --- a/inputmodule-control/src/main.rs +++ b/inputmodule-control/src/main.rs @@ -35,13 +35,9 @@ pub struct ClapCli { #[arg(short, long)] verbose: bool, - /// VID (Vendor ID) in hex digits + /// Serial device, like /dev/ttyACM0 or COM0 #[arg(long)] - vid: Option, - - /// PID (Product ID) in hex digits - #[arg(long)] - pid: Option, + pub serial_dev: Option, /// Retry connecting to the device until it works #[arg(long)] @@ -53,9 +49,7 @@ fn main() { let args = ClapCli::parse_from(args); match args.command { - Some(Commands::B1Display(_)) => serial_commands(&args), - Some(Commands::LedMatrix(_)) => serial_commands(&args), - Some(Commands::C1Minimal(_)) => serial_commands(&args), + Some(_) => serial_commands(&args), None => { if args.list { find_serialdevs(&args, false); From e2e491e811f74b853eb99063bd3a1933e4e90e7c Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 19:14:39 +0800 Subject: [PATCH 09/13] Ignore single_match, I dont' care about this one Usually I do this to match more in the future. Signed-off-by: Daniel Schaefer --- inputmodule-control/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/inputmodule-control/src/main.rs b/inputmodule-control/src/main.rs index 64e93809..25e3d6e8 100644 --- a/inputmodule-control/src/main.rs +++ b/inputmodule-control/src/main.rs @@ -1,4 +1,5 @@ #![allow(clippy::needless_range_loop)] +#![allow(clippy::single_match)] mod b1display; mod c1minimal; mod font; From a2fe964567f52738e35f739911c323ef47d824b8 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 22:15:23 +0800 Subject: [PATCH 10/13] Send command before device is connected By default the app tries to connect with the device and aborts if it can't connect. But you might want to start the app, have it wait until the device is connected and then send the command. ``` > inputmodule-control b1-display --pattern black Failed to find serial devivce. Please manually specify with --serial-dev > inputmodule-control --wait-for-device b1-display --pattern black > inputmodule-control --wait-for-device b1-display --pattern black Device already present. No need to wait. Not executing command. ``` Signed-off-by: Daniel Schaefer --- inputmodule-control/src/inputmodule.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index e68ac4dc..74631fac 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -80,8 +80,9 @@ fn match_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec } } -pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> Vec { +pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec, bool) { let mut serialdevs: Vec; + let mut waited = false; loop { let ports = serialport::available_ports().expect("No ports found!"); if args.list || args.verbose { @@ -109,25 +110,33 @@ pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> Vec = find_serialdevs(args, args.wait_for_device); + let (serialdevs, waited): (Vec, bool) = find_serialdevs(args, args.wait_for_device); if serialdevs.is_empty() { println!("Failed to find serial devivce. Please manually specify with --serial-dev"); return; + } else if args.wait_for_device && !waited { + println!("Device already present. No need to wait. Not executing command. Sleep 1s"); + thread::sleep(Duration::from_millis(1000)); + return; } match &args.command { From 99e842a30dbb06cc309ad33b4f7b6cc5b9844f42 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 22:16:00 +0800 Subject: [PATCH 11/13] Match corresponding module type If running with b1-display parameter, only match B1 displays.Match corresponding module type. --- inputmodule-control/src/inputmodule.rs | 28 ++++++++++++++++++-------- inputmodule-control/src/main.rs | 12 ++++++++++- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/inputmodule-control/src/inputmodule.rs b/inputmodule-control/src/inputmodule.rs index 74631fac..723d1967 100644 --- a/inputmodule-control/src/inputmodule.rs +++ b/inputmodule-control/src/inputmodule.rs @@ -12,9 +12,9 @@ use crate::font::{convert_font, convert_symbol}; use crate::ledmatrix::{Game, GameOfLifeStartParam, Pattern}; const FWK_MAGIC: &[u8] = &[0x32, 0xAC]; -const FRAMEWORK_VID: u16 = 0x32AC; -const LED_MATRIX_PID: u16 = 0x0020; -const B1_LCD_PID: u16 = 0x0021; +pub const FRAMEWORK_VID: u16 = 0x32AC; +pub const LED_MATRIX_PID: u16 = 0x0020; +pub const B1_LCD_PID: u16 = 0x0021; // TODO: Use a shared enum with the firmware code #[derive(Clone, Copy)] @@ -56,7 +56,11 @@ const HEIGHT: usize = 34; const SERIAL_TIMEOUT: Duration = Duration::from_millis(20); -fn match_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec { +fn match_serialdevs( + ports: &[SerialPortInfo], + requested: &Option, + pid: Option, +) -> Vec { if let Some(requested) = requested { for p in ports { if requested == &p.port_name { @@ -66,12 +70,16 @@ fn match_serialdevs(ports: &[SerialPortInfo], requested: &Option) -> Vec vec![] } else { let mut compatible_devs = vec![]; + let pids = if let Some(pid) = pid { + vec![pid] + } else { + // By default accept any type + vec![LED_MATRIX_PID, B1_LCD_PID, 0x22, 0xFF] + }; // Find all supported Framework devices for p in ports { if let SerialPortType::UsbPort(usbinfo) = &p.port_type { - if usbinfo.vid == FRAMEWORK_VID - && [LED_MATRIX_PID, B1_LCD_PID].contains(&usbinfo.pid) - { + if usbinfo.vid == FRAMEWORK_VID && pids.contains(&usbinfo.pid) { compatible_devs.push(p.port_name.clone()); } } @@ -107,7 +115,11 @@ pub fn find_serialdevs(args: &crate::ClapCli, wait_for_device: bool) -> (Vec u16 { + match self { + Self::LedMatrix(_) => LED_MATRIX_PID, + Self::B1Display(_) => B1_LCD_PID, + Self::C1Minimal(_) => 0x22, + } + } +} + /// RAW HID and VIA commandline for QMK devices #[derive(Parser, Debug)] #[command(version, arg_required_else_help = true)] From d2e22003ed3f2cfc5aee1c5bf21aa3f3de82873a Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 23:05:27 +0800 Subject: [PATCH 12/13] README! Signed-off-by: Daniel Schaefer --- README.md | 214 ++++++++++++++++++++------------------------ b1display/README.md | 1 + c1minimal/README.md | 18 ++++ ledmatrix/README.md | 171 +++++++++++++++++++++++++++++++++++ python.md | 58 ++++++++++++ 5 files changed, 347 insertions(+), 115 deletions(-) create mode 100644 b1display/README.md create mode 100644 c1minimal/README.md create mode 100644 ledmatrix/README.md create mode 100644 python.md diff --git a/README.md b/README.md index b7ecce7c..228e5f07 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,122 @@ # Lotus Input Module Firmware -See below sections for LED Matrix, LCD Display and C1 Minimal module details. +This repository contains both the firmware for the Lotus input modules, as well +as the tool to control them. -Rust project setup based off of: https://github.com/rp-rs/rp2040-project-template +Rust firmware project setup based off of: https://github.com/rp-rs/rp2040-project-template -## Features +## Modules -- Reset into bootloader when firmware crashes/panics +See pages of the individual modules for details about how they work and how +they're controlled. + +- [LED Matrix](ledmatrix/README.md) +- [2nd Display](b1display/README.md) +- [Minimal C1 Input Module](c1minimal/README.md) + +## Generic Features + +All modules are built with an RP2040 microcontroller +Features that all modules share + +- Firmware written in bare-metal Rust +- Reset into RP2040 bootloader when firmware crashes/panics +- Sleep Mode to save power - API over USB ACM Serial Port - Requires no Drivers on Windows and Linux - - Display various pre-programmed patterns - - Light up a percentage of the screen - - Change brightness - - Send a black/white image to the display - - Send a greyscale image to the display - Go to sleep - Reset into bootloader - - Scroll and loop the display content vertically - - A commandline script and graphical application to control it -- Sleep Mode - - Transition slowly turns off/on the LEDs - - Current hardware does not have the SLEEP# GPIO connected, can't sleep automatically + - Control and read module state (brightness, displayed image, ...) -Future features: +## Control from the host -- API - - Send a greyscale image to display - - Read current system state (brightness, sleeping, ...) +To build your own application see the: [API command documentation](commands.md) -## Control from the host +Or use our `inputmodule-control` app. Optionally there are is also a +[Python script](python.md). -See: [API command documentation](commands.md) +For device specific commands, see their individual documentation pages. -Requirements: Python, [PySimpleGUI](https://www.pysimplegui.org) and optionally [pillow](https://pillow.readthedocs.io/en/stable/index.html) +Common commands: -Use `control.py`. Either the commandline, see `control.py --help` or the graphical version: `control.py --gui` +###### Listing available devices +```sh +> inputmodule-control --list +/dev/ttyACM0 + VID 0x32AC + PID 0x0020 + SN FRAKDEAM0020110001 + Product Lotus_LED_Matrix +/dev/ttyACM1 + VID 0x32AC + PID 0x0021 + SN FRAKDEAM0000000000 + Product Lotus_B1_Display ``` -options: - -h, --help show this help message and exit - --bootloader Jump to the bootloader to flash new firmware - --sleep, --no-sleep Simulate the host going to sleep or waking up - --brightness BRIGHTNESS - Adjust the brightness. Value 0-255 - --animate, --no-animate - Start/stop vertical scrolling - --pattern {full,lotus,gradient,double-gradient,zigzag,panic,lotus2} - Display a pattern - --image IMAGE Display a PNG or GIF image in black and white only) - --image-grey IMAGE_GREY - Display a PNG or GIF image in greyscale - --percentage PERCENTAGE - Fill a percentage of the screen - --clock Display the current time - --string STRING Display a string or number, like FPS - --symbols SYMBOLS [SYMBOLS ...] - Show symbols (degF, degC, :), snow, cloud, ...) - --gui Launch the graphical version of the program - --blink Blink the current pattern - --breathing Breathing of the current pattern - --eq EQ [EQ ...] Equalizer - --random-eq Random Equalizer - --wpm WPM Demo - --snake Snake - --all-brightnesses Show every pixel in a different brightness - --set-color {white,black,red,green,blue,cyan,yellow,purple} - Set RGB color (C1 Minimal Input Module) - --get-color Get RGB color (C1 Minimal Input Module) - -v, --version Get device version - --serial-dev SERIAL_DEV - Change the serial dev. Probably /dev/ttyACM0 on Linux, COM0 on Windows + +###### Apply command to single device + +By default a command will be sent to all devices that can be found, to apply it +to a single device, specify the COM port. +In this example the command is targeted at `b1-display`, so it will only apply +to this module type. + ``` +# Example on Linux +> inputmodule-control --serial-dev /dev/ttyACM0 b1-display --pattern black -Examples +# Example on Windows +> inputmodule-control.exe --serial-dev COM5 b1-display --pattern black +``` -```sh -# Launch graphical application -./control.py --gui +###### Send command when device connects -# Show current time and keep updating it -./control.py --clock +By default the app tries to connect with the device and aborts if it can't +connect. But you might want to start the app, have it wait until the device is +connected and then send the command. -# Draw PNG or GIF -./control.py --image stripe.gif -./control.py --image stripe.png +``` +> inputmodule-control b1-display --pattern black +Failed to find serial devivce. Please manually specify with --serial-dev + +# No failure, waits until the device is connected, sends command and exits +> inputmodule-control --wait-for-device b1-display --pattern black -# Change brightness (0-255) -./control.py --brightness 50 +# If the device is already connected, it does nothing, just wait 1s. +# This means you can run this command by a system service and restart it when +# it finishes. Then it will only ever do anything if the device reconnects. +> inputmodule-control --wait-for-device b1-display --pattern black +Device already present. No need to wait. Not executing command. ``` -## Control via Rust binary +## Update the Firmware -Currently have to specify the build target because it's not possible to specify a per package build target. -Tracking issue: https://github.com/rust-lang/cargo/issues/9406 +First, put the module into bootloader mode. + +This can be done either by pressing the bootsel button while plugging it in or +by using one of the following commands: +```sh +inputmodule-control led-matrix --bootloader +inputmodule-control b1-display --bootloader +inputmodule-control c1-minimal --bootloader ``` -> cargo build --target x86_64-unknown-linux-gnu -p inputmodule-control -> cargo run --target x86_64-unknown-linux-gnu -p inputmodule-control + +Then the module will present itself in the same way as a USB thumb drive. +Copy the UF2 firmware file onto it and the device will flash and reset automatically. +Alternatively when building from source, run one of the following commands: + +```sh +cargo run -p ledmatrix +cargo run -p b1display +cargo run -p c1minimal ``` -## Building +## Building the firmware Dependencies: Rust -Prepare Rust toolchain: +Prepare Rust toolchain (once): ```sh rustup target install thumbv6m-none-eabi @@ -118,36 +132,33 @@ cargo build -p b1display cargo build -p c1minimal ``` -Generate UF2 file: +Generate the UF2 update file: ```sh elf2uf2-rs target/thumbv6m-none-eabi/debug/ledmatrix ledmatrix.uf2 elf2uf2-rs target/thumbv6m-none-eabi/debug/b1display b1dipslay.uf2 -elf2uf2-rs target/thumbv6m-none-eabi/debug/b1display c1minimal.uf2 +elf2uf2-rs target/thumbv6m-none-eabi/debug/c1minimal c1minimal.uf2 ``` -## Flashing +## Building the Application -First, put the module into bootloader mode, which will expose a filesystem +Dependencies: Rust, pkg-config, libudev -This can be done by pressing the bootsel button while plugging it in. +Currently have to specify the build target because it's not possible to specify a per package build target. +Tracking issue: https://github.com/rust-lang/cargo/issues/9406 -```sh -cargo run -p ledmatrix -cargo run -p b1display -cargo run -p c1minimal ``` - -Or by copying the above generated UF2 file to the partition mounted when the -module is in the bootloder. +> cargo build --target x86_64-unknown-linux-gnu -p inputmodule-control +> cargo run --target x86_64-unknown-linux-gnu -p inputmodule-control +``` ### Check the firmware version of the device -###### In-band using `control.py` +###### In-band using commandline ```sh -> ./control.py --version -Device version: 0.1.2 +> inputmodule-control b1-display --version +Device Version: 0.1.3 ``` ###### By looking at the USB descriptor @@ -171,30 +182,3 @@ Additionally the panic message is written to flash, which can be read as follows sudo picotool save -r 0x15000000 0x15004000 message.bin strings message.bin | head ``` - -## LED Matrix - -It's a 9x34 (306) LED matrix, controlled by RP2040 MCU and IS31FL3741A LED controller. - -Connection to the host system is via USB 2.0 and currently there is a USB Serial API to control it without reflashing. - -## B1 Display - -## C1 Minimal Input Module - -It's a very minimal input module. Many GPIO pins are exposed so that headers -can be soldered onto them. Additionally there are pads for a WS2812/Neopixel -compatible RGB LED. - -When booting up this LED is lit in green color. -Its color and brightness can be controlled via the commands: - -```sh -> ./control.py --brightness 255 -> ./control.py --get-brightness -Current brightness: 255 - -> ./control.py --set-color yellow -> ./control.py --get-color -Current color: RGB:(255, 255, 0) -``` diff --git a/b1display/README.md b/b1display/README.md new file mode 100644 index 00000000..5249d227 --- /dev/null +++ b/b1display/README.md @@ -0,0 +1 @@ +## B1 Display diff --git a/c1minimal/README.md b/c1minimal/README.md new file mode 100644 index 00000000..a4d6c92e --- /dev/null +++ b/c1minimal/README.md @@ -0,0 +1,18 @@ +## C1 Minimal Input Module + +It's a very minimal input module. Many GPIO pins are exposed so that headers +can be soldered onto them. Additionally there are pads for a WS2812/Neopixel +compatible RGB LED. + +When booting up this LED is lit in green color. +Its color and brightness can be controlled via the commands: + +```sh +> ./control.py --brightness 255 +> ./control.py --get-brightness +Current brightness: 255 + +> ./control.py --set-color yellow +> ./control.py --get-color +Current color: RGB:(255, 255, 0) +``` diff --git a/ledmatrix/README.md b/ledmatrix/README.md new file mode 100644 index 00000000..5d56293b --- /dev/null +++ b/ledmatrix/README.md @@ -0,0 +1,171 @@ +# LED Matrix + +It's a 9x34 (306) LED matrix, controlled by RP2040 MCU and IS31FL3741A LED controller. + +Connection to the host system is via USB 2.0 and currently there is a USB Serial API to control it without reflashing. + +- Commands + - Display various pre-programmed patterns + - Light up a percentage of the screen + - Change brightness + - Send a black/white image to the display + - Send a greyscale image to the display + - Scroll and loop the display content vertically + - A commandline script and graphical application to control it +- Sleep Mode + - Transition slowly turns off/on the LEDs + +## Controlling + +### Commandline + +``` +> inputmodule-control led-matrix +LED Matrix + +Usage: ipc led-matrix [OPTIONS] + +Options: + --brightness [] + Set LED max brightness percentage or get, if no value provided + --sleeping [] + Set sleep status or get, if no value provided [possible values: true, false] + --bootloader + Jump to the bootloader + --percentage + Display a percentage (0-100) + --animate [] + Start/stop animation [possible values: true, false] + --pattern + Display a pattern [possible values: percentage, gradient, double-gradient, lotus-sideways, zigzag, all-on, panic, lotus-top-down] + --all-brightnesses + Show every brightness, one per pixel + --blinking + Blink the current pattern once a second + --breathing + Breathing brightness of the current pattern + --image-bw + Display black&white image (9x34px) + --image-gray + Display grayscale image + --random-eq + Random EQ + --eq + EQ with custom values + --clock + Show the current time + --string + Display a string (max 5 chars) + --symbols [...] + Display a string (max 5 symbols) + --start-game + Start a game [possible values: snake, pong, tetris, game-of-life] + --game-param + Paramater for starting the game. Required for some games [possible values: current-matrix, pattern1, blinker, toad, beacon, glider] + --stop-game + Stop the currently running game + --panic + Crash the firmware (TESTING ONLY!) + -v, --version + Get the device version + -h, --help + Print help +``` + +### Non-trivial Examples + +Most commandline arguments should be self-explanatory. +If not, please open an issue. +Those that require an argument or setup have examples here: + +###### Percentage + +Light up a percentage of the module. From bottom to top. +This could be used to show volume level, progress of something, or similar. + +```sh +inputmodule-control led-matrix --percentage 30 +``` + +###### Display an Image + +Display an image (tested with PNG and GIF). It must be 9x34 pixels in size. It +doesn't have to be black/white or grayscale. The program will calculate the +brightness of each pixel. But if the brightness doesn't vary enough, it won't +look good. +Two example images are included in the repository. + +```sh +# Convert image to black/white and display +inputmodule-control led-matrix --image-bw stripe.gif + +# Convert image to grayscale and display +inputmodule-control led-matrix --image-gray grayscale.gif +``` + +###### Random equalizer +To show off the equalizer use-case, this command generates a +random but authentic looking equalizer pattern until the command is terminated. + +Alternatively you can provide 9 EQ values yourself. A script might capture +audio input and feed it into this command. + +```sh +inputmodule-control led-matrix --random-eq +inputmodule-control led-matrix --eq 1 2 3 4 5 4 3 2 1 +``` + +###### Custom string + +Display a custom string of up to 5 characters. +Currently only uppercase A-Z, 0-9 and some punctuation is implemented. + +```sh +inputmodule-control led-matrix --string "LOTUS" +``` + +The symbols parameter is much more powerful, it can also show extra symbols. +The full list of symbols is defined [here](https://github.com/FrameworkComputer/led_matrix_fw/blob/main/inputmodule-control/src/font.rs). + +```sh +# Show 0 °C, a snow icon and a smiley +inputmodule-control led-matrix --symbols 0 degC ' ' snow ':)' +``` + +###### Games + +While the game commands are implemented, the controls don't take easy keyboard +input. +Instead try out the [Python script](../python.md): + +```sh +# Snake +./control.py --snake + +# Pong (Seems broken at the moment) +./control.py --pong-embedded +``` + +###### Game of Life + +[Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) +needs a parameter to start. Choose either one of the preprogrammed starting patterns. +Or display whatever you like using the other commands and have the game start based on that. +Font patterns generally look pretty good and survive for a while or even stay alive forever. + +The game board wraps around the edges to make gliders possible that move continuously. + +```sh +# Start from the currently displayed pattern +inputmodule-control led-matrix --start-game game-of-life --game-param current-matrix + +# Show two gliders that move forever +inputmodule-control led-matrix --start-game game-of-life --game-param glider +``` + +If you want to display something else, either reset the module (unplugging) or +run the stop command. + +```sh +inputmodule-control led-amtrix --stop-game +``` diff --git a/python.md b/python.md new file mode 100644 index 00000000..b720d3b9 --- /dev/null +++ b/python.md @@ -0,0 +1,58 @@ +# Python script to control Lotus input modules + +Requirements: Python, [PySimpleGUI](https://www.pysimplegui.org) and optionally [pillow](https://pillow.readthedocs.io/en/stable/index.html) + +Use `control.py`. Either the commandline, see `control.py --help` or the graphical version: `control.py --gui` + +``` +options: + -h, --help show this help message and exit + --bootloader Jump to the bootloader to flash new firmware + --sleep, --no-sleep Simulate the host going to sleep or waking up + --brightness BRIGHTNESS + Adjust the brightness. Value 0-255 + --animate, --no-animate + Start/stop vertical scrolling + --pattern {full,lotus,gradient,double-gradient,zigzag,panic,lotus2} + Display a pattern + --image IMAGE Display a PNG or GIF image in black and white only) + --image-grey IMAGE_GREY + Display a PNG or GIF image in greyscale + --percentage PERCENTAGE + Fill a percentage of the screen + --clock Display the current time + --string STRING Display a string or number, like FPS + --symbols SYMBOLS [SYMBOLS ...] + Show symbols (degF, degC, :), snow, cloud, ...) + --gui Launch the graphical version of the program + --blink Blink the current pattern + --breathing Breathing of the current pattern + --eq EQ [EQ ...] Equalizer + --random-eq Random Equalizer + --wpm WPM Demo + --snake Snake + --all-brightnesses Show every pixel in a different brightness + --set-color {white,black,red,green,blue,cyan,yellow,purple} + Set RGB color (C1 Minimal Input Module) + --get-color Get RGB color (C1 Minimal Input Module) + -v, --version Get device version + --serial-dev SERIAL_DEV + Change the serial dev. Probably /dev/ttyACM0 on Linux, COM0 on Windows +``` + +Examples + +```sh +# Launch graphical application +./control.py --gui + +# Show current time and keep updating it +./control.py --clock + +# Draw PNG or GIF +./control.py --image stripe.gif +./control.py --image stripe.png + +# Change brightness (0-255) +./control.py --brightness 50 +``` From c623ede892cc424ca5753e83070be35ffa4e8107 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 10 Mar 2023 23:05:40 +0800 Subject: [PATCH 13/13] font: Fix degree symbols Circle is on the left... Signed-off-by: Daniel Schaefer --- inputmodule-control/src/font.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/inputmodule-control/src/font.rs b/inputmodule-control/src/font.rs index ab910306..fc06f30b 100644 --- a/inputmodule-control/src/font.rs +++ b/inputmodule-control/src/font.rs @@ -4,20 +4,20 @@ pub fn convert_symbol(symbol: &str) -> Vec { match symbol { "degC" => vec![ - 0, 0, 0, 1, 1, - 0, 0, 0, 1, 1, - 1, 1, 1, 0, 0, - 1, 0, 0, 0, 0, - 1, 0, 0, 0, 0, - 1, 1, 1, 0, 0, + 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 1, 1, ], "degF" => vec![ - 0, 0, 0, 1, 1, - 0, 0, 0, 1, 1, - 1, 1, 1, 0, 0, - 1, 0, 0, 0, 0, - 1, 1, 1, 0, 0, - 1, 0, 0, 0, 0, + 1, 1, 0, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 1, 1, + 0, 0, 1, 0, 0, ], "snow" => vec![ 0, 0, 0, 0, 0,