diff --git a/docs/src/content/docs/features/feature-backlight.md b/docs/src/content/docs/features/feature-backlight.md index 5bbbd9f..648a9f3 100644 --- a/docs/src/content/docs/features/feature-backlight.md +++ b/docs/src/content/docs/features/feature-backlight.md @@ -29,23 +29,41 @@ Some drivers may not be able to support all backlight types. ## Required code -To set up backlighting, you must add `(driver = "")` to your `#[keyboard]` macro invocation, -and your keyboard must implement the `BacklightDevice` trait. +To set up backlighting, you must create a new type to implement traits on. +Then, you can add `(id = , driver_setup_fn = )` to your `#[keyboard]` macro +invocation. Your new type must implement the appropriate traits depending on the type of lighting you're using: -```rust ins={5-7,11-16} +- `simple_backlight`: [`SimpleBacklightDevice`](/rumcake/api/nrf52840/rumcake/lighting/simple_backlight/trait.SimpleBacklightDevice.html) +- `simple_backlight_matrix`: [`SimpleBacklightMatrixDevice`](/rumcake/api/nrf52840/rumcake/lighting/simple_backlight_matrix/trait.SimpleBacklightMatrixDevice.html) +- `rgb_backlight_matrix`: [`RGBBacklightMatrixDevice`](/rumcake/api/nrf52840/rumcake/lighting/rgb_backlight_matrix/trait.RGBBacklightMatrixDevice.html) + +The `driver_setup_fn` must be an async function that has no parameters, and returns a type that implements the appropriate +driver trait: + +- `simple_backlight`: [`SimpleBacklightDriver`](/rumcake/api/nrf52840/rumcake/lighting/simple_backlight/trait.SimpleBacklightDriver.html) +- `simple_backlight_matrix`: [`SimpleBacklightMatrixDriver`](/rumcake/api/nrf52840/rumcake/lighting/simple_backlight_matrix/trait.SimpleBacklightMatrixDriver.html) +- `rgb_backlight_matrix`: [`RGBBacklightMatrixDriver`](/rumcake/api/nrf52840/rumcake/lighting/rgb_backlight_matrix/trait.RGBBacklightMatrixDriver.html) + +```rust ins={5-8,13-22} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... simple_backlight_matrix( // TODO: Change this to `rgb_backlight_matrix` or `simple_backlight` if that's what you want. - driver = "is31fl3731", // TODO: change this to your desired backlight driver, and implement the appropriate trait (info below) + id = MyKeyboardLighting, + driver_setup_fn = my_backlight_setup, ) )] struct MyKeyboard; // Backlight configuration -use rumcake::backlight::BacklightDevice; -impl BacklightDevice for MyKeyboard { +use rumcake::lighting::simple_backlight_matrix::{SimpleBacklightMatrixDevice, SimpleBacklightMatrixDriver}; +struct MyKeyboardLighting; // New type to implement lighting traits on +async fn my_backlight_setup() -> impl SimpleBacklightMatrixDriver { + // TODO: We will fill this out soon! + todo!() +} +impl SimpleBacklightMatrixDevice for MyKeyboardLighting { // optionally, set FPS const FPS: usize = 20; } @@ -57,13 +75,14 @@ hue, saturation, effect, etc.) will **NOT** be saved by default. Optionally, you can add `use_storage`, and a `storage` driver to save backlight config data. -```rust ins={7,9} +```rust ins={8,10} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... simple_backlight_matrix( // TODO: Change this to `rgb_backlight_matrix` or `simple_backlight` if that's what you want. - driver = "is31fl3731", // TODO: change this to your desired backlight driver, and implement the appropriate trait (info below) + id = MyKeyboardLighting, + driver_setup_fn = my_backlight_setup, use_storage // Optional, if you want to save backlight configuration ), storage(driver = "internal") // You need to specify a storage driver if you enabled `use_storage`. See feature-storage.md for more information. @@ -77,35 +96,41 @@ For more information, see the docs for the [storage feature](../feature-storage/ If you're implementing a backlight matrix (either the `simple-backlight-matrix` or `rgb-backlight-matrix`), your keyboard must also implement the `BacklightMatrixDevice` trait: -```rust ins={18-37} +```rust ins={14,25-42} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... simple_backlight_matrix( // TODO: Change this to `rgb_backlight_matrix` or `simple_backlight` if that's what you want. - driver = "is31fl3731", // TODO: change this to your desired backlight driver, and implement the appropriate trait (info below) + id = MyKeyboardLighting, + driver_setup_fn = my_backlight_setup, ) )] struct MyKeyboard; // Backlight configuration -use rumcake::backlight::BacklightDevice; -impl BacklightDevice for MyKeyboard { +use rumcake::lighting::simple_backlight_matrix::{SimpleBacklightMatrixDevice, SimpleBacklightMatrixDriver}; +use rumcake::lighting::{BacklightMatrixDevice, setup_backlight_matrix}; +struct MyKeyboardLighting; +async fn my_backlight_setup() -> impl SimpleBacklightMatrixDriver { + // TODO: We will fill this out soon! + todo!() +} +impl SimpleBacklightMatrixDevice for MyKeyboardLighting { // optionally, set FPS const FPS: usize = 20; } -use rumcake::backlight::{BacklightMatrixDevice, setup_backlight_matrix}; -impl BacklightMatrixDevice for MyKeyboard { +impl BacklightMatrixDevice for MyKeyboardLighting { setup_backlight_matrix! { - { // LED layout + led_layout: { [ (0,0) (17,0) (34,0) (51,0) (68,0) (85,0) (102,0) (119,0) (136,0) (153,0) (170,0) (187,0) (204,0) (221,0) (238,0) (255,0) ] [ (4,17) (26,17) (43,17) (60,17) (77,17) (94,17) (111,17) (128,17) (145,17) (162,17) (178,17) (196,17) (213,17) (234,17) (255,17) ] [ (6,34) (30,34) (47,34) (64,34) (81,34) (98,34) (115,34) (132,34) (149,34) (166,34) (183,34) (200,34) (227,34) (227,34) (255,34) ] [ (11,51) (0,0) (38,51) (55,51) (72,51) (89,51) (106,51) (123,51) (140,51) (157,51) (174,51) (191,51) (208,51) (231,51) (255,51) ] [ (28,68) (49,68) (79,68) (121,68) (155,68) (176,68) (196,68) (213,68) (230,68) ] - } - { // LED flags (must have same number of rows and columns as the layout above) + }, + led_flags: { // must have same number of rows and columns as the layout above [ NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE ] [ NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE ] [ NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE NONE ] @@ -123,33 +148,41 @@ Note that for reactive effects, matrix positions will map directly to LED positi a key at switch matrix position row 0, column 0, will correspond to the LED at row 0, column 0 on your LED matrix. ::: -Lastly, you must also implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. -Check the [list of available backlight drivers](#available-drivers) for this information. +Lastly, you must set up the driver. To do this, you need to complete your `driver_setup_fn` by constructing the driver. +You can [check the API reference for your chosen driver](/rumcake/api/nrf52840/rumcake/drivers/index.html) for a set up +function or macro to make this process easier. -For example, with `is31fl3731`, you must implement `IS31FL3731DriverSettings` and `IS31FL3731BacklightDriver`: +Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver. +Check the [list of available backlight drivers](#available-drivers) for this information. -```rust ins={3-30} -// later in your file... +For example, with `is31fl3731`, you must implement `IS31FL3731BacklightDriver`, and you can use the `setup_is31fl3731!` macro to set up the driver: -use rumcake::hw::mcu::setup_i2c; -use rumcake::drivers::is31fl3731::backlight::{ - get_led_from_matrix_coordinates, IS31FL3731BacklightDriver +```rust del={9-10} ins={3-5,11-23,25-34} +use rumcake::lighting::simple_backlight_matrix::{SimpleBacklightMatrixDevice, SimpleBacklightMatrixDriver}; +use rumcake::lighting::{BacklightMatrixDevice, setup_backlight_matrix}; +use rumcake::hw::platform::setup_i2c; +use rumcake::drivers::is31fl3731::{ + get_led_from_matrix_coordinates, setup_is31fl3731, IS31FL3731BacklightDriver }; -// Note: The IS31FL3731DriverSettings trait does NOT come from the `rumcake` library. It is generated by the `keyboard` macro. -impl IS31FL3731DriverSettings for MyKeyboard { - const LED_DRIVER_ADDR: u8 = 0b1110100; // see https://github.com/qmk/qmk_firmware/blob/d9fa80c0b0044bb951694aead215d72e4a51807c/docs/feature_rgb_matrix.md#is31fl3731-idis31fl3731 - - setup_i2c! { // Note: The arguments of setup_i2c may change depending on platform. This assumes STM32. - I2C1_EV, // Event interrupt - I2C1_ER, // Error interrupt - I2C1, // I2C peripheral - PB6, // SCL - PB7, // SDA - DMA1_CH7, // RX DMA Channel - DMA1_CH6 // TX DMA Channel +struct MyKeyboardLighting; +async fn my_backlight_setup() -> impl SimpleBacklightMatrixDriver { + // TODO: We will fill this out soon! + todo!() + setup_is31fl3731! { + device: MyKeyboardLighting, // Must be a type that implements IS31FL3731BacklightDriver + address: 0b1110100, // see https://github.com/qmk/qmk_firmware/blob/d9fa80c0b0044bb951694aead215d72e4a51807c/docs/feature_rgb_matrix.md#is31fl3731-idis31fl3731 + i2c: setup_i2c! { // Note: The arguments of setup_i2c may change depending on platform. This assumes STM32. + event_interrupt: I2C1_EV, + error_interrupt: I2C1_ER, + i2c: I2C1, + scl: PB6, + sda: PB7, + rx_dma: DMA1_CH7, + tx_dma: DMA1_CH6, + } } } -impl IS31FL3731BacklightDriver for MyKeyboard { +impl IS31FL3731BacklightDriver for MyKeyboardLighting { // This must have the same number of rows and columns as specified in your `BacklightMatrixDevice` implementation. get_led_from_matrix_coordinates! { [ C1_1 C1_2 C1_3 C1_4 C1_5 C1_6 C1_7 C1_8 C1_9 C1_10 C1_11 C1_12 C1_13 C1_14 C1_15 C2_15 ] @@ -159,6 +192,10 @@ impl IS31FL3731BacklightDriver for MyKeyboard { [ C5_2 C5_3 C5_6 C5_7 C5_10 C5_11 C5_12 C5_13 C5_14 ] } } + +impl SimpleBacklightMatrixDevice for MyKeyboardLighting { /* ... */ } +impl BacklightMatrixDevice for MyKeyboardLighting { /* ... */ } + ``` :::note @@ -202,32 +239,37 @@ In your `keyberon` layout, you can use `{Custom(SimpleBacklight())}`, `{Custom(SimpleBacklightMatrix())}`, `{Custom(RGBBacklightMatrix())}`, depending on what type of backlight system you are using. +Additionally, you must choose the backlight system that the keycodes will correspond to by +implementing one of the associated types `SimpleBacklightDeviceType`, `SimpleBacklightMatrixDeviceType`, +`RGBBacklightDeviceType`. + Example of usage: -```rust +```rust ins={14} use keyberon::action::Action::*; -use rumcake::backlight::animations::BacklightCommand::*; +use rumcake::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::*; use rumcake::keyboard::{build_layout, Keyboard, Keycode::*}; -/* ... */ +impl KeyboardLayout for MyKeyboard { + /* ... */ build_layout! { { [ Escape {Custom(SimpleBacklightMatrix(Toggle))} A B C] } } + + type SimpleBacklightMatrixDeviceType = MyKeyboardLighting; +} ``` # To-do List - [ ] RGB Backlight animations -- [ ] Allow different backlighting systems to be used at the same time # Available Drivers -| Name | Feature Flag | `keyboard` Macro Driver String | Required Traits | -| -------------- | ---------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| IS31FL3731 | `is31fl3731` | `"is31fl3731"` | `IS31FL3731DriverSettings`[^1], [`IS31FL3731BacklightDriver`](/rumcake/api/nrf52840/rumcake/drivers/is31fl3731/backlight/trait.IS31FL3731BacklightDriver.html) | -| WS2812 Bitbang | `ws2812_bitbang` | `"ws2812_bitbang"` | `WS2812BitbangDriverSettings`[^1], [`WS2812BitbangBacklightDriver`](/rumcake/api/nrf52840/rumcake/drivers/ws2812_bitbang/backlight/trait.WS2812BitbangBacklightDriver.html) | - -[^1]: This trait is generated by the `keyboard` macro, and not included in the `rumcake` API. +| Name | Feature Flag | Required Traits | +| -------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| IS31FL3731 | `is31fl3731` | [`IS31FL3731BacklightDriver`](/rumcake/api/nrf52840/rumcake/drivers/is31fl3731/backlight/trait.IS31FL3731BacklightDriver.html) | +| WS2812 Bitbang | `ws2812_bitbang` | [`WS2812BitbangBacklightDriver`](/rumcake/api/nrf52840/rumcake/drivers/ws2812_bitbang/backlight/trait.WS2812BitbangBacklightDriver.html) | diff --git a/docs/src/content/docs/features/feature-bluetooth-host.md b/docs/src/content/docs/features/feature-bluetooth-host.md index 4784cf2..f5bd2f0 100644 --- a/docs/src/content/docs/features/feature-bluetooth-host.md +++ b/docs/src/content/docs/features/feature-bluetooth-host.md @@ -45,7 +45,7 @@ use rumcake::keyboard; )] struct MyKeyboard; -use rumcake::hw::mcu::BluetoothDevice; +use rumcake::hw::platform::BluetoothDevice; impl BluetoothDevice for WingpairLeft { // This addresses can be whatever you want, as long as it is a valid "Random Static" bluetooth addresses. // See "Random Static Address" in this link: https://novelbits.io/bluetooth-address-privacy-ble/ @@ -71,14 +71,16 @@ Also check the sections below for more information. # Keycodes -In your keyberon layout, you can use any of the enum members defined in `BluetoothCommand`: +In your keyberon layout, you can use any of the enum members defined in `HardwareCommand`: ```rust -ToggleOutput // Only available if the `usb` feature flag is also enabled. More information below. -OutputUSB // Only available if the `usb` feature flag is also enabled. More information below. -OutputBluetooth // Only available if the `usb` feature flag is also enabled. More information below. +ToggleOutput +OutputUSB +OutputBluetooth ``` +More information below. + ## USB host communication interoperability By default, your keyboard will use Bluetooth to communicate with your device. diff --git a/docs/src/content/docs/features/feature-display.md b/docs/src/content/docs/features/feature-display.md index 424e5ac..3f59822 100644 --- a/docs/src/content/docs/features/feature-display.md +++ b/docs/src/content/docs/features/feature-display.md @@ -18,22 +18,29 @@ You must enable the following `rumcake` features: ## Required code -To set up your display, you must add `display(driver = "")` to your `#[keyboard]` macro invocation, +To set up your display, you must add `display(driver_setup_fn = )` to your `#[keyboard]` macro invocation, and your keyboard must implement the `DisplayDevice` trait. -```rust ins={5-7,11-17} +The `driver_setup_fn` must be an async function that has no parameters, and returns a type that implements the +[`DisplayDriver`](/rumcake/api/nrf52840/rumcake/display/drivers/index.html) trait. + +```rust ins={5-7,11-21} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... display( - driver = "ssd1306" // TODO: change this to your desired display driver, and implement the appropriate trait (info below) + driver_setup_fn = my_display_setup ) )] struct MyKeyboard; // Display configuration -use rumcake::display::DisplayDevice; +use rumcake::display::{DisplayDevice, DisplayDriver}; +async fn my_display_setup() -> impl DisplayDriver { + // TODO: We will fill this out soon! + todo!() +} impl DisplayDevice for MyKeyboard { // Optional: set timeout and FPS const FPS: usize = 0 // Only update the display when information changes. Change this if you are displaying animations. @@ -41,35 +48,38 @@ impl DisplayDevice for MyKeyboard { } ``` -Lastly, you must also implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. +Lastly, you must set up the driver. To do this, you need to complete your `driver_setup_fn` by constructing the driver. +You can [check the API reference for your chosen driver](/rumcake/api/nrf52840/rumcake/drivers/index.html) for a set up +function or macro to make this process easier. + +Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver. Check the [list of available display drivers](#available-drivers) for this information. -For example, with `ssd1306`, you must implement `Ssd1306I2cDriverSettings` and `Ssd1306I2cDisplayDriver`: - -```rust ins={3-23} -// later in your file... - -use rumcake::hw::mcu::setup_i2c_blocking; -use rumcake::drivers::ssd1306::driver::size::DisplaySize128x32; -use rumcake::drivers::ssd1306::display::Ssd1306I2cDisplayDriver; -// Note: The Ssd1306I2cDriverSettings trait does NOT come from the `rumcake` library. It is generated by the `keyboard` macro. -impl Ssd1306I2cDriverSettings for MyKeyboard { - // Set size of the display - type SIZE_TYPE = DisplaySize128x32; - const SIZE: Self::SIZE_TYPE = DisplaySize128x32; - - // Optional: set rotation - const ROTATION: DisplayRotation = DisplayRotation::Rotate90; - - // Set up the I2C peripheral to communicate with the SSD1306 screen - setup_i2c_blocking! { - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, - TWISPI0, - P0_17, - P0_20 +For example, with `ssd1306`, you must implement `Ssd1306I2cDisplayDriver`, and you can use the `setup_ssd1306!` macro to set up the driver: + +```rust del={4-5} ins={2,6-19,21} +use rumcake::display::{DisplayDevice, DisplayDriver}; +use rumcake::drivers::ssd1306::{setup_ssd1306, Ssd1306I2cDisplayDriver}; +async fn my_display_setup() -> impl DisplayDriver { + // TODO: We will fill this out soon! + todo!() + setup_ssd1306! { + i2c: setup_i2c! { // Note: The arguments of setup_i2c may change depending on platform. This assumes STM32. + event_interrupt: I2C1_EV, + error_interrupt: I2C1_ER, + i2c: I2C1, + scl: PB6, + sda: PB7, + rx_dma: DMA1_CH7, + tx_dma: DMA1_CH6, + }, + // See the API reference for the ssd1306 crate for `size` and `rotation` values: https://docs.rs/ssd1306/latest/ssd1306/ + size: DisplaySize96x16, + rotation: Rotate90, } } impl Ssd1306I2cDisplayDriver for MyKeyboard {} +impl DisplayDevice for MyKeyboard { /* ... */ } ``` # Custom graphics @@ -98,7 +108,7 @@ use rumcake::drivers::ssd1306::driver::mode::BufferedGraphicsMode; use rumcake::drivers::ssd1306::driver::prelude::I2CInterface; use rumcake::drivers::ssd1306::driver::size::{DisplaySize, DisplaySize128x32}; use rumcake::drivers::ssd1306::driver::Ssd1306; -use rumcake::drivers::ssd1306::display::Ssd1306I2cDisplayDriver; +use rumcake::drivers::ssd1306::Ssd1306I2cDisplayDriver; pub static DEFAULT_STYLE: MonoTextStyle<'_, BinaryColor> = MonoTextStyleBuilder::new() .font(&FONT_5X8) @@ -128,9 +138,8 @@ impl Ssd1306I2cDisplayDriver for MyKeyboard { # Available Drivers -| Name | Feature Flag | `keyboard` Macro Driver String | Required Traits | -| ----------- | ------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| SSD1306[^1] | `ssd1306` | `"ssd1306"` | `Ssd1306I2cDriverSettings`[^2], [`Ssd1306I2cDisplayDriver`](/rumcake/api/nrf52840/rumcake/drivers/ssd1306/display/trait.Ssd1306I2cDisplayDriver.html) | +| Name | Feature Flag | Required Traits | +| ----------- | ------------ | --------------------------------------------------------------------------------------------------------------------- | +| SSD1306[^1] | `ssd1306` | [`Ssd1306I2cDisplayDriver`](/rumcake/api/nrf52840/rumcake/drivers/ssd1306/display/trait.Ssd1306I2cDisplayDriver.html) | [^1]: I2C only -[^2]: This trait is generated by the `keyboard` macro, and not included in the `rumcake` API. diff --git a/docs/src/content/docs/features/feature-split.md b/docs/src/content/docs/features/feature-split.md index 285f277..e6b81a8 100644 --- a/docs/src/content/docs/features/feature-split.md +++ b/docs/src/content/docs/features/feature-split.md @@ -44,58 +44,40 @@ You must compile a binary with the following `rumcake` features: ## Required code for central device -To set up the central device, you must add `split_central(driver = "")` to your `#[keyboard]` macro invocation, -and your keyboard must implement the appropriate trait for the driver you're using. Check the [list of available split -keyboard drivers](#available-drivers) for this information. +To set up the central device, you must add `split_central(driver_setup_fn = )` to your `#[keyboard]` macro invocation, +and your keyboard must implement the `CentralDevice` trait. Your `CentralDevice` implementation should include `type Layout = Self;`. +This will tell rumcake to redirect matrix events (received from other peripherals) to the layout, to be processed as keycodes. -For example, with the `ble` and an nRF5x chip selected, you must implement `NRFBLECentralDriverSettings`, -and `BluetoothDevice`: +The `driver_setup_fn` must be an async function that has no parameters, and returns a type that implements the +[`CentralDeviceDriver`](/rumcake/api/nrf52840/rumcake/split/drivers/trait.CentralDeviceDriver.html) trait. -```rust ins={6-8,20-36} +```rust ins={6-8,17-24} // left.rs use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... split_central( - driver = "ble" // TODO: change this to your desired split driver, and implement the appropriate trait + driver_setup_fn = my_central_setup ) )] struct MyKeyboardLeftHalf; // KeyboardLayout should already be implemented use rumcake::keyboard::KeyboardLayout; -impl KeyboardLayout for MyKeyboard { - // ... -} - -// later in your file ... - -// Bluetooth device setup -use rumcake::hw::mcu::BluetoothDevice; -impl BluetoothDevice for MyKeyboardLeftHalf { - // This addresses can be whatever you want, as long as it is a valid "Random Static" bluetooth addresses. - // See "Random Static Address" in this link: https://novelbits.io/bluetooth-address-privacy-ble/ - const BLUETOOTH_ADDRESS: [u8; 6] = [0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]; // TODO: Change this to something else -} +impl KeyboardLayout for MyKeyboardLeftHalf { /* ... */ } // Split central setup -// Note: The NRFBLECentralDriverSettings trait does NOT come from the `rumcake` library. It is generated by the `keyboard` macro. -impl NRFBLECentralDriverSettings for MyKeyboardLeftHalf { - // Must be valid "Random Static" bluetooth addresses. - // This central device can connect to one other peripheral. Feel free to add more addresses to connect more peripherals. - const PERIPHERAL_ADDRESSES: &'static [[u8; 6]] = [ - [0x92, 0x32, 0x98, 0xC7, 0xF6, 0xF8], - ]; +use rumcake::split::central::{CentralDevice, CentralDeviceDriver}; +async fn my_central_setup() -> impl CentralDeviceDriver { + // TODO: We will fill this out soon! + todo!() +} +impl CentralDevice for MyKeyboardLeftHalf { + type Layout = Self; } ``` -:::note -In case you are using the `ble` driver, if your keyboard also communicates with your host device using Bluetooth -(basically if you followed the [Bluetooth doc](../feature-bluetooth-host/) or chose a template with Bluetooth), -then the `BluetoothDevice` trait should already be implemented for you. -::: - :::caution Make sure your central device communicates with a host device over [USB](../feature-usb-host/) or [Bluetooth](../feature-bluetooth-host/). Please follow those documents to implement @@ -105,6 +87,49 @@ Although it is possible to compile a central device without them, your keyboard be able to communicate with the host device that you want to use it with. ::: +Lastly, you must set up the driver. To do this, you need to complete your `driver_setup_fn` by constructing the driver. +You can [check the API reference for your chosen driver](/rumcake/api/nrf52840/rumcake/drivers/index.html) for a set up +function or macro to make this process easier. + +Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. +Check the [list of available split drivers](#available-drivers) for this information. + +For example, with the `SerialSplitDriver` struct, you can construct it like so: + +```rust del={11-12} ins={13-23} +// KeyboardLayout should already be implemented +use rumcake::keyboard::KeyboardLayout; +impl KeyboardLayout for MyKeyboardLeftHalf { /* ... */ } + +// Split central setup +use rumcake::split::central::{CentralDevice, CentralDeviceDriver}; +use rumcake::drivers::SerialSplitDriver; +use rumcake::hw::platform::setup_buffered_uarte; +async fn my_central_setup() -> impl CentralDeviceDriver { + // TODO: We will fill this out soon! + todo!() + SerialSplitDriver { + serial: setup_buffered_uarte! { // Note: this assumes nRF5x, other MCUs have their own macros with their own arguments. + interrupt: UARTE0_UART0, + uarte: UARTE0, + timer: TIMER1, + ppi_ch0: PPI_CH0, + ppi_ch1: PPI_CH1, + ppi_group: PPI_GROUP0, + rx_pin: P0_29, + tx_pin: P0_31, + }, + } +} +impl CentralDevice for MyKeyboardLeftHalf { + type Layout = Self; +} +``` + +:::note +If you would like to use nRF BLE as the driver for split keyboard communication, see the [nRF-BLE](#nrf-ble-driver) section for more instruction. +::: + # Peripheral setup The "peripheral" device in a split keyboard setup has a switch matrix, and sends matrix events to the central device. A split keyboard setup could have more than one peripheral. @@ -115,13 +140,17 @@ If the split keyboard also uses extra features, then all the peripherals should You must compile a binary with the following `rumcake` features: - `split-peripheral` -- `drivers` (optional built-in drivers to your peripheral device to a central device) +- Feature flag for one of the [available split drivers](#available-drivers) that you would like to use ## Required code for peripheral device -To set up the peripheral device, you must add `split_peripheral(driver = "")` to your `#[keyboard]` macro invocation, -and your keyboard must implement the appropriate trait for the driver you're using. For example, with `ble` and an nRF5x chip -selected, you must implement `NRFBLEPeripheralDriverSettings`, and `BluetoothDevice`: +To set up the peripheral device, you must add `split_peripheral(driver_setup_fn = )` to your `#[keyboard]` macro invocation, +and your keyboard must implement the `PeripheralDevice` trait. Your `KeyboardMatrix` implementation (which should already be implemented) +should include `type PeripheralDeviceType = Self`. This will tell rumcake to redirect matrix events to the peripheral device driver, to +be sent to the central device. + +The `driver_setup_fn` must be an async function that has no parameters, and returns a type that implements the +[`PeripheralDeviceDriver`](/rumcake/api/nrf52840/rumcake/split/drivers/trait.PeripheralDeviceDriver.html) trait. ```rust ins={6-8,12-24} // right.rs @@ -130,34 +159,69 @@ use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... split_peripheral( - driver = "ble" // TODO: change this to your desired split driver, and implement the appropriate trait below + driver_setup_fn = my_peripheral_setup ) )] struct MyKeyboardRightHalf; -// Bluetooth device setup -use rumcake::hw::mcu::BluetoothDevice; -impl BluetoothDevice for WingpairLeft { - // Must be valid "Random Static" bluetooth address. - const BLUETOOTH_ADDRESS: [u8; 6] = [0x92, 0x32, 0x98, 0xC7, 0xF6, 0xF8]; // TODO: Change this to something else +// KeyboardMatrix should already be implemented +use rumcake::keyboard::KeyboardMatrix; +impl KeyboardMatrix for MyKeyboardRightHalf { + type PeripheralDeviceType = Self; } // Split peripheral setup -// Note: The NRFBLEPeripheralDriverSettings trait does NOT come from the `rumcake` library. It is generated by the `keyboard` macro. -impl NRFBLEPeripheralDriverSettings for MyKeyboardRightHalf { - // Must be valid "Random Static" bluetooth address. - const CENTRAL_ADDRESS: [u8; 6] = [0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]; // Must match the BLUETOOTH_ADDRESS specified in the left half +use rumcake::split::peripheral::{PeripheralDevice, PeripheralDeviceDriver}; +async fn my_peripheral_setup() -> impl PeripheralDeviceDriver { + // TODO: We will fill this out soon! + todo!() } +impl PeripheralDevice for MyKeyboardRightHalf {} ``` :::note For a peripheral device, you do not have to implement `KeyboardLayout`. Only `KeyboardMatrix` is required. ::: +Lastly, you must set up the driver. To do this, you need to complete your `driver_setup_fn` by constructing the driver. +You can [check the API reference for your chosen driver](/rumcake/api/nrf52840/rumcake/drivers/index.html) for a set up +function or macro to make this process easier. + +Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. +Check the [list of available split drivers](#available-drivers) for this information. + +For example, with the `SerialSplitDriver` struct, you can construct it like so: + +```rust del={10-11} ins={12-23} +// KeyboardLayout should already be implemented +use rumcake::keyboard::KeyboardLayout; +impl KeyboardLayout for MyKeyboardLeftHalf { /* ... */ } + +// Split central setup +use rumcake::drivers::SerialSplitDriver; +use rumcake::hw::platform::setup_buffered_uarte; +use rumcake::split::peripheral::{PeripheralDevice, PeripheralDeviceDriver}; +async fn my_peripheral_setup() -> impl PeripheralDeviceDriver { + // TODO: We will fill this out soon! + todo!() + SerialSplitDriver { + serial: setup_buffered_uarte! { // Note: this assumes nRF5x, other MCUs have their own macros with their own arguments. + interrupt: UARTE0_UART0, + uarte: UARTE0, + timer: TIMER1, + ppi_ch0: PPI_CH0, + ppi_ch1: PPI_CH1, + ppi_group: PPI_GROUP0, + rx_pin: P0_31, + tx_pin: P0_29, + }, + } +} +impl PeripheralDevice for MyKeyboardRightHalf {} +``` + :::note -In case you are using the `ble` driver, if your keyboard also communicates with your host device using Bluetooth -(basically if you followed the [Bluetooth doc](../feature-bluetooth-host/) or chose a template with Bluetooth), -then the `BluetoothDevice` trait should already be implemented for you. +If you would like to use nRF BLE as the driver for split keyboard communication, see the [nRF-BLE](#nrf-ble-driver) section for more instruction. ::: # Central Device Without a Matrix (Dongle) @@ -184,6 +248,104 @@ struct MyKeyboardDongle; // rest of your config ... ``` +# nRF-BLE Driver + +If you are using an nRF5x MCU, and want to use BLE for split keyboard communication, there are additional changes +you need to make it work. + +For both central and peripheral devices, the [`BluetoothDevice`](/rumcake/api/nrf52840/rumcake/hw/platform/trait.BluetoothDevice.html) trait must be implemented: + +`BLUETOOTH_ADDRESS` can be whatever you want, as long as it is a valid "Random Static" bluetooth address. +See "Random Static Address" here: https://novelbits.io/bluetooth-address-privacy-ble/ + +```rust ins={2-5} +// central file +use rumcake::hw::platform::BluetoothDevice; +impl BluetoothDevice for MyKeyboardLeftHalf { + const BLUETOOTH_ADDRESS: [u8; 6] = [0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7]; // TODO: Change this to something else +} +``` + +```rust ins={2-5} +// peripheral file +use rumcake::hw::platform::BluetoothDevice; +impl BluetoothDevice for MyKeyboardRightHalf { + const BLUETOOTH_ADDRESS: [u8; 6] = [0x92, 0x32, 0x98, 0xC7, 0xF6, 0xF8]; // TODO: Change this to something else +} +``` + +:::note +In case you are using the `ble` driver, if your keyboard also communicates with your host device using Bluetooth +(basically if you followed the [Bluetooth doc](../feature-bluetooth-host/) or chose a template with Bluetooth), +then the `BluetoothDevice` trait should already be implemented for you. +::: + +You will also need to change the `#[keyboard]` macro invocation to add `driver_type = "nrf-ble"`. +This will change the requirements for the signature of `driver_setup_fn`. + +```rust ins={6} +// central file +#[keyboard( + // somewhere in your keyboard macro invocation ... + split_central( + driver_setup_fn = my_central_setup, + driver_type = "nrf-ble" + ) +)] +struct MyKeyboardLeftHalf; +``` + +```rust ins={6} +// peripheral file +#[keyboard( + // somewhere in your keyboard macro invocation ... + split_peripheral( + driver_setup_fn = my_peripheral_setup, + driver_type = "nrf-ble" + ) +)] +struct MyKeyboardRightHalf; +``` + +Now, your `driver_setup_fn` will need to change it's signature. + +For central devices, it will need to return: + +- `CentralDeviceDriver` implementor +- A slice containing peripheral addresses to connect to + +For peripheral devices, it will need to return: + +- `PeripheralDeviceDriver` implementor +- Address of the central device to connect to + +The `setup_nrf_ble_split_central!` and `setup_nrf_ble_split_peripheral!` driver can be used to +implement your `driver_setup_fn`. + +```rust del={3} ins={4-9} +// central file +use rumcake::drivers::nrf_ble::central::setup_nrf_ble_split_central; +async fn my_central_setup() -> impl CentralDeviceDriver { +async fn my_central_setup() -> (impl CentralDeviceDriver, &'static [[u8; 6]]) { + setup_nrf_ble_split_central! { + peripheral_addresses: [ + [0x92, 0x32, 0x98, 0xC7, 0xF6, 0xF8] // address of peripheral we specified in the peripheral device's file + ] + } +} +``` + +```rust del={3} ins={4-7} +// peripheral file +use rumcake::drivers::nrf_ble::peripheral::setup_nrf_ble_split_peripheral; +async fn my_peripheral_setup() -> impl PeripheralDeviceDriver { +async fn my_peripheral_setup() -> (impl PeripheralDeviceDriver, [u8; 6]) { + setup_nrf_ble_split_peripheral! { + central_address: [0x41, 0x5A, 0xE3, 0x1E, 0x83, 0xE7] // address of central device we specified in the central device's file + } +} +``` + # To-do List - [ ] Method of syncing backlight and underglow commands from central to peripherals on split keyboard setups @@ -193,13 +355,11 @@ struct MyKeyboardDongle; # Available Drivers -| Name | Feature Flag | `keyboard` Macro Driver String | Required Traits | -| ---------------- | -------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Serial[^1] | N/A (available by default) | `"serial"` | `SerialDriverSettings`[^2] | -| nRF Bluetooth LE | `nrf-ble` | `"ble"` | [`BluetoothDevice`](/rumcake/api/nrf52840/rumcake/hw/mcu/trait.BluetoothDevice.html), `NRFBLECentralDriverSettings`[^2] (central device only), `NRFBLEPeripheralDriverSettings`[^2] (peripheral device only) | +| Name | Feature Flag | Required Traits | +| ---------------- | -------------------------- | ------------------------------------------------------------------------------------ | +| Serial[^1] | N/A (available by default) | N/A | +| nRF Bluetooth LE | `nrf-ble` | [`BluetoothDevice`](/rumcake/api/nrf52840/rumcake/hw/mcu/trait.BluetoothDevice.html) | [^1]: Compatible with any type that implements both `embedded_io_async::Read` and `embedded_io_async::Write`. This includes `embassy_nrf::buffered_uarte::BufferedUarte` (nRF UARTE) and `embassy_stm32::usart::BufferedUart` (STM32 UART). - -[^2]: This trait is generated by the `keyboard` macro, and not included in the `rumcake` API. diff --git a/docs/src/content/docs/features/feature-storage.md b/docs/src/content/docs/features/feature-storage.md index bc96c59..c545f49 100644 --- a/docs/src/content/docs/features/feature-storage.md +++ b/docs/src/content/docs/features/feature-storage.md @@ -66,54 +66,52 @@ __config_end = __config_start + LENGTH(CONFIG); /* add this */ - The value of `__config_start` and `__config_end` must be **relative to the start address of the FLASH section**. - Note that in the above example, we subtract `ORIGIN(FLASH)` for this reason. -Finally, you can add `storage(driver = "internal")` to your `#[keyboard]` macro invocation, and make sure to implement -`StorageDevice` for your keyboard: +Finally, you can add `storage(driver = "internal")` to your `#[keyboard]` macro invocation. -```rust ins={5,7-11,16-23} +```rust ins={5,7-13} #[keyboard( // somewhere in your keyboard macro invocation ... underglow( - driver = "ws2812_bitbang", + driver_setup_fn = my_underglow_setup, use_storage // This underglow feature uses storage ), storage( driver = "internal", - // `flash_size` below is required for RP2040, omit if you are not using an RP2040. + // `flash_size` below is only for RP2040. Omit if you are not using an RP2040. // Should be equal to the total size of the flash chip (not the size of your CONFIG partition) - flash_size = 2097152 + flash_size = 2097152, + // `dma` channel used to handle flash operations on RP2040. Omit if not using RP2040. + dma = DMA_CH0 ) )] struct MyKeyboard; - -use rumcake::storage::StorageDevice; -impl StorageDevice for MyKeyboard {} - -// Required for RP2040, omit if you are not using an RP2040 -use rumcake::hw::mcu::setup_dma_channel; -impl RP2040FlashSettings for MyKeyboard { - setup_dma_channel! { DMA_CH0 } -} ``` :::note -**For RP2040 users**: You must implement the `RP2040FlashSettings` trait (generated by the -`#[keyboard]` macro), and the `#[keyboard]` macro invocation must also include `flash_size` as +**For RP2040 users**: the `#[keyboard]` macro invocation must also include `flash_size`, and a `dma` channel shown in the example above. If you are not using RP2040, these things can be omitted. ::: :::tip By default, the `setup_storage_buffer()` function in the `StorageDevice` trait creates a buffer with a size of 1024 bytes. You can override the implementation to increase the size of the -buffer to store values that may be larger, or you can decrease the size to save memory: - -```rust del={3} ins={4} -impl StorageDevice for MyKeyboard { - fn get_storage_buffer() -> &'static mut [u8] { - static mut STORAGE_BUFFER: [u8; 1024] = [0; 1024]; - static mut STORAGE_BUFFER: [u8; 32] = [0; 32]; - unsafe { &mut STORAGE_BUFFER } - } -} +buffer to store values that may be larger, or you can decrease the size to save memory. This can +be done by adding `buffer_size` to your macro invocation: + +```rust ins={10} +#[keyboard( + // somewhere in your keyboard macro invocation ... + storage( + driver = "internal", + // `flash_size` below is required for RP2040, omit if you are not using an RP2040. + // Should be equal to the total size of the flash chip (not the size of your CONFIG partition) + flash_size = 2097152, + // `dma` channel used to handle flash operations on RP2040. Omit if not using RP2040. + dma = DMA_CH0, + buffer_size = 512 + ) +)] +struct MyKeyboard; ``` Keep in mind, that the size of this buffer must be large enough to store the largest possible value diff --git a/docs/src/content/docs/features/feature-underglow.md b/docs/src/content/docs/features/feature-underglow.md index 2b19fc7..2ec0529 100644 --- a/docs/src/content/docs/features/feature-underglow.md +++ b/docs/src/content/docs/features/feature-underglow.md @@ -19,24 +19,33 @@ You must enable the following `rumcake` features: ## Required code -To set up underglow, you must add `underglow(driver = "")` to your `#[keyboard]` macro invocation, -and your keyboard must implement the `UnderglowDevice` trait. Optionally, you can add `use_storage` to the -macro invocation to use the specified storage driver to save underglow config data. +To set up underglow, you must add a new type to implement traits on. +Then, you can add `underglow(id = , driver_setup_fn = )` to your `#[keyboard]` macro +invocation. Your new type must implement the `UnderglowDevice` trait. -```rust ins={5-7,11-16} +The `driver_setup_fn` must be an async function that has no parameters, and returns a type that implements the +[`UnderglowDriver`](/rumcake/api/nrf52840/rumcake/lighting/underglow/trait.UnderglowDriver.html) trait. + +```rust ins={5-7,13-22} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... underglow( - driver = "ws2812_bitbang", // TODO: change this to your desired underglow driver, and implement the appropriate trait (info below) + id = MyKeyboardUnderglow, + driver_setup_fn = my_underglow_setup, ) )] struct MyKeyboard; // Underglow configuration -use rumcake::underglow::UnderglowDevice; -impl UnderglowDevice for MyKeyboard { +use rumcake::lighting::underglow::{UnderglowDevice, UnderglowDriver}; +struct MyKeyboardUnderglow; // New type to implement underglow traits on +async fn my_underglow_setup() -> impl UnderglowDriver { + // TODO: We will fill this out soon! + todo!() +} +impl UnderglowDevice for MyKeyboardUnderglow { // Mandatory: set number of LEDs const NUM_LEDS: usize = 20 } @@ -48,13 +57,14 @@ hue, saturation, effect, etc.) will **NOT** be saved by default. Optionally, you can add `use_storage`, and a `storage` driver to save underglow config data. -```rust ins={7,9} +```rust ins={8,10} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... underglow( - driver = "ws2812_bitbang", // TODO: change this to your desired underglow driver, and implement the appropriate trait (info below) + id = MyKeyboardUnderglow, + driver_setup_fn = my_underglow_setup, use_storage // Optional, if you want to save underglow configuration ) storage(driver = "internal") // You need to specify a storage driver if you specified `use_storage`. See feature-storage.md for more information. @@ -66,19 +76,27 @@ You will need to do additional setup for your selected storage driver as well. For more information, see the docs for the [storage feature](../feature-storage/). ::: -Lastly, you must also implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. +Lastly, you must set up the driver. To do this, you need to complete your `driver_setup_fn` by constructing the driver. +You can [check the API reference for your chosen driver](/rumcake/api/nrf52840/rumcake/drivers/index.html) for a set up +function or macro to make this process easier. + +Depending on the driver, you may also need to implement the appropriate trait that corresponds to your chosen driver in the `#[keyboard]` macro. Check the [list of available underglow drivers](#available-drivers) for this information. -For example, with `ws2812_bitbang`, you must implement `WS2812BitbangDriverSettings`: +For example, with `ws2812_bitbang`, you can use the `setup_ws2812_bitbang!` macro to set up the driver: -```rust ins={3-7} +```rust del={7-8} ins={4,9} // later in your file... -use rumcake::drivers::ws2812_bitbang::ws2812_bitbang_pin; -// Note: The WS2812BitbangDriverSettings trait does NOT come from the `rumcake` library. It is generated by the `keyboard` macro. -impl WS2812BitbangDriverSettings for MyKeyboard { - ws2812_bitbang_pin! { PA10 } +use rumcake::lighting::underglow::{UnderglowDevice, UnderglowDriver}; +use rumcake::drivers::ws2812_bitbang::setup_ws2812_bitbang; +struct MyKeyboardUnderglow; // New type to implement underglow traits on +async fn my_underglow_setup() -> impl UnderglowDriver { + // TODO: We will fill this out soon! + todo!() + setup_ws2812_bitbang! { pin: PA10 } } +impl UnderglowDevice for MyKeyboardUnderglow { /* ... */ } ``` # Keycodes @@ -108,26 +126,31 @@ SaveConfig, // normally called internally when the underglow config changes, onl ResetTime, // normally used internally for syncing LEDs for split keyboards ``` +In your `KeyboardLayout` implementation, you must choose the underglow system that the keycodes will +correspond to by implementing `UnderglowDeviceType`. + Example of usage: -```rust +```rust ins={14} use keyberon::action::Action::*; -use rumcake::underglow::animations::UnderglowCommand::*; +use rumcake::lighting::underglow::UnderglowCommand::*; use rumcake::keyboard::{build_layout, Keyboard, Keycode::*}; -/* ... */ +impl KeyboardLayout for MyKeyboard { + /* ... */ build_layout! { { [ Escape {Custom(Underglow(Toggle))} A B C] } } + + type UnderglowDeviceType = MyKeyboardUnderglow; +} ``` # Available Drivers -| Name | Feature Flag | `keyboard` Macro Driver String | Required Traits | -| -------------- | ---------------- | ------------------------------ | --------------------------------- | -| WS2812 Bitbang | `ws2812-bitbang` | `"ws2812_bitbang"` | `WS2812BitbangDriverSettings`[^1] | - -[^1]: This trait is generated by the `keyboard` macro, and not included in the `rumcake` API. +| Name | Feature Flag | Required Traits | +| -------------- | ---------------- | --------------- | +| WS2812 Bitbang | `ws2812-bitbang` | N/A | diff --git a/docs/src/content/docs/features/feature-via-vial.md b/docs/src/content/docs/features/feature-via-vial.md index 214f73f..890aa02 100644 --- a/docs/src/content/docs/features/feature-via-vial.md +++ b/docs/src/content/docs/features/feature-via-vial.md @@ -36,7 +36,8 @@ You must enable the following `rumcake` features: ## Required code -To set up Via and Vial support, your keyboard must implement the `ViaKeyboard` trait, and add `via` to your `keyboard` macro invocation. +To set up Via and Vial support, you must add a new type to implement the `ViaKeyboard` trait on. +Then, you can add `via(id = )` to your `keyboard` macro invocation. Certain Via features need to be configured manually as well: @@ -48,23 +49,32 @@ Certain Via features need to be configured manually as well: For other configurable Via options, see the [`ViaKeyboard` trait](/rumcake/api/nrf52840/rumcake/via/trait.ViaKeyboard.html). -```rust ins={5,9-17} +```rust ins={5-7,16-26} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... - via + via( + id = MyKeyboardVia + ) )] struct MyKeyboard; +impl KeyboardLayout for MyKeyboard { + /* ... */ +} + // Via setup use rumcake::via::{BacklightType, setup_macro_buffer, ViaKeyboard}; -impl ViaKeyboard for MyKeyboard { +struct MyKeyboardVia; +impl ViaKeyboard for MyKeyboardVia { + type Layout = MyKeyboard; // Must be a type that implements `KeyboardLayout` + // OPTIONAL, this example assumes you are using simple-backlight-matrix. const BACKLIGHT_TYPE: Option = Some(BacklightType::SimpleBacklightMatrix) // OPTIONAL, include this if you want to create macros using the Via app. - setup_macro_buffer!(512, 16) // Max number of bytes that can be taken up by macros, followed by the max number of macros that can be created. + setup_macro_buffer!(buffer_size: 512, macro_count: 16) // Max number of bytes that can be taken up by macros, followed by the max number of macros that can be created. } ``` @@ -74,18 +84,28 @@ lighting settings, etc.) will **NOT** be saved by default. Optionally, you can add `use_storage`, and a `storage` driver to save Via data. -```rust del={5} ins={6-9,22-23} +Additionally, you will need to call `connect_storage_service` in your `ViaKeyboard` implementation. + +```rust del={5} ins={6-10,18} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... via, via( + id = MyKeyboardVia, use_storage // Optional, if you want to save Via configuration ), storage(driver = "internal") // You need to specify a storage driver if you specified `use_storage`. See feature-storage.md for more information. )] struct MyKeyboard; + +//... +use rumcake::via::connect_storage_service; +impl ViaKeyboard for MyKeyboardVia { + //... + connect_storage_service!(MyKeyboard) +} ``` You will need to do additional setup for your selected storage driver as well. @@ -100,15 +120,19 @@ implement `KEYBOARD_DEFINITION`. Please follow the instructions in the [Vial Def For other configurable Vial options, see the [`VialKeyboard` trait](/rumcake/api/nrf52840/rumcake/vial/trait.VialKeyboard.html) -```rust del={7} ins={1-3,8,24-30} +```rust del={7} ins={1-3,10,30-35} // GENERATED_KEYBOARD_DEFINITION comes from _generated.rs, which is made by the build.rs script. #[cfg(vial)] include!(concat!(env!("OUT_DIR"), "/_generated.rs")); #[keyboard( // somewhere in your keyboard macro invocation ... - via - vial + via( + id = MyKeyboardVia + ) + vial( + id = MyKeyboardVia + ) )] struct MyKeyboard; @@ -117,11 +141,13 @@ struct MyKeyboard; // Via setup use rumcake::via::{setup_macro_buffer, ViaKeyboard}; impl ViaKeyboard for MyKeyboard { + type Layout = MyKeyboard; // Must be a type that implements `KeyboardLayout` + // OPTIONAL, this example assumes you are using simple-backlight-matrix. const BACKLIGHT_TYPE: Option = Some(BacklightType::SimpleBacklightMatrix) // OPTIONAL, include this if you want to create macros using the Via app. - setup_macro_buffer!(512, 16) // Max number of bytes that can be taken up by macros, followed by the max number of macros that can be created. + setup_macro_buffer!(buffer_size: 512, macro_count: 16) // Max number of bytes that can be taken up by macros, followed by the max number of macros that can be created. } use rumcake::vial::VialKeyboard; @@ -133,20 +159,29 @@ impl VialKeyboard for MyKeyboard { ``` :::caution -Similarly to the previous caution, you need to specify `use_storage`, to save Vial data: +Similarly to the previous caution, you need to specify `use_storage`, to save Vial data. +`connect_storage_service!` is still implemented inside `ViaKeyboard`: -```rust del={5} ins={6-9,22-23} +```rust del={5} ins={6-10,18} use rumcake::keyboard; #[keyboard( // somewhere in your keyboard macro invocation ... vial, vial( + id = MyKeyboardVia, use_storage // Optional, if you want to save Vial configuration ), storage = "internal" // You need to specify a storage driver if you specified `use_storage`. See feature-storage.md for more information. )] struct MyKeyboard; + +//... +use rumcake::via::connect_storage_service; +impl ViaKeyboard for MyKeyboardVia { + //... + connect_storage_service!(MyKeyboard) +} ``` ::: diff --git a/docs/src/content/docs/getting-started/matrix-and-layout.md b/docs/src/content/docs/getting-started/matrix-and-layout.md index c19aeeb..6e91cab 100644 --- a/docs/src/content/docs/getting-started/matrix-and-layout.md +++ b/docs/src/content/docs/getting-started/matrix-and-layout.md @@ -43,7 +43,7 @@ In the [templates](https://github.com/Univa/rumcake-templates), you will see tha to implement a keyboard matrix, you need to implement the `KeyboardMatrix` trait using one of the `build__matrix!` macros: -```rust ins={13-20} +```rust ins={13-21} use rumcake::keyboard; #[keyboard(usb)] @@ -58,13 +58,18 @@ impl Keyboard for MyKeyboard { use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { + type Layout = Self; // Don't worry about the error here yet. It will be fixed once you implement `KeyboardLayout` + build_standard_matrix! { - { PB2 PB10 PB11 PA3 } // Rows - { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns + rows: [ PB2 PB10 PB11 PA3 ], + cols: [ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 ] } } ``` +If you see an error about `Self` not implementing `KeyboardLayout`, don't worry. This will be fixed once you follow +the next section. Note that this associated type is used to redirect matrix events to the implemented layout. + The identifiers used for the matrix pins must match the identifiers used by the respective HAL (hardware abstraction library) for your MCU. The linked sites below have a dropdown menu at the top to allow you to select a chip. Choose your chip to see what pins are available: @@ -92,7 +97,7 @@ Please follow `keyberon`'s macro instructions there to set up your keyboard layo The following example shows a 3-layer keyboard layout, meant to be used with the matrix we defined previously: -```rust ins={22-44} +```rust ins={24-46} use rumcake::keyboard; #[keyboard(usb)] @@ -107,9 +112,11 @@ impl Keyboard for MyKeyboard { use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { + type Layout = Self; + build_standard_matrix! { - { PB2 PB10 PB11 PA3 } // Rows - { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 } // Columns + rows: [ PB2 PB10 PB11 PA3 ], + cols: [ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 PB3 PB4 PA15 PB5 ] } } @@ -149,11 +156,13 @@ and flashing your firmware, or try implementing additional features in the "Feat If your MCU pins are connected directly to a switch (as opposed to pins being connected to a row / column of switches), then you can use the `build_direct_pin_matrix!` macro instead. -```rust ins={3-9} +```rust ins={3-11} // rest of your config... use rumcake::keyboard::{build_direct_pin_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { + type Layout = Self; + build_direct_pin_matrix! { [ PB2 PB10 PB11 PA3 ] [ PB12 PB1 PB0 No ] @@ -191,16 +200,15 @@ If your switch is powered by an analog-to-digital conversion peripheral (which i then you can use the `build_analog_matrix!` macro. In addition, you will need to specify an ADC sampler configuration, using the `setup_adc_sampler!` macro. -```rust ins={4-15,17-29} +```rust ins={4-15,17-31} // rest of your config... // Create an ADC sampler, where the pins of the MCU are either connected to a multiplexer, or directly to the analog source setup_adc_sampler! { - // (interrupt, ADC peripheral) => { ... - (ADC1_2, ADC2) => { + (interrupt: ADC1_2, adc: ADC2) => { Multiplexer { - pin: PA2 // MCU analog pin connected to a multiplexer - select_pins: { PA3 No PA4 } // Pins connected to the selection pins on the multiplexer + pin: PA2, // MCU analog pin connected to a multiplexer + select_pins: [ PA3 No PA4 ] // Pins connected to the selection pins on the multiplexer }, Direct { pin: PA5 // MCU analog pin connected directly to an analog source @@ -210,12 +218,14 @@ setup_adc_sampler! { use rumcake::keyboard::{build_analog_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { + type Layout = Self; + build_analog_matrix! { - { + channels: { [ (1,0) (0,1) (0,4) (0,5) ] [ (0,0) No No No ] - } - { + }, + ranges: { [ 3040..4080 3040..4080 3040..4080 3040..4080 ] [ 3040..4080 No No No ] } @@ -277,12 +287,12 @@ you to configure something that would look like your matrix. This can be useful for your keyboard layout config, or your backlight matrix config: -```rust del={50-63} ins={1-26,64-75} +```rust del={52-65} ins={1-26,66-77} // This creates a `remap!` macro that you can use in other parts of your config. remap_matrix! { // This has the same number of rows and columns that you specified in your matrix. // Note that `No` is used to denote an unused matrix position. - { + original: { [ K00 K01 K02 K03 K04 K05 K06 K07 ] [ K08 K09 K10 K11 K12 K13 K14 No ] [ K15 K16 K17 K18 K19 K20 K21 K22 ] @@ -293,10 +303,10 @@ remap_matrix! { [ K53 K54 K55 K56 K57 K58 K59 No ] [ K60 K61 K62 K63 K64 K65 K66 K67 ] [ No No No No No K68 K69 No ] - } + }, // This can be whatever you want it to be. Make it look like your physical layout! - { + remapped: { [ K00 K08 K01 K09 K02 K10 K03 K11 K04 K12 K05 K13 K06 K14 K07 K22 ] [ K15 K23 K16 K24 K17 K25 K18 K26 K19 K27 K20 K28 K21 K29 K37 ] [ K30 K38 K31 K39 K32 K40 K33 K41 K34 K42 K35 K43 K36 K44 K52 ] @@ -319,9 +329,11 @@ impl Keyboard for MyKeyboard { use rumcake::keyboard::{build_standard_matrix, KeyboardMatrix}; impl KeyboardMatrix for MyKeyboard { + type Layout = Self; + build_standard_matrix! { - { PB3 PB4 PA15 PB5 PA0 PA1 PB2 PB10 PB11 PA3 } // Rows - { PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 } // Columns + rows: [ PB3 PB4 PA15 PB5 PA0 PA1 PB2 PB10 PB11 PA3 ], + cols: [ PB12 PB1 PB0 PA7 PA6 PA5 PA4 PA2 ] } } diff --git a/rumcake-macros/src/backlight.rs b/rumcake-macros/src/backlight.rs index 9cfd54e..8274a89 100644 --- a/rumcake-macros/src/backlight.rs +++ b/rumcake-macros/src/backlight.rs @@ -1,17 +1,16 @@ use proc_macro2::TokenStream; -use proc_macro_error::{abort, OptionExt}; +use proc_macro_error::OptionExt; use quote::{quote, ToTokens}; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{braced, Ident, Token}; +use syn::{Ident, Token}; -use crate::keyboard::{MatrixLike, OptionalItem}; +use crate::common::{Layer, MatrixLike, OptionalItem}; use crate::TuplePair; pub fn led_layout(input: MatrixLike>) -> TokenStream { let coordinates = input.rows.iter().map(|row| { - let items = &row.cols; + let items = &row.items; quote! { #(#items),* } }); @@ -40,15 +39,15 @@ impl ToTokens for LEDFlags { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let flags = self.flags.iter(); - quote! { #(::rumcake::backlight::LEDFlags::#flags)|* }.to_tokens(tokens) + quote! { #(::rumcake::lighting::LEDFlags::#flags)|* }.to_tokens(tokens) } } pub fn led_flags(input: MatrixLike>) -> TokenStream { let flags = input.rows.iter().map(|row| { - let items = row.cols.iter().map(|col| match col { + let items = row.items.iter().map(|col| match col { OptionalItem::None => quote! { - ::rumcake::backlight::LEDFlags::NONE + ::rumcake::lighting::LEDFlags::NONE }, OptionalItem::Some(ident) => quote! { #ident @@ -65,56 +64,40 @@ pub fn led_flags(input: MatrixLike>) -> TokenStream { } } -#[derive(Debug)] -pub struct BacklightMatrixMacroInput { - pub led_layout_brace: syn::token::Brace, - pub led_layout: MatrixLike>, - pub led_flags_brace: syn::token::Brace, - pub led_flags: MatrixLike>, -} - -impl Parse for BacklightMatrixMacroInput { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let led_layout_content; - let led_layout_brace = braced!(led_layout_content in input); - let led_flags_content; - let led_flags_brace = braced!(led_flags_content in input); - Ok(BacklightMatrixMacroInput { - led_layout_brace, - led_layout: led_layout_content.parse()?, - led_flags_brace, - led_flags: led_flags_content.parse()?, - }) +crate::parse_as_custom_fields! { + pub struct BacklightMatrixMacroInputBuilder for BacklightMatrixMacroInput { + pub led_layout: Layer>, + pub led_flags: Layer>, } } -pub fn setup_backlight_matrix(input: BacklightMatrixMacroInput) -> TokenStream { - let BacklightMatrixMacroInput { +pub fn setup_backlight_matrix( + BacklightMatrixMacroInput { led_layout, led_flags, - .. - } = input; - + }: BacklightMatrixMacroInput, +) -> TokenStream { let first_row = led_layout + .layer .rows .first() .expect_or_abort("Expected at least one row to be defined."); - let row_count = led_layout.rows.len(); - let col_count = first_row.cols.len(); + let row_count = led_layout.layer.rows.len(); + let col_count = first_row.items.len(); - let led_layout = self::led_layout(led_layout); - let led_flags = self::led_flags(led_flags); + let led_layout = self::led_layout(led_layout.layer); + let led_flags = self::led_flags(led_flags.layer); quote! { const LIGHTING_COLS: usize = #col_count; const LIGHTING_ROWS: usize = #row_count; fn get_backlight_matrix( - ) -> ::rumcake::backlight::BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }> + ) -> ::rumcake::lighting::BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }> { - const BACKLIGHT_MATRIX: ::rumcake::backlight::BacklightMatrix<#col_count, #row_count> = - ::rumcake::backlight::BacklightMatrix::new(#led_layout, #led_flags); + const BACKLIGHT_MATRIX: ::rumcake::lighting::BacklightMatrix<#col_count, #row_count> = + ::rumcake::lighting::BacklightMatrix::new(#led_layout, #led_flags); BACKLIGHT_MATRIX } } diff --git a/rumcake-macros/src/drivers/is31fl3731.rs b/rumcake-macros/src/drivers/is31fl3731.rs index edecdf6..5b13c12 100644 --- a/rumcake-macros/src/drivers/is31fl3731.rs +++ b/rumcake-macros/src/drivers/is31fl3731.rs @@ -1,7 +1,7 @@ -use crate::keyboard::{LayoutLike, MatrixLike, OptionalItem}; +use crate::common::{Layer, MatrixLike, OptionalItem}; use proc_macro2::{Ident, TokenStream}; -use proc_macro_error::OptionExt; use quote::quote; +use syn::{Expr, LitInt}; fn render_optional_item_to_led(item: &OptionalItem) -> TokenStream { match item { @@ -14,7 +14,7 @@ fn render_optional_item_to_led(item: &OptionalItem) -> TokenStream { pub fn get_led_from_matrix_coordinates(input: MatrixLike>) -> TokenStream { let values = input.rows.iter().map(|row| { - let items = row.cols.iter().map(render_optional_item_to_led); + let items = row.items.iter().map(render_optional_item_to_led); quote! { #(#items),* } }); @@ -29,40 +29,29 @@ pub fn get_led_from_matrix_coordinates(input: MatrixLike>) - } } -pub fn get_led_from_rgb_matrix_coordinates(input: LayoutLike>) -> TokenStream { - // let convert_to_ - let red_values = input - .layers - .first() - .expect_or_abort("Red LED positions not specified.") - .layer - .rows - .iter(); - - let green_values = input - .layers - .get(1) - .expect_or_abort("Green LEDs positions not specified.") - .layer - .rows - .iter(); +crate::parse_as_custom_fields! { + pub struct IS31FL3731RgbMatrixLedArgsBuilder for IS31FL3731RgbMatrixLedArgs { + red: Layer>, + green: Layer>, + blue: Layer>, + } +} - let blue_values = input - .layers - .get(2) - .expect_or_abort("Blue LEDs positions not specified.") - .layer - .rows - .iter(); +pub fn get_led_from_rgb_matrix_coordinates( + IS31FL3731RgbMatrixLedArgs { red, green, blue }: IS31FL3731RgbMatrixLedArgs, +) -> TokenStream { + let red_values = red.layer.rows.iter(); + let green_values = green.layer.rows.iter(); + let blue_values = blue.layer.rows.iter(); let rows = red_values .zip(green_values) .zip(blue_values) .map(|((red_row, green_row), blue_row)| { - let red_leds = red_row.cols.iter().map(render_optional_item_to_led); - let green_leds = green_row.cols.iter().map(render_optional_item_to_led); - let blue_leds = blue_row.cols.iter().map(render_optional_item_to_led); + let red_leds = red_row.items.iter().map(render_optional_item_to_led); + let green_leds = green_row.items.iter().map(render_optional_item_to_led); + let blue_leds = blue_row.items.iter().map(render_optional_item_to_led); quote! { #(#red_leds),*, #(#green_leds),*, #(#blue_leds),* } }); @@ -77,17 +66,28 @@ pub fn get_led_from_rgb_matrix_coordinates(input: LayoutLike } } -pub fn driver_trait() -> TokenStream { - quote! { - /// A trait that must be implemented to set up the IS31FL3731 driver. - pub(crate) trait IS31FL3731DriverSettings { - /// I2C Address for the IS31FL3731 driver. Consult the datasheet for more information. - const LED_DRIVER_ADDR: u8; +crate::parse_as_custom_fields! { + pub struct IS31FL3731ArgsBuilder for IS31FL3731Args { + device: Ident, + i2c: Expr, + address: LitInt, + } +} - /// Setup the I2C peripheral to communicate with the IS31FL3731 chip. - /// - /// It is recommended to use [`rumcake::hw::mcu::setup_i2c`] to implement this function. - fn setup_i2c() -> impl ::rumcake::embedded_hal_async::i2c::I2c; - } +pub fn setup_is31fl3731( + IS31FL3731Args { + device, + i2c, + address, + }: IS31FL3731Args, +) -> TokenStream { + quote! { + ::rumcake::drivers::is31fl3731::setup_driver( + #i2c, + #address, + <#device as ::rumcake::lighting::BacklightMatrixDevice>::LIGHTING_COLS as u8, + <#device as ::rumcake::lighting::BacklightMatrixDevice>::LIGHTING_ROWS as u8, + <#device as ::rumcake::drivers::is31fl3731::IS31FL3731BacklightDriver>::get_led_from_matrix_coordinates + ).await } } diff --git a/rumcake-macros/src/drivers/mod.rs b/rumcake-macros/src/drivers/mod.rs index 2ac5bbd..8ddf576 100644 --- a/rumcake-macros/src/drivers/mod.rs +++ b/rumcake-macros/src/drivers/mod.rs @@ -1,19 +1,4 @@ -use proc_macro2::TokenStream; -use quote::quote; - pub mod is31fl3731; pub mod nrf_ble; pub mod ssd1306; pub mod ws2812; - -pub fn serial_driver_trait() -> TokenStream { - quote! { - /// A trait that must be implemented to set up the IS31FL3731 driver. - pub(crate) trait SerialDriverSettings { - /// Setup a serial driver that is capable of both reading and writing. - /// - /// It is recommended to use a macro to implement this function. - fn setup_serial() -> impl ::rumcake::embedded_io_async::Write + ::rumcake::embedded_io_async::Read; - } - } -} diff --git a/rumcake-macros/src/drivers/nrf_ble.rs b/rumcake-macros/src/drivers/nrf_ble.rs index 61c5c6c..f39e9b9 100644 --- a/rumcake-macros/src/drivers/nrf_ble.rs +++ b/rumcake-macros/src/drivers/nrf_ble.rs @@ -1,22 +1,40 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; +use syn::ExprArray; -pub fn peripheral_driver_trait() -> TokenStream { +use crate::common::Row; + +crate::parse_as_custom_fields! { + pub struct NrfBlePeripheralArgsBuilder for NrfBlePeripheralArgs { + central_address: ExprArray, + } +} + +pub fn setup_nrf_ble_split_peripheral( + NrfBlePeripheralArgs { central_address }: NrfBlePeripheralArgs, +) -> TokenStream { quote! { - /// A trait that nRF-based keyboards must implement to use bluetooth to drive peripheral devices in a split keyboard setup. - pub(crate) trait NRFBLEPeripheralDriverSettings { - /// A "Random Static" bluetooth address of the central device that this peripheral will connect to. - const CENTRAL_ADDRESS: [u8; 6]; - } + (::rumcake::drivers::nrf_ble::peripheral::setup_driver(), #central_address) } } -pub fn central_driver_trait() -> TokenStream { +crate::parse_as_custom_fields! { + pub struct NrfBleCentralArgsBuilder for NrfBleCentralArgs { + peripheral_addresses: Row, + } +} + +pub fn setup_nrf_ble_split_central( + NrfBleCentralArgs { + peripheral_addresses, + }: NrfBleCentralArgs, +) -> TokenStream { + let items = peripheral_addresses + .items + .iter() + .map(|item| quote! { #item }); + quote! { - /// A trait that nRF-based keyboards must implement to use bluetooth to drive central devices in a split keyboard setup. - pub(crate) trait NRFBLECentralDriverSettings { - /// A list of "Random Static" bluetooth addresses that this central device can connect to. - const PERIPHERAL_ADDRESSES: &'static [[u8; 6]]; - } + (::rumcake::drivers::nrf_ble::central::setup_driver(), &[ #(#items),* ] ) } } diff --git a/rumcake-macros/src/drivers/ssd1306.rs b/rumcake-macros/src/drivers/ssd1306.rs index 2ecef9e..8c0ff35 100644 --- a/rumcake-macros/src/drivers/ssd1306.rs +++ b/rumcake-macros/src/drivers/ssd1306.rs @@ -1,24 +1,27 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; +use syn::Expr; -pub fn driver_trait() -> TokenStream { - quote! { - /// A trait that keyboards must implement to set up the SSD1306 driver. - pub(crate) trait Ssd1306I2cDriverSettings { - /// Size of the display. Must be an implementor of [`DisplaySize`]. - type SIZE_TYPE: ::rumcake::drivers::ssd1306::driver::size::DisplaySize; - - /// Size of the display. Must be an implementor of [`DisplaySize`]. - const SIZE: Self::SIZE_TYPE; - - /// Rotation of the SSD1306 display. See [`DisplayRotation`]. - const ROTATION: ::rumcake::drivers::ssd1306::driver::rotation::DisplayRotation = - ::rumcake::drivers::ssd1306::driver::rotation::DisplayRotation::Rotate90; +crate::parse_as_custom_fields! { + pub struct Ssd1306ArgsBuilder for Ssd1306Args { + i2c: Expr, + size: Ident, + rotation: Ident, + } +} - /// Setup the I2C peripheral to communicate with the SSD1306 display. - /// - /// It is recommended to use [`rumcake::hw::mcu::setup_i2c`] to implement this function. - fn setup_i2c() -> impl ::rumcake::embedded_hal::blocking::i2c::Write; - } +pub fn setup_ssd1306( + Ssd1306Args { + i2c, + size, + rotation, + }: Ssd1306Args, +) -> TokenStream { + quote! { + ::rumcake::drivers::ssd1306::setup_driver( + #i2c, + ::rumcake::drivers::ssd1306::driver::size::#size, + ::rumcake::drivers::ssd1306::driver::rotation::DisplayRotation::#rotation + ) } } diff --git a/rumcake-macros/src/drivers/ws2812.rs b/rumcake-macros/src/drivers/ws2812.rs index 442a616..c7674df 100644 --- a/rumcake-macros/src/drivers/ws2812.rs +++ b/rumcake-macros/src/drivers/ws2812.rs @@ -1,10 +1,10 @@ -use crate::keyboard::{MatrixLike, OptionalItem}; +use crate::common::{MatrixLike, OptionalItem}; use proc_macro2::{Literal, TokenStream}; use quote::quote; pub fn get_led_from_matrix_coordinates(input: MatrixLike>) -> TokenStream { let values = input.rows.iter().map(|row| { - let items = &row.cols; + let items = &row.items; quote! {#(#items),*} }); @@ -21,27 +21,33 @@ pub fn get_led_from_matrix_coordinates(input: MatrixLike>) pub mod bitbang { use proc_macro2::{Ident, TokenStream}; + use proc_macro_error::abort; use quote::quote; + use syn::LitInt; - pub fn driver_trait() -> TokenStream { - quote! { - /// A trait that must be implemented to set up the WS2812 driver. - pub(crate) trait WS2812BitbangDriverSettings { - /// Setup the GPIO pin used to send data to the WS2812 LEDs. - /// - /// It is recommended to use - /// [`rumcake::drivers::ws2812_bitbang::ws2812_bitbang_pin`] to implement this - /// function. - fn ws2812_pin() -> impl ::rumcake::embedded_hal::digital::v2::OutputPin; - } + crate::parse_as_custom_fields! { + pub struct WS2812BitbangArgsBuilder for WS2812BitbangArgs { + pin: Ident, + fudge: Option } } - pub fn pin(input: Ident) -> TokenStream { + pub fn setup_ws2812_bitbang( + WS2812BitbangArgs { pin, fudge }: WS2812BitbangArgs, + ) -> TokenStream { + let fudge = if let Some(lit) = fudge { + lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided fudge value could not be parsed as a u8 value." + ) + }) + } else { + 60 + }; + quote! { - fn ws2812_pin() -> impl ::rumcake::embedded_hal::digital::v2::OutputPin { - ::rumcake::hw::mcu::output_pin!(#input) - } + ::rumcake::drivers::ws2812_bitbang::setup_driver::<{ ::rumcake::hw::platform::SYSCLK }, #fudge>(::rumcake::hw::platform::output_pin!(#pin)) } } } diff --git a/rumcake-macros/src/hw/nrf.rs b/rumcake-macros/src/hw/nrf.rs index edb0fb2..c61cdf9 100644 --- a/rumcake-macros/src/hw/nrf.rs +++ b/rumcake-macros/src/hw/nrf.rs @@ -1,22 +1,26 @@ -use proc_macro2::{Ident, TokenStream}; +use darling::FromMeta; +use proc_macro2::{Ident, Span, TokenStream}; use proc_macro_error::{abort, OptionExt}; use quote::quote; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{braced, parenthesized, Token}; +use syn::{braced, parenthesized, LitInt, Token}; -use crate::common::{AnalogPinType, DirectPinDefinition, MultiplexerDefinition}; +use crate::common::{ + AnalogPinType, DirectPinArgs, DirectPinDefinition, MultiplexerArgs, MultiplexerDefinition, + OptionalItem, +}; -pub const HAL_CRATE: &'static str = "embassy_nrf"; +pub const HAL_CRATE: &str = "embassy_nrf"; pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_nrf::gpio::Input::new( - ::rumcake::hw::mcu::embassy_nrf::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_nrf::gpio::Input::new( + ::rumcake::hw::platform::embassy_nrf::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_nrf::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_nrf::gpio::Pull::Up, + ::rumcake::hw::platform::embassy_nrf::gpio::Pull::Up, ) } } @@ -25,114 +29,108 @@ pub fn input_pin(ident: Ident) -> TokenStream { pub fn output_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_nrf::gpio::Output::new( - ::rumcake::hw::mcu::embassy_nrf::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_nrf::gpio::Output::new( + ::rumcake::hw::platform::embassy_nrf::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_nrf::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_nrf::gpio::Level::High, - ::rumcake::hw::mcu::embassy_nrf::gpio::OutputDrive::Standard, + ::rumcake::hw::platform::embassy_nrf::gpio::Level::High, + ::rumcake::hw::platform::embassy_nrf::gpio::OutputDrive::Standard, ) } } } -fn setup_i2c_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let i2c = args - .next() - .expect_or_abort("Missing I2C peripheral argument."); - let sda = args - .next() - .expect_or_abort("Missing SDA peripheral argument."); - let scl = args - .next() - .expect_or_abort("Missing SCL peripheral argument."); - - if let Some(literal) = args.next() { - abort!(literal.span(), "Unexpected extra arguments.") +crate::parse_as_custom_fields! { + pub struct I2cArgsBuilder for I2cArgs { + interrupt: Ident, + i2c: Ident, + sda: Ident, + scl: Ident, } +} +pub fn setup_i2c( + I2cArgs { + interrupt, + i2c, + sda, + scl, + }: I2cArgs, +) -> TokenStream { quote! { - use ::rumcake::hw::mcu::embassy_nrf::interrupt::InterruptExt; unsafe { - ::rumcake::hw::mcu::embassy_nrf::bind_interrupts! { + use ::rumcake::hw::platform::embassy_nrf::interrupt::InterruptExt; + ::rumcake::hw::platform::embassy_nrf::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_nrf::twim::InterruptHandler<::rumcake::hw::mcu::embassy_nrf::peripherals::#i2c>; + #interrupt => ::rumcake::hw::platform::embassy_nrf::twim::InterruptHandler<::rumcake::hw::platform::embassy_nrf::peripherals::#i2c>; } }; - ::rumcake::hw::mcu::embassy_nrf::interrupt::#interrupt.set_priority(::rumcake::hw::mcu::embassy_nrf::interrupt::Priority::P2); - let i2c = ::rumcake::hw::mcu::embassy_nrf::peripherals::#i2c::steal(); - let sda = ::rumcake::hw::mcu::embassy_nrf::peripherals::#sda::steal(); - let scl = ::rumcake::hw::mcu::embassy_nrf::peripherals::#scl::steal(); - ::rumcake::hw::mcu::embassy_nrf::twim::Twim::new(i2c, Irqs, sda, scl, Default::default()) - } - } -} - -pub fn setup_i2c(args: Punctuated) -> TokenStream { - let inner = setup_i2c_inner(args); - quote! { - fn setup_i2c() -> impl ::rumcake::embedded_hal_async::i2c::I2c { - #inner + ::rumcake::hw::platform::embassy_nrf::interrupt::#interrupt.set_priority(::rumcake::hw::platform::embassy_nrf::interrupt::Priority::P2); + let i2c = ::rumcake::hw::platform::embassy_nrf::peripherals::#i2c::steal(); + let sda = ::rumcake::hw::platform::embassy_nrf::peripherals::#sda::steal(); + let scl = ::rumcake::hw::platform::embassy_nrf::peripherals::#scl::steal(); + ::rumcake::hw::platform::embassy_nrf::twim::Twim::new(i2c, Irqs, sda, scl, Default::default()) } } } -pub fn setup_i2c_blocking(args: Punctuated) -> TokenStream { - let inner = setup_i2c_inner(args); - quote! { - fn setup_i2c( - ) -> impl ::rumcake::embedded_hal::blocking::i2c::Write { - #inner - } +crate::parse_as_custom_fields! { + pub struct BufferedUarteArgsBuilder for BufferedUarteArgs { + interrupt: Ident, + uarte: Ident, + timer: Ident, + ppi_ch0: Ident, + ppi_ch1: Ident, + ppi_group: Ident, + rx_pin: Ident, + tx_pin: Ident, + buffer_size: Option, } } -fn setup_buffered_uarte_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let uarte = args - .next() - .expect_or_abort("Missing UARTE peripheral argument."); - let timer = args - .next() - .expect_or_abort("Missing timer peripheral argument."); - let ppi_ch1 = args - .next() - .expect_or_abort("Missing PPI CH1 peripheral argument."); - let ppi_ch2 = args - .next() - .expect_or_abort("Missing PPI CH2 peripheral argument."); - let ppi_group = args - .next() - .expect_or_abort("Missing PPI Group peripheral argument."); - let rx_pin = args.next().expect_or_abort("Missing RX pin argument."); - let tx_pin = args.next().expect_or_abort("Missing TX pin argument."); +pub fn setup_buffered_uarte( + BufferedUarteArgs { + interrupt, + uarte, + timer, + ppi_ch0, + ppi_ch1, + ppi_group, + rx_pin, + tx_pin, + buffer_size, + }: BufferedUarteArgs, +) -> TokenStream { + let buf_size = buffer_size.map_or(1024, |lit| { + lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided buffer size could not be parsed as a usize value." + ) + }) + }); quote! { unsafe { - static mut RBUF: [u8; 4096] = [0; 4096]; - static mut TBUF: [u8; 4096] = [0; 4096]; - ::rumcake::hw::mcu::embassy_nrf::bind_interrupts! { + static mut RBUF: [u8; #buf_size] = [0; #buf_size]; + static mut TBUF: [u8; #buf_size] = [0; #buf_size]; + ::rumcake::hw::platform::embassy_nrf::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_nrf::buffered_uarte::InterruptHandler<::rumcake::hw::mcu::embassy_nrf::peripherals::#uarte>; + #interrupt => ::rumcake::hw::platform::embassy_nrf::buffered_uarte::InterruptHandler<::rumcake::hw::platform::embassy_nrf::peripherals::#uarte>; } }; - let uarte = ::rumcake::hw::mcu::embassy_nrf::peripherals::#uarte::steal(); - let timer = ::rumcake::hw::mcu::embassy_nrf::peripherals::#timer::steal(); - let ppi_ch1 = ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch1::steal(); - let ppi_ch2 = ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch2::steal(); - let ppi_group = ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_group::steal(); - let rx_pin = ::rumcake::hw::mcu::embassy_nrf::peripherals::#rx_pin::steal(); - let tx_pin = ::rumcake::hw::mcu::embassy_nrf::peripherals::#tx_pin::steal(); - ::rumcake::hw::mcu::embassy_nrf::buffered_uarte::BufferedUarte::new( + let uarte = ::rumcake::hw::platform::embassy_nrf::peripherals::#uarte::steal(); + let timer = ::rumcake::hw::platform::embassy_nrf::peripherals::#timer::steal(); + let ppi_ch0 = ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch0::steal(); + let ppi_ch1 = ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch1::steal(); + let ppi_group = ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_group::steal(); + let rx_pin = ::rumcake::hw::platform::embassy_nrf::peripherals::#rx_pin::steal(); + let tx_pin = ::rumcake::hw::platform::embassy_nrf::peripherals::#tx_pin::steal(); + ::rumcake::hw::platform::embassy_nrf::buffered_uarte::BufferedUarte::new( uarte, timer, + ppi_ch0, ppi_ch1, - ppi_ch2, ppi_group, Irqs, rx_pin, @@ -145,19 +143,17 @@ fn setup_buffered_uarte_inner(args: Punctuated) -> TokenStream } } -pub fn setup_buffered_uarte(args: Punctuated) -> TokenStream { - let inner = setup_buffered_uarte_inner(args); - - quote! { - fn setup_serial( - ) -> impl ::rumcake::embedded_io_async::Write + ::rumcake::embedded_io_async::Read { - #inner - } +crate::parse_as_custom_fields! { + pub struct AdcArgsBuilder for AdcArgs { + timer: Ident, + ppi_ch0: Ident, + ppi_ch1: Ident, } } + pub struct NrfAdcSamplerDefinition { parenthesis_token: syn::token::Paren, - adc_instance_args: Punctuated, + adc_instance_args: AdcArgs, colon_token: Token![=>], brace_token: syn::token::Brace, channels: Punctuated, @@ -169,7 +165,7 @@ impl Parse for NrfAdcSamplerDefinition { let channels_content; Ok(Self { parenthesis_token: parenthesized!(adc_type_content in input), - adc_instance_args: Punctuated::parse_terminated(&adc_type_content)?, + adc_instance_args: adc_type_content.parse()?, colon_token: input.parse()?, brace_token: braced!(channels_content in input), channels: Punctuated::parse_terminated(&channels_content)?, @@ -184,15 +180,19 @@ pub fn setup_adc_sampler( .. }: NrfAdcSamplerDefinition, ) -> TokenStream { - let mut args = adc_instance_args.iter(); - let timer = args.next().expect_or_abort("Missing timer argument."); - let ppi_ch0 = args.next().expect_or_abort("Missing PPI CH0 argument."); - let ppi_ch1 = args.next().expect_or_abort("Missing PPI CH1 argument."); + let AdcArgs { + timer, + ppi_ch0, + ppi_ch1, + } = adc_instance_args; let channel_count = channels.len(); let select_pin_count = channels.iter().fold(0, |acc, ch| { - if let AnalogPinType::Multiplexed(MultiplexerDefinition { select_pins, .. }) = ch { - acc.max(select_pins.len()) + if let AnalogPinType::Multiplexed(MultiplexerDefinition { + multiplexer_args, .. + }) = ch + { + acc.max(multiplexer_args.select_pins.items.len()) } else { acc } @@ -203,18 +203,19 @@ pub fn setup_adc_sampler( .iter() .map(|ch| match ch { AnalogPinType::Multiplexed(MultiplexerDefinition { - pin, select_pins, .. + multiplexer_args, .. }) => { - let select_pins = select_pins.iter().map(|select_pin| match select_pin { - crate::keyboard::OptionalItem::None => quote! { None }, - crate::keyboard::OptionalItem::Some(pin_ident) => { - quote! { Some(::rumcake::hw::mcu::output_pin!(#pin_ident)) } + let MultiplexerArgs { pin, select_pins } = multiplexer_args; + let select_pins = select_pins.items.iter().map(|select_pin| match select_pin { + OptionalItem::None => quote! { None }, + OptionalItem::Some(pin_ident) => { + quote! { Some(::rumcake::hw::platform::output_pin!(#pin_ident)) } } }); ( quote! { - ::rumcake::hw::mcu::AnalogPinType::Multiplexed( + ::rumcake::hw::platform::AnalogPinType::Multiplexed( [0; #buf_size], ::rumcake::hw::Multiplexer::new( [ #(#select_pins),* ], @@ -223,31 +224,34 @@ pub fn setup_adc_sampler( ) }, quote! { - ::rumcake::hw::mcu::embassy_nrf::saadc::ChannelConfig::single_ended( - unsafe { ::rumcake::hw::mcu::embassy_nrf::peripherals::#pin::steal() } + ::rumcake::hw::platform::embassy_nrf::saadc::ChannelConfig::single_ended( + unsafe { ::rumcake::hw::platform::embassy_nrf::peripherals::#pin::steal() } ) }, ) } - AnalogPinType::Direct(DirectPinDefinition { pin, .. }) => ( - quote! { - ::rumcake::hw::mcu::AnalogPinType::Direct([0]) - }, - quote! { - ::rumcake::hw::mcu::embassy_nrf::saadc::ChannelConfig::single_ended( - unsafe { ::rumcake::hw::mcu::embassy_nrf::peripherals::#pin::steal() } - ) - }, - ), + AnalogPinType::Direct(DirectPinDefinition { direct_pin_args, .. }) => { + let DirectPinArgs { pin } = direct_pin_args; + ( + quote! { + ::rumcake::hw::platform::AnalogPinType::Direct([0]) + }, + quote! { + ::rumcake::hw::platform::embassy_nrf::saadc::ChannelConfig::single_ended( + unsafe { ::rumcake::hw::platform::embassy_nrf::peripherals::#pin::steal() } + ) + }, + ) + }, }) .unzip(); quote! { - type AdcSamplerType = ::rumcake::hw::mcu::AdcSampler< + type AdcSamplerType = ::rumcake::hw::platform::AdcSampler< 'static, - ::rumcake::hw::mcu::embassy_nrf::peripherals::#timer, - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch0, - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch1, + ::rumcake::hw::platform::embassy_nrf::peripherals::#timer, + ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch0, + ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch1, #select_pin_count, #channel_count, >; @@ -256,12 +260,12 @@ pub fn setup_adc_sampler( static SAMPLER: ::rumcake::once_cell::sync::OnceCell = ::rumcake::once_cell::sync::OnceCell::new(); SAMPLER.get_or_init(|| unsafe { - ::rumcake::hw::mcu::AdcSampler::new( + ::rumcake::hw::platform::AdcSampler::new( [ #(#pins),* ], [ #(#channels),* ], - ::rumcake::hw::mcu::embassy_nrf::peripherals::#timer::steal(), - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch0::steal(), - ::rumcake::hw::mcu::embassy_nrf::peripherals::#ppi_ch1::steal(), + ::rumcake::hw::platform::embassy_nrf::peripherals::#timer::steal(), + ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch0::steal(), + ::rumcake::hw::platform::embassy_nrf::peripherals::#ppi_ch1::steal(), ) }) } diff --git a/rumcake-macros/src/hw/rp.rs b/rumcake-macros/src/hw/rp.rs index 2464e4d..307b180 100644 --- a/rumcake-macros/src/hw/rp.rs +++ b/rumcake-macros/src/hw/rp.rs @@ -1,21 +1,24 @@ use proc_macro2::{Ident, TokenStream}; -use proc_macro_error::OptionExt; +use proc_macro_error::abort; use quote::quote; use syn::punctuated::Punctuated; -use syn::Token; +use syn::{LitInt, Token}; -use crate::common::{AnalogPinType, DirectPinDefinition, MultiplexerDefinition}; +use crate::common::{ + AnalogPinType, DirectPinArgs, DirectPinDefinition, MultiplexerArgs, MultiplexerDefinition, + OptionalItem, +}; -pub const HAL_CRATE: &'static str = "embassy_rp"; +pub const HAL_CRATE: &str = "embassy_rp"; pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_rp::gpio::Input::new( - ::rumcake::hw::mcu::embassy_rp::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_rp::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_rp::gpio::Input::new( + ::rumcake::hw::platform::embassy_rp::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_rp::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_rp::gpio::Pull::Up, + ::rumcake::hw::platform::embassy_rp::gpio::Pull::Up, ) } } @@ -24,99 +27,89 @@ pub fn input_pin(ident: Ident) -> TokenStream { pub fn output_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_rp::gpio::Output::new( - ::rumcake::hw::mcu::embassy_rp::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_rp::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_rp::gpio::Output::new( + ::rumcake::hw::platform::embassy_rp::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_rp::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_rp::gpio::Level::High, + ::rumcake::hw::platform::embassy_rp::gpio::Level::High, ) } } } -pub fn internal_storage_trait() -> TokenStream { - quote! { - /// A trait that must be implemented to use the flash chip connected to your RP2040 for storage.. - pub(crate) trait RP2040FlashSettings { - /// Get the DMA channel used for flash operations. - fn setup_dma_channel( - ) -> impl ::rumcake::hw::mcu::embassy_rp::Peripheral

; - } +crate::parse_as_custom_fields! { + pub struct I2cArgsBuilder for I2cArgs { + interrupt: Ident, + i2c: Ident, + scl: Ident, + sda: Ident, } } -pub fn setup_dma_channel(dma: Ident) -> TokenStream { - quote! { - fn setup_dma_channel( - ) -> impl ::rumcake::hw::mcu::embassy_rp::Peripheral

{ - unsafe { - ::rumcake::hw::mcu::embassy_rp::peripherals::#dma::steal() - } - } - } -} - -fn setup_i2c_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let i2c = args - .next() - .expect_or_abort("Missing I2C peripheral argument."); - let scl = args - .next() - .expect_or_abort("Missing SCL peripheral argument."); - let sda = args - .next() - .expect_or_abort("Missing SDA peripheral argument."); - +pub fn setup_i2c( + I2cArgs { + interrupt, + i2c, + scl, + sda, + }: I2cArgs, +) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_rp::bind_interrupts! { + ::rumcake::hw::platform::embassy_rp::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_rp::i2c::InterruptHandler<::rumcake::hw::mcu::embassy_rp::peripherals::#i2c>; + #interrupt => ::rumcake::hw::platform::embassy_rp::i2c::InterruptHandler<::rumcake::hw::platform::embassy_rp::peripherals::#i2c>; } }; - let i2c = ::rumcake::hw::mcu::embassy_rp::peripherals::#i2c::steal(); - let scl = ::rumcake::hw::mcu::embassy_rp::peripherals::#scl::steal(); - let sda = ::rumcake::hw::mcu::embassy_rp::peripherals::#sda::steal(); - ::rumcake::hw::mcu::embassy_rp::i2c::I2c::new_async(i2c, scl, sda, Irqs, Default::default()) + let i2c = ::rumcake::hw::platform::embassy_rp::peripherals::#i2c::steal(); + let scl = ::rumcake::hw::platform::embassy_rp::peripherals::#scl::steal(); + let sda = ::rumcake::hw::platform::embassy_rp::peripherals::#sda::steal(); + ::rumcake::hw::platform::embassy_rp::i2c::I2c::new_async(i2c, scl, sda, Irqs, Default::default()) } } } -pub fn setup_i2c(args: Punctuated) -> TokenStream { - let inner = setup_i2c_inner(args); - quote! { - fn setup_i2c() -> impl ::rumcake::embedded_hal_async::i2c::I2c { - #inner - } +crate::parse_as_custom_fields! { + pub struct BufferedUartArgsBuilder for BufferedUartArgs { + interrupt: Ident, + uart: Ident, + rx_pin: Ident, + tx_pin: Ident, + buffer_size: Option } } -fn setup_buffered_uart_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let uart = args - .next() - .expect_or_abort("Missing UART peripheral argument."); - let rx_pin = args.next().expect_or_abort("Missing RX pin argument."); - let tx_pin = args.next().expect_or_abort("Missing TX pin argument."); +pub fn setup_buffered_uart( + BufferedUartArgs { + interrupt, + uart, + rx_pin, + tx_pin, + buffer_size, + }: BufferedUartArgs, +) -> TokenStream { + let buf_size = buffer_size.map_or(64, |lit| { + lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided buffer size could not be parsed as a usize value." + ) + }) + }); quote! { unsafe { - static mut RBUF: [u8; 64] = [0; 64]; - static mut TBUF: [u8; 64] = [0; 64]; - ::rumcake::hw::mcu::embassy_rp::bind_interrupts! { + static mut RBUF: [u8; #buf_size] = [0; #buf_size]; + static mut TBUF: [u8; #buf_size] = [0; #buf_size]; + ::rumcake::hw::platform::embassy_rp::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_rp::uart::BufferedInterruptHandler<::rumcake::hw::mcu::embassy_rp::peripherals::#uart>; + #interrupt => ::rumcake::hw::platform::embassy_rp::uart::BufferedInterruptHandler<::rumcake::hw::platform::embassy_rp::peripherals::#uart>; } }; - let uart = ::rumcake::hw::mcu::embassy_rp::peripherals::#uart::steal(); - let rx = ::rumcake::hw::mcu::embassy_rp::peripherals::#rx_pin::steal(); - let tx = ::rumcake::hw::mcu::embassy_rp::peripherals::#tx_pin::steal(); - ::rumcake::hw::mcu::embassy_rp::uart::BufferedUart::new( + let uart = ::rumcake::hw::platform::embassy_rp::peripherals::#uart::steal(); + let rx = ::rumcake::hw::platform::embassy_rp::peripherals::#rx_pin::steal(); + let tx = ::rumcake::hw::platform::embassy_rp::peripherals::#tx_pin::steal(); + ::rumcake::hw::platform::embassy_rp::uart::BufferedUart::new( uart, Irqs, rx, @@ -129,22 +122,14 @@ fn setup_buffered_uart_inner(args: Punctuated) -> TokenStream } } -pub fn setup_buffered_uart(args: Punctuated) -> TokenStream { - let inner = setup_buffered_uart_inner(args); - - quote! { - fn setup_serial( - ) -> impl ::rumcake::embedded_io_async::Write + ::rumcake::embedded_io_async::Read { - #inner - } - } -} - pub fn setup_adc_sampler(channels: Punctuated) -> TokenStream { let channel_count = channels.len(); let select_pin_count = channels.iter().fold(0, |acc, ch| { - if let AnalogPinType::Multiplexed(MultiplexerDefinition { select_pins, .. }) = ch { - acc.max(select_pins.len()) + if let AnalogPinType::Multiplexed(MultiplexerDefinition { + multiplexer_args, .. + }) = ch + { + acc.max(multiplexer_args.select_pins.items.len()) } else { acc } @@ -154,18 +139,19 @@ pub fn setup_adc_sampler(channels: Punctuated) -> Toke .iter() .map(|ch| match ch { AnalogPinType::Multiplexed(MultiplexerDefinition { - pin, select_pins, .. + multiplexer_args, .. }) => { - let select_pins = select_pins.iter().map(|select_pin| match select_pin { - crate::keyboard::OptionalItem::None => quote! { None }, - crate::keyboard::OptionalItem::Some(pin_ident) => { - quote! { Some(::rumcake::hw::mcu::output_pin!(#pin_ident)) } + let MultiplexerArgs {pin, select_pins} = multiplexer_args; + let select_pins = select_pins.items.iter().map(|select_pin| match select_pin { + OptionalItem::None => quote! { None }, + OptionalItem::Some(pin_ident) => { + quote! { Some(::rumcake::hw::platform::output_pin!(#pin_ident)) } } }); ( quote! { - ::rumcake::hw::mcu::AnalogPinType::Multiplexed( + ::rumcake::hw::platform::AnalogPinType::Multiplexed( ::rumcake::hw::Multiplexer::new( [ #(#select_pins),* ], None @@ -173,35 +159,38 @@ pub fn setup_adc_sampler(channels: Punctuated) -> Toke ) }, quote! { - ::rumcake::hw::mcu::embassy_rp::adc::Channel::new_pin( - unsafe { ::rumcake::hw::mcu::embassy_rp::peripherals::#pin::steal() }, - ::rumcake::hw::mcu::embassy_rp::gpio::Pull::None + ::rumcake::hw::platform::embassy_rp::adc::Channel::new_pin( + unsafe { ::rumcake::hw::platform::embassy_rp::peripherals::#pin::steal() }, + ::rumcake::hw::platform::embassy_rp::gpio::Pull::None ) }, ) } - AnalogPinType::Direct(DirectPinDefinition { pin, .. }) => ( - quote! { - ::rumcake::hw::mcu::AnalogPinType::Direct - }, - quote! { - ::rumcake::hw::mcu::embassy_rp::adc::Channel::new_pin( - unsafe { ::rumcake::hw::mcu::embassy_rp::peripherals::#pin::steal() }, - ::rumcake::hw::mcu::embassy_rp::gpio::Pull::None - ) - }, - ), + AnalogPinType::Direct(DirectPinDefinition { direct_pin_args, .. }) => { + let DirectPinArgs { pin } = direct_pin_args; + ( + quote! { + ::rumcake::hw::platform::AnalogPinType::Direct + }, + quote! { + ::rumcake::hw::platform::embassy_rp::adc::Channel::new_pin( + unsafe { ::rumcake::hw::platform::embassy_rp::peripherals::#pin::steal() }, + ::rumcake::hw::platform::embassy_rp::gpio::Pull::None + ) + }, + ) + }, }) .unzip(); quote! { - type AdcSamplerType = ::rumcake::hw::mcu::AdcSampler<'static, #select_pin_count, #channel_count>; + type AdcSamplerType = ::rumcake::hw::platform::AdcSampler<'static, #select_pin_count, #channel_count>; static SAMPLER: ::rumcake::once_cell::sync::OnceCell = ::rumcake::once_cell::sync::OnceCell::new(); fn setup_adc_sampler() -> &'static AdcSamplerType { SAMPLER.get_or_init(|| - ::rumcake::hw::mcu::AdcSampler::new( + ::rumcake::hw::platform::AdcSampler::new( [ #(#pins),* ], [ #(#channels),* ] ) diff --git a/rumcake-macros/src/hw/stm32.rs b/rumcake-macros/src/hw/stm32.rs index 6b00394..835b8d2 100644 --- a/rumcake-macros/src/hw/stm32.rs +++ b/rumcake-macros/src/hw/stm32.rs @@ -3,20 +3,23 @@ use proc_macro_error::{abort, OptionExt}; use quote::quote; use syn::parse::Parse; use syn::punctuated::Punctuated; -use syn::{braced, parenthesized, Token}; +use syn::{braced, parenthesized, LitInt, Token}; -use crate::common::{AnalogPinType, DirectPinDefinition, MultiplexerDefinition}; +use crate::common::{ + AnalogPinType, DirectPinArgs, DirectPinDefinition, MultiplexerArgs, MultiplexerDefinition, + OptionalItem, +}; -pub const HAL_CRATE: &'static str = "embassy_stm32"; +pub const HAL_CRATE: &str = "embassy_stm32"; pub fn input_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_stm32::gpio::Input::new( - ::rumcake::hw::mcu::embassy_stm32::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_stm32::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_stm32::gpio::Input::new( + ::rumcake::hw::platform::embassy_stm32::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_stm32::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_stm32::gpio::Pull::Up, + ::rumcake::hw::platform::embassy_stm32::gpio::Pull::Up, ) } } @@ -25,107 +28,110 @@ pub fn input_pin(ident: Ident) -> TokenStream { pub fn output_pin(ident: Ident) -> TokenStream { quote! { unsafe { - ::rumcake::hw::mcu::embassy_stm32::gpio::Output::new( - ::rumcake::hw::mcu::embassy_stm32::gpio::Pin::degrade( - ::rumcake::hw::mcu::embassy_stm32::peripherals::#ident::steal(), + ::rumcake::hw::platform::embassy_stm32::gpio::Output::new( + ::rumcake::hw::platform::embassy_stm32::gpio::Pin::degrade( + ::rumcake::hw::platform::embassy_stm32::peripherals::#ident::steal(), ), - ::rumcake::hw::mcu::embassy_stm32::gpio::Level::High, - ::rumcake::hw::mcu::embassy_stm32::gpio::Speed::Low, + ::rumcake::hw::platform::embassy_stm32::gpio::Level::High, + ::rumcake::hw::platform::embassy_stm32::gpio::Speed::Low, ) } } } -fn setup_i2c_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let event_interrupt = args - .next() - .expect_or_abort("Missing event interrupt argument."); - let error_interrupt = args - .next() - .expect_or_abort("Missing error interrupt argument."); - let i2c = args - .next() - .expect_or_abort("Missing I2C peripheral argument."); - let scl = args - .next() - .expect_or_abort("Missing SCL peripheral argument."); - let sda = args - .next() - .expect_or_abort("Missing SDA peripheral argument."); - let rxdma = args - .next() - .expect_or_abort("Missing RX DMA peripheral argument."); - let txdma = args - .next() - .expect_or_abort("Missing TX DMA peripheral argument."); - - if let Some(literal) = args.next() { - abort!(literal.span(), "Unexpected extra arguments.") +crate::parse_as_custom_fields! { + pub struct I2cArgsBuilder for I2cArgs { + event_interrupt: Ident, + error_interrupt: Ident, + i2c: Ident, + scl: Ident, + sda: Ident, + rx_dma: Ident, + tx_dma: Ident } +} +pub fn setup_i2c( + I2cArgs { + event_interrupt, + error_interrupt, + i2c, + scl, + sda, + rx_dma, + tx_dma, + }: I2cArgs, +) -> TokenStream { let interrupt_setup = if event_interrupt == error_interrupt { quote! { - #event_interrupt => ::rumcake::hw::mcu::embassy_stm32::i2c::EventInterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#i2c>, ::rumcake::hw::mcu::embassy_stm32::i2c::ErrorInterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#i2c>; + #event_interrupt => ::rumcake::hw::platform::embassy_stm32::i2c::EventInterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#i2c>, ::rumcake::hw::platform::embassy_stm32::i2c::ErrorInterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#i2c>; } } else { quote! { - #event_interrupt => ::rumcake::hw::mcu::embassy_stm32::i2c::EventInterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#i2c>; - #error_interrupt => ::rumcake::hw::mcu::embassy_stm32::i2c::ErrorInterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#i2c>; + #event_interrupt => ::rumcake::hw::platform::embassy_stm32::i2c::EventInterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#i2c>; + #error_interrupt => ::rumcake::hw::platform::embassy_stm32::i2c::ErrorInterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#i2c>; } }; quote! { unsafe { - ::rumcake::hw::mcu::embassy_stm32::bind_interrupts! { + ::rumcake::hw::platform::embassy_stm32::bind_interrupts! { struct Irqs { #interrupt_setup } }; - let i2c = ::rumcake::hw::mcu::embassy_stm32::peripherals::#i2c::steal(); - let scl = ::rumcake::hw::mcu::embassy_stm32::peripherals::#scl::steal(); - let sda = ::rumcake::hw::mcu::embassy_stm32::peripherals::#sda::steal(); - let rx_dma = ::rumcake::hw::mcu::embassy_stm32::peripherals::#rxdma::steal(); - let tx_dma = ::rumcake::hw::mcu::embassy_stm32::peripherals::#txdma::steal(); - let time = ::rumcake::hw::mcu::embassy_stm32::time::Hertz(100_000); - ::rumcake::hw::mcu::embassy_stm32::i2c::I2c::new(i2c, scl, sda, Irqs, tx_dma, rx_dma, time, Default::default()) + let i2c = ::rumcake::hw::platform::embassy_stm32::peripherals::#i2c::steal(); + let scl = ::rumcake::hw::platform::embassy_stm32::peripherals::#scl::steal(); + let sda = ::rumcake::hw::platform::embassy_stm32::peripherals::#sda::steal(); + let rx_dma = ::rumcake::hw::platform::embassy_stm32::peripherals::#rx_dma::steal(); + let tx_dma = ::rumcake::hw::platform::embassy_stm32::peripherals::#tx_dma::steal(); + let time = ::rumcake::hw::platform::embassy_stm32::time::Hertz(100_000); + ::rumcake::hw::platform::embassy_stm32::i2c::I2c::new(i2c, scl, sda, Irqs, tx_dma, rx_dma, time, Default::default()) } } } -pub fn setup_i2c(args: Punctuated) -> TokenStream { - let inner = setup_i2c_inner(args); - quote! { - fn setup_i2c() -> impl ::rumcake::embedded_hal_async::i2c::I2c { - #inner - } +crate::parse_as_custom_fields! { + pub struct BufferedUartArgsBuilder for BufferedUartArgs { + interrupt: Ident, + uart: Ident, + rx_pin: Ident, + tx_pin: Ident, + buffer_size: Option, } } -fn setup_buffered_uart_inner(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let uart = args - .next() - .expect_or_abort("Missing UART peripheral argument."); - let rx_pin = args.next().expect_or_abort("Missing RX pin argument."); - let tx_pin = args.next().expect_or_abort("Missing TX pin argument."); +pub fn setup_buffered_uart( + BufferedUartArgs { + interrupt, + uart, + rx_pin, + tx_pin, + buffer_size, + }: BufferedUartArgs, +) -> TokenStream { + let buf_size = buffer_size.map_or(64, |lit| { + lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided buffer size could not be parsed as a usize value." + ) + }) + }); quote! { unsafe { - static mut RBUF: [u8; 64] = [0; 64]; - static mut TBUF: [u8; 64] = [0; 64]; - ::rumcake::hw::mcu::embassy_stm32::bind_interrupts! { + static mut RBUF: [u8; #buf_size] = [0; #buf_size]; + static mut TBUF: [u8; #buf_size] = [0; #buf_size]; + ::rumcake::hw::platform::embassy_stm32::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_stm32::usart::BufferedInterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#uart>; + #interrupt => ::rumcake::hw::platform::embassy_stm32::usart::BufferedInterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#uart>; } }; - let uart = ::rumcake::hw::mcu::embassy_stm32::peripherals::#uart::steal(); - let rx = ::rumcake::hw::mcu::embassy_stm32::peripherals::#rx_pin::steal(); - let tx = ::rumcake::hw::mcu::embassy_stm32::peripherals::#tx_pin::steal(); - ::rumcake::hw::mcu::embassy_stm32::usart::BufferedUart::new( + let uart = ::rumcake::hw::platform::embassy_stm32::peripherals::#uart::steal(); + let rx = ::rumcake::hw::platform::embassy_stm32::peripherals::#rx_pin::steal(); + let tx = ::rumcake::hw::platform::embassy_stm32::peripherals::#tx_pin::steal(); + ::rumcake::hw::platform::embassy_stm32::usart::BufferedUart::new( uart, Irqs, rx, @@ -138,20 +144,16 @@ fn setup_buffered_uart_inner(args: Punctuated) -> TokenStream } } -pub fn setup_buffered_uart(args: Punctuated) -> TokenStream { - let inner = setup_buffered_uart_inner(args); - - quote! { - fn setup_serial( - ) -> impl ::rumcake::embedded_io_async::Write + ::rumcake::embedded_io_async::Read { - #inner - } +crate::parse_as_custom_fields! { + pub struct AdcArgsBuilder for AdcArgs { + interrupt: Ident, + adc: Ident } } pub struct STM32AdcSamplerDefinition { parenthesis_token: syn::token::Paren, - adc_instance_args: Punctuated, + adc_instance_args: AdcArgs, colon_token: Token![=>], brace_token: syn::token::Brace, channels: Punctuated, @@ -163,7 +165,7 @@ impl Parse for STM32AdcSamplerDefinition { let channels_content; Ok(Self { parenthesis_token: parenthesized!(adc_type_content in input), - adc_instance_args: Punctuated::parse_terminated(&adc_type_content)?, + adc_instance_args: adc_type_content.parse()?, colon_token: input.parse()?, brace_token: braced!(channels_content in input), channels: Punctuated::parse_terminated(&channels_content)?, @@ -178,16 +180,15 @@ fn setup_adc_inner(adc_definition: &STM32AdcSamplerDefinition) -> (TokenStream, .. } = adc_definition; - let mut args = adc_instance_args.iter(); - let interrupt = args.next().expect_or_abort("Missing interrupt argument."); - let adc = args - .next() - .expect_or_abort("Missing ADC peripheral argument."); + let AdcArgs { interrupt, adc } = adc_instance_args; let channel_count = channels.len(); let select_pin_count = channels.iter().fold(0, |acc, ch| { - if let AnalogPinType::Multiplexed(MultiplexerDefinition { select_pins, .. }) = ch { - acc.max(select_pins.len()) + if let AnalogPinType::Multiplexed(MultiplexerDefinition { + multiplexer_args, .. + }) = ch + { + acc.max(multiplexer_args.select_pins.items.len()) } else { acc } @@ -197,18 +198,19 @@ fn setup_adc_inner(adc_definition: &STM32AdcSamplerDefinition) -> (TokenStream, .iter() .map(|ch| match ch { AnalogPinType::Multiplexed(MultiplexerDefinition { - pin, select_pins, .. + multiplexer_args, .. }) => { - let select_pins = select_pins.iter().map(|select_pin| match select_pin { - crate::keyboard::OptionalItem::None => quote! { None }, - crate::keyboard::OptionalItem::Some(pin_ident) => { - quote! { Some(::rumcake::hw::mcu::output_pin!(#pin_ident)) } + let MultiplexerArgs {pin, select_pins} = multiplexer_args; + let select_pins = select_pins.items.iter().map(|select_pin| match select_pin { + OptionalItem::None => quote! { None }, + OptionalItem::Some(pin_ident) => { + quote! { Some(::rumcake::hw::platform::output_pin!(#pin_ident)) } } }); ( quote! { - ::rumcake::hw::mcu::AnalogPinType::Multiplexed( + ::rumcake::hw::platform::AnalogPinType::Multiplexed( ::rumcake::hw::Multiplexer::new( [ #(#select_pins),* ], None @@ -216,36 +218,39 @@ fn setup_adc_inner(adc_definition: &STM32AdcSamplerDefinition) -> (TokenStream, ) }, quote! { - ::rumcake::hw::mcu::Channel::new( - unsafe { ::rumcake::hw::mcu::embassy_stm32::peripherals::#pin::steal() } + ::rumcake::hw::platform::Channel::new( + unsafe { ::rumcake::hw::platform::embassy_stm32::peripherals::#pin::steal() } ) }, ) } - AnalogPinType::Direct(DirectPinDefinition { pin, .. }) => ( - quote! { - ::rumcake::hw::mcu::AnalogPinType::Direct - }, - quote! { - ::rumcake::hw::mcu::Channel::new( - unsafe { ::rumcake::hw::mcu::embassy_stm32::peripherals::#pin::steal() } - ) - }, - ), + AnalogPinType::Direct(DirectPinDefinition { direct_pin_args, .. }) => { + let DirectPinArgs { pin } = direct_pin_args; + ( + quote! { + ::rumcake::hw::platform::AnalogPinType::Direct + }, + quote! { + ::rumcake::hw::platform::Channel::new( + unsafe { ::rumcake::hw::platform::embassy_stm32::peripherals::#pin::steal() } + ) + }, + ) + }, }) .unzip(); ( quote! { - ::rumcake::hw::mcu::AdcSampler<'static, ::rumcake::hw::mcu::embassy_stm32::peripherals::#adc, #select_pin_count, #channel_count> + ::rumcake::hw::platform::AdcSampler<'static, ::rumcake::hw::platform::embassy_stm32::peripherals::#adc, #select_pin_count, #channel_count> }, quote! { - ::rumcake::hw::mcu::AdcSampler::new( - unsafe { ::rumcake::hw::mcu::embassy_stm32::peripherals::#adc::steal() }, + ::rumcake::hw::platform::AdcSampler::new( + unsafe { ::rumcake::hw::platform::embassy_stm32::peripherals::#adc::steal() }, { - ::rumcake::hw::mcu::embassy_stm32::bind_interrupts! { + ::rumcake::hw::platform::embassy_stm32::bind_interrupts! { struct Irqs { - #interrupt => ::rumcake::hw::mcu::embassy_stm32::adc::InterruptHandler<::rumcake::hw::mcu::embassy_stm32::peripherals::#adc>; + #interrupt => ::rumcake::hw::platform::embassy_stm32::adc::InterruptHandler<::rumcake::hw::platform::embassy_stm32::peripherals::#adc>; } }; Irqs diff --git a/rumcake-macros/src/keyboard.rs b/rumcake-macros/src/keyboard.rs index 2f1c10d..2b93e4d 100644 --- a/rumcake-macros/src/keyboard.rs +++ b/rumcake-macros/src/keyboard.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; - -use darling::util::Override; +use darling::util::{Override, SpannedValue}; use darling::FromMeta; use proc_macro2::{Ident, TokenStream, TokenTree}; -use proc_macro_error::OptionExt; -use quote::{quote, quote_spanned, ToTokens}; -use syn::parse::Parse; -use syn::spanned::Spanned; -use syn::{braced, bracketed, custom_keyword, ExprRange, ItemStruct, PathSegment}; +use proc_macro_error::{abort, emit_error, OptionExt}; +use quote::quote; +use syn::{ExprRange, ItemStruct, LitInt, LitStr, PathSegment}; +use crate::common::{Layer, LayoutLike, MatrixLike, OptionalItem, Row}; use crate::TuplePair; #[derive(Debug, FromMeta, Default)] @@ -25,303 +22,220 @@ pub(crate) struct KeyboardSettings { display: Option, split_peripheral: Option, split_central: Option, - via: Option>, - vial: Option>, - bootloader_double_tap_reset: Option>, + via: Option, + vial: Option, + bootloader_double_tap_reset: Option>, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct LightingSettings { - driver: String, - use_storage: bool, + id: Ident, + driver_setup_fn: Ident, + use_storage: Option>, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct DisplaySettings { - driver: String, + driver_setup_fn: Ident, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct SplitCentralSettings { - driver: String, + driver_type: Option, + driver_setup_fn: Ident, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct SplitPeripheralSettings { - driver: String, + driver_type: Option, + driver_setup_fn: Ident, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct ViaSettings { - use_storage: bool, + id: Ident, + use_storage: Option>, } -#[derive(Debug, FromMeta, Default)] -#[darling(default)] +#[derive(Debug, FromMeta)] pub(crate) struct StorageSettings { - driver: String, - flash_size: usize, -} - -enum SplitSettings<'a> { - Central(&'a SplitCentralSettings), - Peripheral(&'a SplitPeripheralSettings), + driver: LitStr, + buffer_size: Option, + flash_size: Option, + dma: Option, } -fn setup_split_driver( +fn setup_storage_driver( initialization: &mut TokenStream, - spawning: &mut TokenStream, - traits: &mut HashMap, + outer: &mut TokenStream, kb_name: &Ident, - role: SplitSettings, -) { - match role { - SplitSettings::Central(config) => match config.driver.as_str() { - "ble" => { - if cfg!(feature = "nrf") { - return { - traits.insert( - config.driver.clone(), - crate::drivers::nrf_ble::central_driver_trait(), - ); - initialization.extend(quote! { - let split_central_driver = ::rumcake::drivers::nrf_ble::central::setup_driver(); - }); - spawning.extend(quote! { - spawner.spawn(::rumcake::nrf_ble_central_task!(<#kb_name as NRFBLECentralDriverSettings>::PERIPHERAL_ADDRESSES, sd)).unwrap(); - }); - }; - } - } - "serial" => { - return { - traits.insert(config.driver.clone(), crate::drivers::serial_driver_trait()); - initialization.extend(quote! { - let split_central_driver = ::rumcake::drivers::SerialSplitDriver { serial: <#kb_name as SerialDriverSettings>::setup_serial() }; - }); - }; - } - _ => (), - }, - SplitSettings::Peripheral(config) => match config.driver.as_str() { - "ble" => { - if cfg!(feature = "nrf") { - return { - traits.insert( - config.driver.clone(), - crate::drivers::nrf_ble::peripheral_driver_trait(), - ); - initialization.extend(quote! { - let peripheral_server = ::rumcake::drivers::nrf_ble::peripheral::PeripheralDeviceServer::new(sd).unwrap(); - let split_peripheral_driver = ::rumcake::drivers::nrf_ble::peripheral::setup_driver(); - }); - spawning.extend(quote! { - spawner.spawn(::rumcake::nrf_ble_peripheral_task!(<#kb_name as NRFBLEPeripheralDriverSettings>::CENTRAL_ADDRESS, sd, peripheral_server)).unwrap(); - }); - }; - } - } - "serial" => { - return { - traits.insert(config.driver.clone(), crate::drivers::serial_driver_trait()); - initialization.extend(quote! { - let split_peripheral_driver = ::rumcake::drivers::SerialSplitDriver { serial: <#kb_name as SerialDriverSettings>::setup_serial() }; - }); - }; - } - _ => (), - }, - } + config: &StorageSettings, + uses_bluetooth: bool, +) -> bool { + let buffer_size = if let Some(lit) = &config.buffer_size { + lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided buffer size could not be parsed as a usize value." + ) + }) + } else { + 1024 + }; - match role { - SplitSettings::Central(config) => initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown split central device driver."); - }), - SplitSettings::Peripheral(config) => initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown split peripheral device driver."); - }), - } -} + match config.driver.value().as_str() { + "internal" => { + if cfg!(feature = "nrf") && uses_bluetooth { + // TODO: Fix storage on nrf-ble targets + outer.extend(quote! { + use ::rumcake::storage::FlashStorage; + static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::platform::nrf_softdevice::Flash, #kb_name> = ::rumcake::storage::StorageService::new(); + impl ::rumcake::storage::StorageDevice for #kb_name { + type FlashStorageType = ::rumcake::hw::platform::nrf_softdevice::Flash; -fn setup_underglow_driver( - initialization: &mut TokenStream, - traits: &mut HashMap, - kb_name: &Ident, - config: &LightingSettings, -) { - match config.driver.as_str() { - "ws2812_bitbang" => { - return { - traits.insert( - config.driver.clone(), - crate::drivers::ws2812::bitbang::driver_trait(), - ); + fn get_storage_buffer() -> &'static mut [u8] { + static mut STORAGE_BUFFER: [u8; #buffer_size] = [0; #buffer_size]; + unsafe { &mut STORAGE_BUFFER } + } + + fn get_storage_service( + ) -> &'static rumcake::storage::StorageService<'static, Self::FlashStorageType, Self> + where + [(); Self::FlashStorageType::ERASE_SIZE]:, + Self: Sized, + { + &DATABASE + } + } + }); initialization.extend(quote! { - let underglow_driver = ::rumcake::drivers::ws2812_bitbang::setup_driver(<#kb_name as WS2812BitbangDriverSettings>::ws2812_pin()); + use ::rumcake::storage::FlashStorage; + let flash = ::rumcake::hw::platform::setup_internal_softdevice_flash(sd); + let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize }; + let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize }; + static mut READ_BUF: [u8; ::rumcake::hw::platform::nrf_softdevice::Flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::nrf_softdevice::Flash::ERASE_SIZE]; + static mut OP_BUF: [u8; ::rumcake::hw::platform::nrf_softdevice::Flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::nrf_softdevice::Flash::ERASE_SIZE]; + unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; } }); + + return false; } - } - _ => (), - } - initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown underglow driver."); - }); -} + if cfg!(any(feature = "stm32", feature = "nrf")) { + outer.extend(quote! { + use ::rumcake::storage::FlashStorage; + static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::platform::Flash, #kb_name> = ::rumcake::storage::StorageService::new(); + impl ::rumcake::storage::StorageDevice for #kb_name { + type FlashStorageType = ::rumcake::hw::platform::Flash; -enum BacklightType { - SimpleBacklight, - SimpleBacklightMatrix, - RGBBacklightMatrix, -} + fn get_storage_buffer() -> &'static mut [u8] { + static mut STORAGE_BUFFER: [u8; #buffer_size] = [0; #buffer_size]; + unsafe { &mut STORAGE_BUFFER } + } -fn setup_backlight_driver( - initialization: &mut TokenStream, - traits: &mut HashMap, - kb_name: &Ident, - backlight_type: BacklightType, - config: &LightingSettings, -) { - match config.driver.as_str() { - "is31fl3731" => { - return { - traits.insert( - config.driver.clone(), - crate::drivers::is31fl3731::driver_trait(), - ); - initialization.extend(quote! { - let backlight_driver = ::rumcake::drivers::is31fl3731::setup_driver( - <#kb_name as IS31FL3731DriverSettings>::setup_i2c(), - <#kb_name as IS31FL3731DriverSettings>::LED_DRIVER_ADDR, - <#kb_name as ::rumcake::backlight::BacklightMatrixDevice>::LIGHTING_COLS as u8, - <#kb_name as ::rumcake::backlight::BacklightMatrixDevice>::LIGHTING_ROWS as u8, - <#kb_name as ::rumcake::drivers::is31fl3731::backlight::IS31FL3731BacklightDriver>::get_led_from_matrix_coordinates - ).await; + fn get_storage_service( + ) -> &'static rumcake::storage::StorageService<'static, Self::FlashStorageType, Self> + where + [(); Self::FlashStorageType::ERASE_SIZE]:, + Self: Sized, + { + &DATABASE + } + } }); - } - } - "ws2812_bitbang" => { - return { - traits.insert( - config.driver.clone(), - crate::drivers::ws2812::bitbang::driver_trait(), - ); initialization.extend(quote! { - let backlight_driver = ::rumcake::drivers::ws2812_bitbang::setup_driver(<#kb_name as WS2812BitbangDriverSettings>::ws2812_pin()); + use ::rumcake::storage::FlashStorage; + let flash = ::rumcake::hw::platform::setup_internal_flash(); + let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize }; + let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize }; + static mut READ_BUF: [u8; ::rumcake::hw::platform::Flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::Flash::ERASE_SIZE]; + static mut OP_BUF: [u8; ::rumcake::hw::platform::Flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::Flash::ERASE_SIZE]; + unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; } }); + + return false; } - } - _ => (), - } - match backlight_type { - BacklightType::SimpleBacklight => initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown simple backlight driver."); - }), - BacklightType::SimpleBacklightMatrix => initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown simple backlight matrix driver."); - }), - BacklightType::RGBBacklightMatrix => initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown RGB backlight matrix driver."); - }), - } -} + if cfg!(feature = "rp") { + if config.flash_size.is_none() { + emit_error!( + config.driver, + "You must specify a non-zero size for your flash chip." + ); -fn setup_display_driver( - initialization: &mut TokenStream, - traits: &mut HashMap, - kb_name: &Ident, - config: &DisplaySettings, -) { - match config.driver.as_str() { - "ssd1306" => { - return { - traits.insert( - config.driver.clone(), - crate::drivers::ssd1306::driver_trait(), - ); - initialization.extend(quote! { - let display_driver = ::rumcake::drivers::ssd1306::setup_driver(<#kb_name as Ssd1306I2cDriverSettings>::setup_i2c(), <#kb_name as Ssd1306I2cDriverSettings>::SIZE, <#kb_name as Ssd1306I2cDriverSettings>::ROTATION); + return true; + } + + if config.dma.is_none() { + emit_error!(config.driver, "You must specify a `dma` channel."); + + return true; + } + + let lit = config.flash_size.as_ref().unwrap(); + let dma = config.dma.as_ref().unwrap(); + + let size = lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided flash size could not be parsed as a usize value." + ); }); - } - } - _ => (), - } - initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown display driver."); - }); -} + if size == 0 { + emit_error!( + config.driver, + "You must specify a non-zero size for your flash chip." + ); + return true; + } -fn setup_storage_driver( - initialization: &mut TokenStream, - traits: &mut HashMap, - kb_name: &Ident, - config: &StorageSettings, - uses_bluetooth: bool, -) { - match config.driver.as_str() { - "internal" => { - return if cfg!(feature = "nrf") && uses_bluetooth { - // TODO: Fix storage on nrf-ble targets - initialization.extend(quote! { + outer.extend(quote! { use ::rumcake::storage::FlashStorage; - let flash = ::rumcake::hw::mcu::setup_internal_softdevice_flash(sd); - let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize }; - let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize }; - static mut READ_BUF: [u8; ::rumcake::hw::mcu::nrf_softdevice::Flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::nrf_softdevice::Flash::ERASE_SIZE]; - static mut OP_BUF: [u8; ::rumcake::hw::mcu::nrf_softdevice::Flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::nrf_softdevice::Flash::ERASE_SIZE]; - static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::nrf_softdevice::Flash> = ::rumcake::storage::StorageService::new(); - unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; } - }) - } else if cfg!(any(feature = "stm32", feature = "nrf")) { + static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::platform::Flash<#size>, #kb_name> = ::rumcake::storage::StorageService::new(); + impl ::rumcake::storage::StorageDevice for #kb_name { + type FlashStorageType = ::rumcake::hw::platform::Flash<'static, #size>; + + fn get_storage_buffer() -> &'static mut [u8] { + static mut STORAGE_BUFFER: [u8; #buffer_size] = [0; #buffer_size]; + unsafe { &mut STORAGE_BUFFER } + } + + fn get_storage_service( + ) -> &'static rumcake::storage::StorageService<'static, Self::FlashStorageType, Self> + where + [(); Self::FlashStorageType::ERASE_SIZE]:, + Self: Sized, + { + &DATABASE + } + } + }); initialization.extend(quote! { - use ::rumcake::storage::FlashStorage; - let flash = ::rumcake::hw::mcu::setup_internal_flash(); + let flash = ::rumcake::hw::platform::setup_internal_flash::<#size>(unsafe { ::rumcake::hw::platform::embassy_rp::peripherals::#dma::steal() }); let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize }; let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize }; - static mut READ_BUF: [u8; ::rumcake::hw::mcu::Flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::Flash::ERASE_SIZE]; - static mut OP_BUF: [u8; ::rumcake::hw::mcu::Flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::Flash::ERASE_SIZE]; - static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::Flash> = ::rumcake::storage::StorageService::new(); + static mut READ_BUF: [u8; ::rumcake::hw::platform::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::embassy_rp::flash::ERASE_SIZE]; + static mut OP_BUF: [u8; ::rumcake::hw::platform::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::platform::embassy_rp::flash::ERASE_SIZE]; unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; } - }) - } else if cfg!(feature = "rp") { - #[cfg(feature = "rp")] - traits.insert(config.driver.clone(), crate::hw::internal_storage_trait()); - if config.flash_size == 0 { - initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("You must specify a non-zero size for your flash chip."); - }); - } else { - let size = config.flash_size; - initialization.extend(quote! { - use ::rumcake::storage::FlashStorage; - let flash = ::rumcake::hw::mcu::setup_internal_flash::<#size>(<#kb_name as RP2040FlashSettings>::setup_dma_channel()); - let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize }; - let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize }; - static mut READ_BUF: [u8; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE]; - static mut OP_BUF: [u8; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE]; - static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::Flash<#size>> = ::rumcake::storage::StorageService::new(); - unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; } - }) - } - } else { - initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Internal storage driver is not available for your platform."); }); - }; + + return false; + } + + emit_error!( + config.driver, + "Internal storage driver is not available for your platform." + ); + + return true; } _ => (), }; - initialization.extend(quote_spanned! { - config.driver.span() => compile_error!("Unknown storage driver."); - }); + emit_error!(config.driver, "Unknown storage driver."); + + true } pub(crate) fn keyboard_main( @@ -331,21 +245,24 @@ pub(crate) fn keyboard_main( ) -> TokenStream { let mut initialization = TokenStream::new(); let mut spawning = TokenStream::new(); - let mut traits: HashMap = HashMap::new(); + let mut outer = TokenStream::new(); + let mut error = false; let uses_bluetooth = keyboard.bluetooth - || keyboard - .split_peripheral - .as_ref() - .is_some_and(|args| args.driver == "ble") - || keyboard - .split_central - .as_ref() - .is_some_and(|args| args.driver == "ble"); + || keyboard.split_peripheral.as_ref().is_some_and(|args| { + args.driver_type + .as_ref() + .map_or(false, |d| d.value() == "nrf-ble") + }) + || keyboard.split_central.as_ref().is_some_and(|args| { + args.driver_type + .as_ref() + .map_or(false, |d| d.value() == "nrf-ble") + }); // Setup microcontroller initialization.extend(quote! { - ::rumcake::hw::mcu::initialize_rcc(); + ::rumcake::hw::platform::initialize_rcc(); }); if cfg!(feature = "nrf") { @@ -356,7 +273,7 @@ pub(crate) fn keyboard_main( if uses_bluetooth { initialization.extend(quote! { - let sd = ::rumcake::hw::mcu::setup_softdevice::<#kb_name>(); + let sd = ::rumcake::hw::platform::setup_softdevice::<#kb_name>(); }); spawning.extend(quote! { let sd = &*sd; @@ -377,13 +294,12 @@ pub(crate) fn keyboard_main( // Flash setup if let Some(ref driver) = keyboard.storage { if !cfg!(feature = "storage") { - initialization.extend(quote_spanned! { - driver.driver.span() => compile_error!("Storage driver was specified, but rumcake's `storage` feature flag is not enabled. Please enable the feature."); - }); + emit_error!(driver.driver, "Storage driver was specified, but rumcake's `storage` feature flag is not enabled. Please enable the feature."); + error = true; } else { - setup_storage_driver( + error = setup_storage_driver( &mut initialization, - &mut traits, + &mut outer, &kb_name, driver, uses_bluetooth, @@ -392,6 +308,9 @@ pub(crate) fn keyboard_main( }; if keyboard.bluetooth || keyboard.usb { + outer.extend(quote! { + impl ::rumcake::hw::HIDDevice for #kb_name {} + }); spawning.extend(quote! { spawner.spawn(::rumcake::layout_collect!(#kb_name)).unwrap(); }); @@ -414,7 +333,7 @@ pub(crate) fn keyboard_main( // USB Configuration if keyboard.usb { initialization.extend(quote! { - let mut builder = ::rumcake::hw::mcu::setup_usb_driver::<#kb_name>(); + let mut builder = ::rumcake::hw::platform::setup_usb_driver::<#kb_name>(); // HID Class setup let kb_class = ::rumcake::usb::setup_usb_hid_nkro_writer(&mut builder); @@ -427,7 +346,7 @@ pub(crate) fn keyboard_main( spawner.spawn(::rumcake::start_usb!(usb)).unwrap(); // HID Keyboard Report sending - spawner.spawn(::rumcake::usb_hid_kb_write_task!(kb_class)).unwrap(); + spawner.spawn(::rumcake::usb_hid_kb_write_task!(#kb_name, kb_class)).unwrap(); }); if cfg!(feature = "media-keycodes") { @@ -437,44 +356,42 @@ pub(crate) fn keyboard_main( }); spawning.extend(quote! { // HID Consumer Report sending - spawner.spawn(::rumcake::usb_hid_consumer_write_task!(consumer_class)).unwrap(); + spawner.spawn(::rumcake::usb_hid_consumer_write_task!(#kb_name, consumer_class)).unwrap(); }); } } if keyboard.usb && (keyboard.via.is_some() || keyboard.vial.is_some()) { initialization.extend(quote! { + static VIA_COMMAND_HANDLER: ::rumcake::usb::ViaCommandHandler<#kb_name> = ::rumcake::usb::ViaCommandHandler::new(); // Via HID setup let (via_reader, via_writer) = - ::rumcake::usb::setup_usb_via_hid_reader_writer(&mut builder).split(); + ::rumcake::usb::setup_usb_via_hid_reader_writer(&VIA_COMMAND_HANDLER, &mut builder).split(); }); spawning.extend(quote! { // HID raw report (for VIA) reading and writing spawner - .spawn(::rumcake::usb_hid_via_read_task!(via_reader)) + .spawn(::rumcake::usb_hid_via_read_task!(&VIA_COMMAND_HANDLER, via_reader)) .unwrap(); }); spawning.extend(quote! { - spawner.spawn(::rumcake::usb_hid_via_write_task!(via_writer)).unwrap(); + spawner.spawn(::rumcake::usb_hid_via_write_task!(#kb_name, via_writer)).unwrap(); }); } if keyboard.via.is_some() && keyboard.vial.is_some() { - initialization.extend(quote_spanned! { - str.span() => compile_error!("Via and Vial are both specified. Please only choose one."); - }); + emit_error!( + str, + "Via and Vial are both specified. Please only choose one." + ); + error = true; } else if let Some(args) = keyboard.via { - let args = args.unwrap_or_default(); - - if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.use_storage.span() => compile_error!("Via uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your Via settings."); - }); - } else if args.use_storage { + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "Via uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your Via settings."); + error = true; + } else if args.use_storage.map_or(false, |b| *b) { spawning.extend(quote! { - spawner - .spawn(::rumcake::via_storage_task!(#kb_name, &DATABASE)) - .unwrap(); + ::rumcake::via::initialize_via_data(#kb_name).await; }); } @@ -484,17 +401,12 @@ pub(crate) fn keyboard_main( .unwrap(); }); } else if let Some(args) = keyboard.vial { - let args = args.unwrap_or_default(); - - if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.use_storage.span() => compile_error!("Vial uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your Vial settings."); - }); - } else if args.use_storage { + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "Vial uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your Vial settings."); + error = true; + } else if args.use_storage.map_or(false, |b| *b) { spawning.extend(quote! { - spawner - .spawn(::rumcake::vial_storage_task!(#kb_name, &DATABASE)) - .unwrap(); + ::rumcake::vial::initialize_vial_data(#kb_name).await; }); } @@ -507,181 +419,220 @@ pub(crate) fn keyboard_main( // Split keyboard setup if keyboard.split_peripheral.is_some() && keyboard.split_central.is_some() { - initialization.extend(quote_spanned! { - str.span() => compile_error!("A device can not be a central device and a peripheral at the same time. Please only choose one."); - }); + emit_error!(str, "A device can not be a central device and a peripheral at the same time. Please only choose one."); + error = true; } else if keyboard.split_peripheral.is_some() && keyboard.no_matrix { - initialization.extend(quote_spanned! { - str.span() => compile_error!("A split peripheral must have a matrix. Please remove `no_matrix` or `split_peripheral`."); - }); + emit_error!(str, "A split peripheral must have a matrix. Please remove `no_matrix` or `split_peripheral`."); + error = true; } else if let Some(args) = keyboard.split_peripheral { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify a peripheral device driver."); - }) - } else { - setup_split_driver( - &mut initialization, - &mut spawning, - &mut traits, - &kb_name, - SplitSettings::Peripheral(&args), - ); - spawning.extend(quote! { - spawner.spawn(::rumcake::peripheral_task!(split_peripheral_driver)).unwrap(); - }); + let setup_fn = args.driver_setup_fn; + let driver_type = args + .driver_type + .as_ref() + .map_or(String::from("standard"), |v| v.value()); + match driver_type.as_str() { + "standard" => { + initialization.extend(quote! { + let split_peripheral_driver = #setup_fn().await; + }); + } + "nrf-ble" => { + initialization.extend(quote! { + let peripheral_server = ::rumcake::drivers::nrf_ble::peripheral::PeripheralDeviceServer::new(sd).unwrap(); + let (split_peripheral_driver, central_address) = #setup_fn().await; + }); + spawning.extend(quote! { + spawner.spawn(::rumcake::nrf_ble_peripheral_task!(central_address, sd, peripheral_server)).unwrap(); + }); + } + _ => { + emit_error!(args.driver_type, "Unknown split peripheral driver type."); + error = true; + } } + spawning.extend(quote! { + spawner.spawn(::rumcake::peripheral_task!(#kb_name, split_peripheral_driver)).unwrap(); + }); } if let Some(args) = keyboard.split_central { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify a central device driver."); - }) - } else { - setup_split_driver( - &mut initialization, - &mut spawning, - &mut traits, - &kb_name, - SplitSettings::Central(&args), - ); - spawning.extend(quote! { - spawner.spawn(::rumcake::central_task!(split_central_driver)).unwrap(); - }); + let setup_fn = args.driver_setup_fn; + let driver_type = args + .driver_type + .as_ref() + .map_or(String::from("standard"), |v| v.value()); + match driver_type.as_str() { + "standard" => { + initialization.extend(quote! { + let split_central_driver = #setup_fn().await; + }); + } + "nrf-ble" => { + initialization.extend(quote! { + let (split_central_driver, peripheral_addresses) = #setup_fn().await; + }); + spawning.extend(quote! { + spawner.spawn(::rumcake::nrf_ble_central_task!(peripheral_addresses, sd)).unwrap(); + }); + } + _ => { + emit_error!(args.driver_type, "Unknown split peripheral driver type."); + error = true; + } } + spawning.extend(quote! { + spawner.spawn(::rumcake::central_task!(#kb_name, split_central_driver)).unwrap(); + }); } // Underglow setup if let Some(args) = keyboard.underglow { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify an underglow driver."); - }) - } else if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("Underglow uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your underglow settings."); - }); + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "Underglow uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your underglow settings."); + error = true; } else { - setup_underglow_driver(&mut initialization, &mut traits, &kb_name, &args); - if args.use_storage { + let setup_fn = args.driver_setup_fn; + initialization.extend(quote! { + let underglow_driver = #setup_fn().await; + }); + let id = args.id; + initialization.extend(quote! { + let underglow_animator = ::rumcake::lighting::underglow::UnderglowAnimator::<#id, _>::new(Default::default(), underglow_driver); + }); + + if args.use_storage.map_or(false, |b| *b) { + initialization.extend(quote! { + let underglow_animator_storage = underglow_animator.create_storage_instance(); + }); spawning.extend(quote! { - spawner.spawn(::rumcake::underglow_storage_task!(#kb_name, &DATABASE)).unwrap(); + ::rumcake::lighting::initialize_lighting_data(&underglow_animator_storage, &DATABASE).await; + spawner.spawn(::rumcake::lighting_storage_task!(underglow_animator_storage, &DATABASE)).unwrap(); }); } spawning.extend(quote! { - spawner.spawn(::rumcake::underglow_task!(#kb_name, underglow_driver)).unwrap(); + spawner.spawn(::rumcake::lighting_task!(underglow_animator, None)).unwrap(); }); } } // Backlight setup if let Some(args) = keyboard.simple_backlight { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify a simple backlight driver."); - }) - } else if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("Simple backlighting uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your simple backlight settings."); - }); + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "Simple backlighting uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your simple backlight settings."); + error = true; } else { - setup_backlight_driver( - &mut initialization, - &mut traits, - &kb_name, - BacklightType::SimpleBacklight, - &args, - ); - if args.use_storage { + let setup_fn = args.driver_setup_fn; + initialization.extend(quote! { + let simple_backlight_driver = #setup_fn().await; + }); + let id = args.id; + initialization.extend(quote! { + let simple_backlight_animator = ::rumcake::lighting::simple_backlight::SimpleBacklightAnimator::<#id, _>::new(Default::default(), simple_backlight_driver); + }); + if args.use_storage.map_or(false, |b| *b) { + initialization.extend(quote! { + let simple_backlight_animator_storage = simple_backlight_animator.create_storage_instance(); + }); spawning.extend(quote! { - spawner.spawn(::rumcake::simple_backlight_storage_task!(#kb_name, &DATABASE)).unwrap(); + ::rumcake::lighting::initialize_lighting_data(&simple_backlight_animator_storage, &DATABASE).await; + spawner.spawn(::rumcake::lighting_storage_task!(simple_backlight_animator_storage, &DATABASE)).unwrap(); }); } spawning.extend(quote! { - spawner.spawn(::rumcake::simple_backlight_task!(#kb_name, backlight_driver)).unwrap(); + spawner.spawn(::rumcake::lighting_task!(simple_backlight_animator, None)).unwrap(); }); } } if let Some(args) = keyboard.simple_backlight_matrix { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify a simple backlight matrix driver."); - }) - } else if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("Simple backlight matrix uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your simple backlight matrix settings."); - }); + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "Simple backlight matrix uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your simple backlight matrix settings."); + error = true; } else { - setup_backlight_driver( - &mut initialization, - &mut traits, - &kb_name, - BacklightType::SimpleBacklightMatrix, - &args, - ); - if args.use_storage { + let setup_fn = args.driver_setup_fn; + initialization.extend(quote! { + let simple_backlight_matrix_driver = #setup_fn().await; + }); + let id = args.id; + initialization.extend(quote! { + let simple_backlight_matrix_animator = ::rumcake::lighting::simple_backlight_matrix::SimpleBacklightMatrixAnimator::<#id, _>::new(Default::default(), simple_backlight_matrix_driver); + }); + if args.use_storage.map_or(false, |b| *b) { + initialization.extend(quote! { + let simple_backlight_matrix_animator_storage = simple_backlight_matrix_animator.create_storage_instance(); + }); spawning.extend(quote! { - spawner.spawn(::rumcake::simple_backlight_matrix_storage_task!(#kb_name, &DATABASE)).unwrap(); + ::rumcake::lighting::initialize_lighting_data(&simple_backlight_matrix_animator_storage, &DATABASE).await; + spawner.spawn(::rumcake::lighting_storage_task!(simple_backlight_matrix_animator_storage, &DATABASE)).unwrap(); }); } spawning.extend(quote! { - spawner.spawn(::rumcake::simple_backlight_matrix_task!(#kb_name, backlight_driver)).unwrap(); + spawner.spawn(::rumcake::lighting_task!(simple_backlight_matrix_animator, None)).unwrap(); }); } } if let Some(args) = keyboard.rgb_backlight_matrix { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify an RGB backlight matrix driver."); - }) - } else if args.use_storage && keyboard.storage.is_none() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("RGB backlight matrix uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your RGB backlight matrix settings."); - }); + if args.use_storage.map_or(false, |b| *b) && keyboard.storage.is_none() { + emit_error!(args.use_storage.unwrap().span(), "RGB backlight matrix uses storage but no `storage` driver was specified. Either specify a `storage` driver, or remove `use_storage` from your RGB backlight matrix settings."); + error = true; } else { - setup_backlight_driver( - &mut initialization, - &mut traits, - &kb_name, - BacklightType::RGBBacklightMatrix, - &args, - ); - if args.use_storage { + let setup_fn = args.driver_setup_fn; + initialization.extend(quote! { + let rgb_backlight_matrix_driver = #setup_fn().await; + }); + let id = args.id; + initialization.extend(quote! { + let rgb_backlight_matrix_animator = ::rumcake::lighting::rgb_backlight_matrix::RGBBacklightMatrixAnimator::<#id, _>::new(Default::default(), rgb_backlight_matrix_driver); + }); + if args.use_storage.map_or(false, |b| *b) { + initialization.extend(quote! { + let rgb_backlight_matrix_animator_storage = rgb_backlight_matrix_animator.create_storage_instance(); + }); spawning.extend(quote! { - spawner.spawn(::rumcake::rgb_backlight_matrix_storage_task!(#kb_name, &DATABASE)).unwrap(); + ::rumcake::lighting::initialize_lighting_data(&rgb_backlight_matrix_animator_storage, &DATABASE).await; + spawner.spawn(::rumcake::lighting_storage_task!(rgb_backlight_matrix_animator_storage, &DATABASE)).unwrap(); }); } spawning.extend(quote! { - spawner.spawn(::rumcake::rgb_backlight_matrix_task!(#kb_name, backlight_driver)).unwrap(); + spawner.spawn(::rumcake::lighting_task!(rgb_backlight_matrix_animator, None)).unwrap(); }); } } // Display setup if let Some(args) = keyboard.display { - if args.driver.is_empty() { - initialization.extend(quote_spanned! { - args.driver.span() => compile_error!("You must specify a display driver."); - }) - } else { - setup_display_driver(&mut initialization, &mut traits, &kb_name, &args); - spawning.extend(quote! { - spawner.spawn(::rumcake::display_task!(#kb_name, display_driver)).unwrap(); - }); - } + let setup_fn = args.driver_setup_fn; + initialization.extend(quote! { + let display_driver = #setup_fn().await; + }); + spawning.extend(quote! { + spawner.spawn(::rumcake::display_task!(#kb_name, display_driver)).unwrap(); + }); } if let Some(arg) = keyboard.bootloader_double_tap_reset { - let timeout = arg.unwrap_or(200); + let timeout: u64 = match arg { + Override::Inherit => 200, + Override::Explicit(lit) => { + let value = lit.base10_parse::().unwrap_or_else(|_| { + abort!( + lit, + "The provided timeout value could not be parsed as a u64 value." + ); + }); - if timeout == 0 { - initialization.extend(quote! { - compile_error!("The timeout for double tapping the reset button should be > 0"); - }) - } + if value == 0 { + emit_error!( + lit, + "The timeout for double tapping the reset button should be > 0" + ); + error = true; + } + + value + } + }; spawning.extend(quote! { unsafe { @@ -690,101 +641,39 @@ pub(crate) fn keyboard_main( }); } - - let final_traits = traits.values(); - - quote! { - #[::embassy_executor::main] - async fn main(spawner: ::embassy_executor::Spawner) { - #initialization - #spawning + if error { + quote! { + #str } + } else { + quote! { + #[::embassy_executor::main] + async fn main(spawner: ::embassy_executor::Spawner) { + #initialization + #spawning + } - #(#final_traits)* - - #str - } -} - -#[derive(Debug)] -/// This is the exact same as [`Option`], but has a different [`syn::parse::Parse`] implementation, -/// where "No" parses to `None`, and anything else that parses as `T` corresponds `Some(T)` -pub(crate) enum OptionalItem { - None, - Some(T), -} - -custom_keyword!(No); + #outer -impl Parse for OptionalItem { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(No) { - input.parse::().map(|_| OptionalItem::None) - } else { - input.parse().map(OptionalItem::Some) + #str } } } -impl ToTokens for OptionalItem { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - OptionalItem::None => quote! { None }.to_tokens(tokens), - OptionalItem::Some(item) => quote! { Some(#item) }.to_tokens(tokens), - } - } -} - -#[derive(Debug)] -pub struct StandardMatrixDefinition { - pub row_brace: syn::token::Brace, - pub rows: Vec, - pub col_brace: syn::token::Brace, - pub cols: Vec, -} - -impl syn::parse::Parse for StandardMatrixDefinition { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let row_content; - let row_brace = braced!(row_content in input); - let mut rows = Vec::new(); - while let Ok(t) = row_content.parse() { - rows.push(t) - } - if !row_content.is_empty() { - return Err(syn::Error::new( - row_content.span(), - "Encountered an invalid token.", - )); - } - - let col_content; - let col_brace = braced!(col_content in input); - let mut cols = Vec::new(); - while let Ok(t) = col_content.parse() { - cols.push(t) - } - if !col_content.is_empty() { - return Err(syn::Error::new( - row_content.span(), - "Encountered an invalid token.", - )); - } - - Ok(Self { - row_brace, - rows, - col_brace, - cols, - }) +crate::parse_as_custom_fields! { + pub struct StandardMatrixDefinitionBuilder for StandardMatrixDefinition { + pub rows: Row, + pub cols: Row, } } pub fn build_standard_matrix(input: StandardMatrixDefinition) -> TokenStream { - let StandardMatrixDefinition { rows, cols, .. } = input; - let row_count = rows.len(); - let col_count = cols.len(); + let StandardMatrixDefinition { rows, cols } = input; + let row_count = rows.items.len(); + let col_count = cols.items.len(); + + let rows = rows.items.iter(); + let cols = cols.items.iter(); let hal_name: PathSegment = syn::parse_str(crate::hw::HAL_CRATE).unwrap(); @@ -796,8 +685,8 @@ pub fn build_standard_matrix(input: StandardMatrixDefinition) -> TokenStream { static MATRIX: ::rumcake::once_cell::sync::OnceCell< ::rumcake::keyboard::PollableMatrix< ::rumcake::keyboard::PollableStandardMatrix< - ::rumcake::hw::mcu::#hal_name::gpio::Input<'static>, - ::rumcake::hw::mcu::#hal_name::gpio::Output<'static>, + ::rumcake::hw::platform::#hal_name::gpio::Input<'static>, + ::rumcake::hw::platform::#hal_name::gpio::Output<'static>, #col_count, #row_count > @@ -808,12 +697,12 @@ pub fn build_standard_matrix(input: StandardMatrixDefinition) -> TokenStream { ::rumcake::keyboard::setup_standard_keyboard_matrix( [ #( - ::rumcake::hw::mcu::input_pin!(#cols) + ::rumcake::hw::platform::input_pin!(#cols) ),* ], [ #( - ::rumcake::hw::mcu::output_pin!(#rows) + ::rumcake::hw::platform::output_pin!(#rows) ),* ], Self::DEBOUNCE_MS @@ -826,10 +715,10 @@ pub fn build_standard_matrix(input: StandardMatrixDefinition) -> TokenStream { pub fn build_direct_pin_matrix(input: MatrixLike>) -> TokenStream { let values = input.rows.iter().map(|row| { - let items = row.cols.iter().map(|item| match item { + let items = row.items.iter().map(|item| match item { OptionalItem::None => quote! { None }, OptionalItem::Some(pin_ident) => { - quote! { Some(::rumcake::hw::mcu::input_pin!(#pin_ident)) } + quote! { Some(::rumcake::hw::platform::input_pin!(#pin_ident)) } } }); quote! { #(#items),* } @@ -840,7 +729,7 @@ pub fn build_direct_pin_matrix(input: MatrixLike>) -> TokenS .rows .first() .expect_or_abort("At least one row is required.") - .cols + .items .len(); let hal_name: PathSegment = syn::parse_str(crate::hw::HAL_CRATE).unwrap(); @@ -853,7 +742,7 @@ pub fn build_direct_pin_matrix(input: MatrixLike>) -> TokenS static MATRIX: ::rumcake::once_cell::sync::OnceCell< ::rumcake::keyboard::PollableMatrix< ::rumcake::keyboard::PollableDirectPinMatrix< - ::rumcake::hw::mcu::#hal_name::gpio::Input<'static>, + ::rumcake::hw::platform::#hal_name::gpio::Input<'static>, #col_count, #row_count > @@ -873,41 +762,24 @@ pub fn build_direct_pin_matrix(input: MatrixLike>) -> TokenS } } -#[derive(Debug)] -pub struct AnalogMatrixDefinition { - pub pos_to_ch_brace: syn::token::Brace, - pub pos_to_ch: MatrixLike>, - pub ranges_brace: syn::token::Brace, - pub ranges: MatrixLike>, -} - -impl syn::parse::Parse for AnalogMatrixDefinition { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let pos_to_ch_content; - let pos_to_ch_brace = braced!(pos_to_ch_content in input); - let ranges_content; - let ranges_brace = braced!(ranges_content in input); - - Ok(Self { - pos_to_ch_brace, - pos_to_ch: pos_to_ch_content.parse()?, - ranges_brace, - ranges: ranges_content.parse()?, - }) +crate::parse_as_custom_fields! { + pub struct AnalogMatrixDefinitionBuilder for AnalogMatrixDefinition { + pub channels: Layer>, + pub ranges: Layer>, } } pub fn build_analog_matrix(input: AnalogMatrixDefinition) -> TokenStream { - let pos_to_ch = input.pos_to_ch.rows.iter().map(|row| { - let items = row.cols.iter().map(|item| match item { + let pos_to_ch = input.channels.layer.rows.iter().map(|row| { + let items = row.items.iter().map(|item| match item { OptionalItem::None => quote! { (0, 0) }, OptionalItem::Some(tuple) => quote! { #tuple }, }); quote! { #(#items),* } }); - let ranges = input.ranges.rows.iter().map(|row| { - let items = row.cols.iter().map(|item| match item { + let ranges = input.ranges.layer.rows.iter().map(|row| { + let items = row.items.iter().map(|item| match item { OptionalItem::None => quote! { 0..0 }, OptionalItem::Some(range) => quote! { #range }, }); @@ -916,11 +788,12 @@ pub fn build_analog_matrix(input: AnalogMatrixDefinition) -> TokenStream { let row_count = pos_to_ch.len(); let col_count = input - .pos_to_ch + .channels + .layer .rows .first() .expect_or_abort("At least one row must be specified") - .cols + .items .len(); quote! { @@ -954,93 +827,6 @@ pub fn build_analog_matrix(input: AnalogMatrixDefinition) -> TokenStream { } } -#[derive(Debug)] -pub struct LayoutLike { - pub layers: Vec>, -} - -impl syn::parse::Parse for LayoutLike { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut layers = Vec::new(); - while let Ok(t) = input.parse() { - layers.push(t) - } - if !input.is_empty() { - return Err(syn::Error::new( - input.span(), - "Encountered tokens that don't look like a layer definition.", - )); - } - - Ok(Self { layers }) - } -} - -#[derive(Debug)] -pub struct Layer { - pub layer_brace: syn::token::Brace, - pub layer: MatrixLike, -} - -impl syn::parse::Parse for Layer { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - let layer_brace = braced!(content in input); - - Ok(Self { - layer_brace, - layer: content.parse()?, - }) - } -} - -#[derive(Debug)] -pub struct MatrixLike { - pub rows: Vec>, -} - -impl syn::parse::Parse for MatrixLike { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let mut rows = Vec::new(); - while let Ok(t) = input.parse() { - rows.push(t) - } - if !input.is_empty() { - return Err(syn::Error::new( - input.span(), - "Encountered tokens that don't look like a row definition.", - )); - } - - Ok(Self { rows }) - } -} - -#[derive(Debug)] -pub struct MatrixRow { - pub row_bracket: syn::token::Bracket, - pub cols: Vec, -} - -impl syn::parse::Parse for MatrixRow { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - let row_bracket = bracketed!(content in input); - let mut cols = Vec::new(); - while let Ok(t) = content.parse() { - cols.push(t) - } - if !content.is_empty() { - return Err(syn::Error::new( - input.span(), - "Encountered an invalid token.", - )); - } - - Ok(Self { row_bracket, cols }) - } -} - pub fn build_layout(raw: TokenStream, layers: LayoutLike) -> TokenStream { let rows = &layers .layers @@ -1055,7 +841,7 @@ pub fn build_layout(raw: TokenStream, layers: LayoutLike) -> TokenStr let layer_count = layers.layers.len(); let row_count = rows.len(); - let col_count = first_row.cols.len(); + let col_count = first_row.items.len(); quote! { const LAYOUT_COLS: usize = #col_count; @@ -1071,39 +857,35 @@ pub fn build_layout(raw: TokenStream, layers: LayoutLike) -> TokenStr fn get_layout( ) -> &'static ::rumcake::keyboard::Layout<{ Self::LAYOUT_COLS }, { Self::LAYOUT_ROWS }, { Self::LAYERS }> { use ::rumcake::keyberon; - static KEYBOARD_LAYOUT: ::rumcake::keyboard::Layout<#col_count, #row_count, #layer_count> = ::rumcake::keyboard::Layout::new(); - static mut LAYERS: ::rumcake::keyberon::layout::Layers<#col_count, #row_count, #layer_count, ::rumcake::keyboard::Keycode> = ::rumcake::keyberon::layout::layout! { #raw }; - KEYBOARD_LAYOUT.init(unsafe { &mut LAYERS }); - &KEYBOARD_LAYOUT + static KEYBOARD_LAYOUT: ::rumcake::once_cell::sync::OnceCell< + ::rumcake::keyboard::Layout<#col_count, #row_count, #layer_count>, + > = ::rumcake::once_cell::sync::OnceCell::new(); + const LAYERS: ::rumcake::keyberon::layout::Layers<#col_count, #row_count, #layer_count, ::rumcake::keyboard::Keycode> = ::rumcake::keyberon::layout::layout! { #raw }; + KEYBOARD_LAYOUT.get_or_init(|| { + static mut LAYERS: ::rumcake::keyberon::layout::Layers< + #col_count, + #row_count, + #layer_count, + ::rumcake::keyboard::Keycode, + > = ::rumcake::keyberon::layout::layout! { #raw }; + ::rumcake::keyboard::Layout::new(::rumcake::keyberon::layout::Layout::new( + unsafe { &mut LAYERS } + )) + }) } } } -pub struct RemapMacroInput { - pub original_matrix_brace: syn::token::Brace, - pub original_matrix: MatrixLike>, - pub remapped_matrix_brace: syn::token::Brace, - pub remapped_matrix: MatrixLike, -} - -impl Parse for RemapMacroInput { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let original_matrix_content; - let original_matrix_brace = braced!(original_matrix_content in input); - let remapped_matrix_content; - let remapped_matrix_brace = braced!(remapped_matrix_content in input); - Ok(RemapMacroInput { - original_matrix_brace, - original_matrix: original_matrix_content.parse()?, - remapped_matrix_brace, - remapped_matrix: remapped_matrix_content.parse()?, - }) +crate::parse_as_custom_fields! { + pub struct RemapMacroInputBuilder for RemapMacroInput { + pub original: Layer>, + pub remapped: Layer, } } pub fn remap_matrix(input: RemapMacroInput) -> TokenStream { - let old = input.original_matrix.rows.iter().map(|row| { - let items = row.cols.iter().map(|col| match col { + let old = input.original.layer.rows.iter().map(|row| { + let items = row.items.iter().map(|col| match col { OptionalItem::None => quote! { No }, OptionalItem::Some(ident) => quote! { $#ident }, }); @@ -1111,27 +893,70 @@ pub fn remap_matrix(input: RemapMacroInput) -> TokenStream { quote! { [ #(#items)* ] } }); let old2 = old.clone(); + let old3 = old.clone(); - let new = input.remapped_matrix.rows.iter().map(|row| { - let items = row.cols.iter().map(|col| quote! { $#col:tt }); + let new = input.remapped.layer.rows.iter().map(|row| { + let items = row.items.iter().map(|col| quote! { $#col:tt }); quote! { [ #(#items)* ] } }); let new2 = new.clone(); + let new3 = new.clone(); quote! { macro_rules! remap { - ($macro:ident! { $({ #(#new)* })* }) => { + ($macro:ident! { $({ #(#new2)* })* }) => { $macro! { $( { - #(#old)* + #(#old2)* } )* } }; - ($macro:ident! { #(#new2)* }) => { + ($macro:ident! { #(#new3)* }) => { + $macro! { + #(#old3)* + } + }; + ($macro:ident! { [ $field_name:ident: { #(#new)* } $(, $($rest:tt)*)? ] -> [$($processed:tt)*] }) => { + remap! { $macro! { + [ + $( + $($rest)* + )? + ] -> [ + $($processed)* + $field_name: { #(#old)* }, + ] + } + } + }; + ($macro:ident! { [ $field_name:ident: $($value:tt)* $(, $($rest:tt)*)? ] -> [$($processed:tt)*] }) => { + remap! { $macro! { + [ + $( + $($rest)* + )? + ] -> [ + $($processed)* + $field_name: $($value)*, + ] + } + } + }; + ($macro:ident! { [] -> [$($processed:tt)*] }) => { $macro! { - #(#old2)* + $($processed)* + } + }; + ($macro:ident! { $($all:tt)* }) => { + remap! { $macro! { + [ + $( + $all + )* + ] -> [] + } } }; } diff --git a/rumcake-macros/src/lib.rs b/rumcake-macros/src/lib.rs index 131b99e..1677ec4 100644 --- a/rumcake-macros/src/lib.rs +++ b/rumcake-macros/src/lib.rs @@ -144,6 +144,7 @@ pub fn derive_cycle(e: proc_macro::TokenStream) -> proc_macro::TokenStream { mod keyboard; #[proc_macro_attribute] +#[proc_macro_error] pub fn keyboard_main( args: proc_macro::TokenStream, str: proc_macro::TokenStream, @@ -167,21 +168,24 @@ pub fn build_standard_matrix(input: proc_macro::TokenStream) -> proc_macro::Toke } #[proc_macro] +#[proc_macro_error] pub fn build_direct_pin_matrix(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let matrix = parse_macro_input!(input as keyboard::MatrixLike>); + let matrix = parse_macro_input!(input as common::MatrixLike>); keyboard::build_direct_pin_matrix(matrix).into() } #[proc_macro] +#[proc_macro_error] pub fn build_analog_matrix(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let matrix = parse_macro_input!(input as keyboard::AnalogMatrixDefinition); keyboard::build_analog_matrix(matrix).into() } #[proc_macro] +#[proc_macro_error] pub fn build_layout(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let raw = input.clone(); - let layers = parse_macro_input!(input as keyboard::LayoutLike); + let layers = parse_macro_input!(input as common::LayoutLike); keyboard::build_layout(raw.into(), layers).into() } @@ -201,43 +205,48 @@ pub fn setup_backlight_matrix(input: proc_macro::TokenStream) -> proc_macro::Tok } #[proc_macro] -#[proc_macro_error] pub fn led_layout(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let matrix = - parse_macro_input!(input as keyboard::MatrixLike>); + let matrix = parse_macro_input!(input as common::MatrixLike>); backlight::led_layout(matrix).into() } #[proc_macro] pub fn led_flags(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let matrix = parse_macro_input!( - input as keyboard::MatrixLike> - ); + let matrix = + parse_macro_input!(input as common::MatrixLike>); backlight::led_flags(matrix).into() } mod drivers; #[proc_macro] -pub fn ws2812_bitbang_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let pin = parse_macro_input!(input as Ident); - drivers::ws2812::bitbang::pin(pin).into() +#[proc_macro_error] +pub fn setup_ws2812_bitbang(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let matrix = parse_macro_input!(input as drivers::ws2812::bitbang::WS2812BitbangArgs); + drivers::ws2812::bitbang::setup_ws2812_bitbang(matrix).into() } #[proc_macro] pub fn ws2812_get_led_from_matrix_coordinates( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let matrix = parse_macro_input!(input as keyboard::MatrixLike>); + let matrix = parse_macro_input!(input as common::MatrixLike>); drivers::ws2812::get_led_from_matrix_coordinates(matrix).into() } +#[proc_macro] +#[proc_macro_error] +pub fn setup_is31fl3731(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as drivers::is31fl3731::IS31FL3731Args); + drivers::is31fl3731::setup_is31fl3731(args).into() +} + #[proc_macro] pub fn is31fl3731_get_led_from_matrix_coordinates( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let matrix = parse_macro_input!(input as keyboard::MatrixLike>); - drivers::is31fl3731::get_led_from_matrix_coordinates(matrix).into() + let layout = parse_macro_input!(input as common::MatrixLike>); + drivers::is31fl3731::get_led_from_matrix_coordinates(layout).into() } #[proc_macro] @@ -245,183 +254,171 @@ pub fn is31fl3731_get_led_from_matrix_coordinates( pub fn is31fl3731_get_led_from_rgb_matrix_coordinates( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let layout = parse_macro_input!(input as keyboard::LayoutLike>); + let layout = parse_macro_input!(input as drivers::is31fl3731::IS31FL3731RgbMatrixLedArgs); drivers::is31fl3731::get_led_from_rgb_matrix_coordinates(layout).into() } +#[proc_macro] +#[proc_macro_error] +pub fn setup_ssd1306(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as drivers::ssd1306::Ssd1306Args); + drivers::ssd1306::setup_ssd1306(args).into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn setup_nrf_ble_split_central(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as drivers::nrf_ble::NrfBleCentralArgs); + drivers::nrf_ble::setup_nrf_ble_split_central(args).into() +} + +#[proc_macro] +#[proc_macro_error] +pub fn setup_nrf_ble_split_peripheral(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as drivers::nrf_ble::NrfBlePeripheralArgs); + drivers::nrf_ble::setup_nrf_ble_split_peripheral(args).into() +} + #[cfg_attr(feature = "stm32", path = "hw/stm32.rs")] #[cfg_attr(feature = "nrf", path = "hw/nrf.rs")] #[cfg_attr(feature = "rp", path = "hw/rp.rs")] mod hw; -pub(crate) mod common { - use proc_macro2::Ident; - use syn::parse::Parse; - use syn::{braced, custom_keyword, Token}; - - custom_keyword!(Multiplexer); - custom_keyword!(Direct); - custom_keyword!(pin); - custom_keyword!(select_pins); - - #[allow(dead_code)] - pub struct MultiplexerDefinition { - pub multiplexer_field_name: Multiplexer, - pub pin_brace_token: syn::token::Brace, - pub pin_field_name: pin, - pub pin_field_colon_token: Token![:], - pub pin: Ident, - pub select_pins_field_name: select_pins, - pub select_pins_field_colon_token: Token![:], - pub select_pins_brace_token: syn::token::Brace, - pub select_pins: Vec>, - } - - impl Parse for MultiplexerDefinition { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - let select_pins_content; - Ok(Self { - multiplexer_field_name: input.parse()?, - pin_brace_token: braced!(content in input), - pin_field_name: content.parse()?, - pin_field_colon_token: content.parse()?, - pin: content.parse()?, - select_pins_field_name: content.parse()?, - select_pins_field_colon_token: content.parse()?, - select_pins_brace_token: braced!(select_pins_content in content), - select_pins: { - let mut pins = Vec::new(); - while let Ok(t) = select_pins_content.parse() { - pins.push(t) - } - if !select_pins_content.is_empty() { - return Err(syn::Error::new( - select_pins_content.span(), - "Encountered an invalid token.", - )); - } - pins - }, - }) - } - } +#[cfg(feature = "stm32")] +#[proc_macro] +pub fn stm32_input_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + hw::input_pin(ident).into() +} - #[allow(dead_code)] - pub struct DirectPinDefinition { - pub direct_field_name: Direct, - pub brace_token: syn::token::Brace, - pub pin_field_name: pin, - pub colon_token: Token![:], - pub pin: Ident, - } +#[cfg(feature = "stm32")] +#[proc_macro] +pub fn stm32_output_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + hw::output_pin(ident).into() +} - impl Parse for DirectPinDefinition { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - Ok(Self { - direct_field_name: input.parse()?, - brace_token: braced!(content in input), - pin_field_name: content.parse()?, - colon_token: content.parse()?, - pin: content.parse()?, - }) - } - } +#[cfg(feature = "stm32")] +#[proc_macro] +#[proc_macro_error] +pub fn stm32_setup_buffered_uart(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as hw::BufferedUartArgs); + hw::setup_buffered_uart(ident).into() +} - pub enum AnalogPinType { - Multiplexed(MultiplexerDefinition), - Direct(DirectPinDefinition), - } +#[cfg(feature = "stm32")] +#[proc_macro] +#[proc_macro_error] +pub fn stm32_setup_adc_sampler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let channels = parse_macro_input!(input with Punctuated::parse_terminated); + hw::setup_adc_sampler(channels).into() +} - impl Parse for AnalogPinType { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); - if lookahead.peek(Direct) { - input.parse().map(AnalogPinType::Direct) - } else if lookahead.peek(Multiplexer) { - input.parse().map(AnalogPinType::Multiplexed) - } else { - Err(lookahead.error()) - } - } - } +#[cfg(feature = "stm32")] +#[proc_macro] +#[proc_macro_error] +pub fn stm32_setup_i2c(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as hw::I2cArgs); + hw::setup_i2c(args).into() } +#[cfg(feature = "rp")] #[proc_macro] -pub fn input_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn rp_input_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ident = parse_macro_input!(input as Ident); hw::input_pin(ident).into() } +#[cfg(feature = "rp")] #[proc_macro] -pub fn output_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn rp_output_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ident = parse_macro_input!(input as Ident); hw::output_pin(ident).into() } +#[cfg(feature = "rp")] #[proc_macro] -pub fn setup_i2c(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args = parse_macro_input!(input with Punctuated::parse_terminated); - hw::setup_i2c(args).into() +#[proc_macro_error] +pub fn rp_setup_buffered_uart(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as hw::BufferedUartArgs); + hw::setup_buffered_uart(ident).into() } -#[cfg(feature = "nrf")] +#[cfg(feature = "rp")] #[proc_macro] -pub fn setup_i2c_blocking(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ident = parse_macro_input!(input with Punctuated::parse_terminated); - hw::setup_i2c_blocking(ident).into() +#[proc_macro_error] +pub fn rp_setup_adc_sampler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let channels = parse_macro_input!(input with Punctuated::parse_terminated); + hw::setup_adc_sampler(channels).into() } -#[cfg(feature = "nrf")] +#[cfg(feature = "rp")] #[proc_macro] -pub fn setup_buffered_uarte(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ident = parse_macro_input!(input with Punctuated::parse_terminated); - hw::setup_buffered_uarte(ident).into() +#[proc_macro_error] +pub fn rp_setup_i2c(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as hw::I2cArgs); + hw::setup_i2c(args).into() } -#[cfg(any(feature = "stm32", feature = "rp"))] +#[cfg(feature = "nrf")] #[proc_macro] -pub fn setup_buffered_uart(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ident = parse_macro_input!(input with Punctuated::parse_terminated); - hw::setup_buffered_uart(ident).into() +pub fn nrf_input_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + hw::input_pin(ident).into() } -#[cfg(feature = "rp")] +#[cfg(feature = "nrf")] #[proc_macro] -pub fn setup_dma_channel(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args = parse_macro_input!(input as Ident); - hw::setup_dma_channel(args).into() +pub fn nrf_output_pin(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + hw::output_pin(ident).into() } +#[cfg(feature = "nrf")] #[proc_macro] #[proc_macro_error] -pub fn setup_adc_sampler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - #[cfg(feature = "stm32")] - let channels = parse_macro_input!(input with Punctuated::parse_terminated); - - #[cfg(feature = "nrf")] +pub fn nrf_setup_adc_sampler(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let channels = parse_macro_input!(input as hw::NrfAdcSamplerDefinition); + hw::setup_adc_sampler(channels).into() +} - #[cfg(feature = "rp")] - let channels = parse_macro_input!(input with Punctuated::parse_terminated); +#[cfg(feature = "nrf")] +#[proc_macro] +#[proc_macro_error] +pub fn nrf_setup_buffered_uarte(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as hw::BufferedUarteArgs); + hw::setup_buffered_uarte(ident).into() +} - hw::setup_adc_sampler(channels).into() +#[cfg(feature = "nrf")] +#[proc_macro] +#[proc_macro_error] +pub fn nrf_setup_i2c(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(input as hw::I2cArgs); + hw::setup_i2c(args).into() } mod via; #[proc_macro] +#[proc_macro_error] pub fn setup_macro_buffer(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args = parse_macro_input!(input with Punctuated::parse_terminated); + let args = parse_macro_input!(input as via::MacroBufferArgs); via::setup_macro_buffer(args).into() } +#[proc_macro] +pub fn connect_storage_service(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + via::connect_storage_service(ident).into() +} + mod vial; #[proc_macro] -pub fn enable_vial_rgb(_input: proc_macro::TokenStream) -> proc_macro::TokenStream { - vial::enable_vial_rgb().into() +pub fn enable_vial_rgb(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ident = parse_macro_input!(input as Ident); + vial::enable_vial_rgb(ident).into() } #[proc_macro_attribute] @@ -479,3 +476,273 @@ pub fn task( } .into() } + +macro_rules! parse_as_custom_fields { + ($str_vis:vis struct $builder_name:ident for $str_name:ident { $($all:tt)* }) => { + $crate::parse_as_custom_fields!($str_vis struct $builder_name for $str_name [$($all)*] -> []); + }; + ($str_vis:vis struct $builder_name:ident for $str_name:ident [$vis:vis $field_name:ident: Option<$type:ty> $(, $($rest:tt)*)? ] -> [$($processed:tt)*]) => { + $crate::parse_as_custom_fields!($str_vis struct $builder_name for $str_name [$($($rest)*)?] -> [$($processed)* $vis $field_name: (Some(None), Option<$type>),]); + }; + ($str_vis:vis struct $builder_name:ident for $str_name:ident [$vis:vis $field_name:ident: $type:ty $(, $($rest:tt)*)? ] -> [$($processed:tt)*]) => { + $crate::parse_as_custom_fields!($str_vis struct $builder_name for $str_name [$($($rest)*)?] -> [$($processed)* $vis $field_name: (None, $type),]); + }; + ($str_vis:vis struct $builder_name:ident for $str_name:ident [] -> [$($vis:vis $field_name:ident: ($default:expr, $($type:tt)*)),*,]) => { + $str_vis struct $str_name { + $($vis $field_name: $($type)*),* + } + + struct $builder_name { + $($field_name: Option<$($type)*>),* + } + + impl $builder_name { + fn new() -> Self { + Self { + $($field_name: $default),* + } + } + + fn build(self) -> $str_name { + $( + let $field_name = proc_macro_error::OptionExt::expect_or_abort(self.$field_name, stringify!($field_name field is missing)); + )* + + $str_name { + $($field_name),* + } + } + } + + impl syn::parse::Parse for $str_name { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut args: $builder_name = $builder_name::new(); + + loop { + if input.is_empty() { + break; + } + let ident: Ident = input.parse()?; + let _colon: syn::Token![:] = input.parse()?; + match ident.to_string().as_str() { + $(stringify!($field_name) => args.$field_name = Some(input.parse()?)),*, + _ => return Err(syn::Error::new(input.span(), "unknown field encountered.")) + } + if input.is_empty() { + break; + } + let _comma: syn::Token![,] = input.parse()?; + } + + Ok(args.build()) + } + } + } +} + +pub(crate) use parse_as_custom_fields; +pub(crate) mod common { + use proc_macro2::{Ident, TokenStream}; + use proc_macro_error::OptionExt; + use quote::{quote, ToTokens}; + use syn::parse::Parse; + use syn::{braced, bracketed, custom_keyword, Token}; + + custom_keyword!(Multiplexer); + custom_keyword!(Direct); + custom_keyword!(pin); + custom_keyword!(select_pins); + + crate::parse_as_custom_fields! { + pub struct MultiplexerArgsBuilder for MultiplexerArgs { + pub pin: Ident, + pub select_pins: Row> + } + } + + #[allow(dead_code)] + pub struct MultiplexerDefinition { + pub multiplexer_field_name: Multiplexer, + pub pin_brace_token: syn::token::Brace, + pub multiplexer_args: MultiplexerArgs, + } + + impl Parse for MultiplexerDefinition { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + Ok(Self { + multiplexer_field_name: input.parse()?, + pin_brace_token: braced!(content in input), + multiplexer_args: content.parse()?, + }) + } + } + + crate::parse_as_custom_fields! { + pub struct DirectPinArgsBuilder for DirectPinArgs { + pub pin: Ident + } + } + + #[allow(dead_code)] + pub struct DirectPinDefinition { + pub direct_field_name: Direct, + pub brace_token: syn::token::Brace, + pub direct_pin_args: DirectPinArgs, + } + + impl Parse for DirectPinDefinition { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + Ok(Self { + direct_field_name: input.parse()?, + brace_token: braced!(content in input), + direct_pin_args: content.parse()?, + }) + } + } + + pub enum AnalogPinType { + Multiplexed(MultiplexerDefinition), + Direct(DirectPinDefinition), + } + + impl Parse for AnalogPinType { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Direct) { + input.parse().map(AnalogPinType::Direct) + } else if lookahead.peek(Multiplexer) { + input.parse().map(AnalogPinType::Multiplexed) + } else { + Err(lookahead.error()) + } + } + } + + #[derive(Debug)] + pub struct LayoutLike { + pub layers: Vec>, + } + + impl syn::parse::Parse for LayoutLike { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut layers = Vec::new(); + while let Ok(t) = input.parse() { + layers.push(t) + } + if !input.is_empty() { + return Err(syn::Error::new( + input.span(), + "Encountered tokens that don't look like a layer definition.", + )); + } + + Ok(Self { layers }) + } + } + + #[derive(Debug)] + pub struct Layer { + pub layer_brace: syn::token::Brace, + pub layer: MatrixLike, + } + + impl syn::parse::Parse for Layer { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + let layer_brace = braced!(content in input); + + Ok(Self { + layer_brace, + layer: content.parse()?, + }) + } + } + + #[derive(Debug)] + pub struct MatrixLike { + pub rows: Vec>, + } + + impl syn::parse::Parse for MatrixLike { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut rows = Vec::new(); + while match input.parse() { + Ok(t) => { + rows.push(t); + true + } + Err(e) if !input.is_empty() => { + let mut err = syn::Error::new( + input.span(), + "Encountered tokens that don't look like a row definition.", + ); + err.combine(e); + return Err(err); + } + Err(_) => false, + } {} + + Ok(Self { rows }) + } + } + + #[derive(Debug)] + pub struct Row { + pub row_bracket: syn::token::Bracket, + pub items: Vec, + } + + impl syn::parse::Parse for Row { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + let row_bracket = bracketed!(content in input); + let mut items = Vec::new(); + while match content.parse() { + Ok(t) => { + items.push(t); + true + } + Err(e) if !content.is_empty() => { + let mut err = syn::Error::new(input.span(), "Encountered an invalid token."); + err.combine(e); + return Err(err); + } + Err(_) => false, + } {} + + Ok(Self { row_bracket, items }) + } + } + + #[derive(Debug)] + /// This is the exact same as [`Option`], but has a different [`syn::parse::Parse`] implementation, + /// where "No" parses to `None`, and anything else that parses as `T` corresponds `Some(T)` + pub(crate) enum OptionalItem { + None, + Some(T), + } + + custom_keyword!(No); + + impl Parse for OptionalItem { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(No) { + input.parse::().map(|_| OptionalItem::None) + } else { + input.parse().map(OptionalItem::Some) + } + } + } + + impl ToTokens for OptionalItem { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + OptionalItem::None => quote! { None }.to_tokens(tokens), + OptionalItem::Some(item) => quote! { Some(#item) }.to_tokens(tokens), + } + } + } +} diff --git a/rumcake-macros/src/via.rs b/rumcake-macros/src/via.rs index c897d21..ccbba53 100644 --- a/rumcake-macros/src/via.rs +++ b/rumcake-macros/src/via.rs @@ -1,19 +1,20 @@ -use proc_macro2::{Literal, TokenStream}; -use proc_macro_error::{abort, OptionExt}; +use proc_macro2::{Ident, TokenStream}; use quote::quote; -use syn::punctuated::Punctuated; -use syn::Token; +use syn::LitInt; -pub fn setup_macro_buffer(args: Punctuated) -> TokenStream { - let mut args = args.iter(); - - let buffer_size = args.next().expect_or_abort("Missing buffer size argument."); - let macro_count = args.next().expect_or_abort("Missing macro count argument."); - - if let Some(literal) = args.next() { - abort!(literal.span(), "Unexpected extra arguments.") +crate::parse_as_custom_fields! { + pub struct MacroBufferArgsBuilder for MacroBufferArgs { + buffer_size: LitInt, + macro_count: LitInt, } +} +pub fn setup_macro_buffer( + MacroBufferArgs { + buffer_size, + macro_count, + }: MacroBufferArgs, +) -> TokenStream { quote! { const DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE: u16 = #buffer_size; const DYNAMIC_KEYMAP_MACRO_COUNT: u8 = #macro_count; @@ -31,3 +32,21 @@ pub fn setup_macro_buffer(args: Punctuated) -> TokenStream { } } } + +pub fn connect_storage_service(ident: Ident) -> TokenStream { + quote! { + type StorageType = #ident; + fn get_storage_service() -> Option< + &'static ::rumcake::storage::StorageService< + 'static, + ::FlashStorageType, + Self::StorageType, + >, + > + where + [(); <::FlashStorageType as ::rumcake::storage::FlashStorage>::ERASE_SIZE]:, + { + Some(::get_storage_service()) + } + } +} diff --git a/rumcake-macros/src/vial.rs b/rumcake-macros/src/vial.rs index e7c5c19..3b3d91d 100644 --- a/rumcake-macros/src/vial.rs +++ b/rumcake-macros/src/vial.rs @@ -1,17 +1,17 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::quote; -pub fn enable_vial_rgb() -> TokenStream { +pub fn enable_vial_rgb(ident: Ident) -> TokenStream { quote! { const VIALRGB_ENABLE: bool = true; - type BacklightMatrixDevice = Self; + type RGBBacklightMatrixDevice = #ident; fn get_backlight_matrix() -> Option< - ::rumcake::backlight::BacklightMatrix< - { ::LIGHTING_COLS }, - { ::LIGHTING_ROWS }, + ::rumcake::lighting::BacklightMatrix< + { ::LIGHTING_COLS }, + { ::LIGHTING_ROWS }, >, > { - Some(::get_backlight_matrix()) + Some(::get_backlight_matrix()) } } } diff --git a/rumcake/Cargo.toml b/rumcake/Cargo.toml index eae2ab7..0796885 100644 --- a/rumcake/Cargo.toml +++ b/rumcake/Cargo.toml @@ -82,8 +82,6 @@ rumcake-macros = { path = "../rumcake-macros" } [features] default = [] -drivers = [] - # # Chips # @@ -112,19 +110,18 @@ storage = ["rumcake-macros/storage"] media-keycodes = ["rumcake-macros/media-keycodes"] # Via/Vial -via = [] -vial = ["via", "_backlight"] +via = ["storage"] +vial = ["via", "rgb-backlight-matrix"] # Host communication usb = [] bluetooth = ["nrf-softdevice?/ble-peripheral", "nrf-softdevice?/ble-gatt-server"] -underglow = [] - -_backlight = [] -simple-backlight = ["_backlight"] -simple-backlight-matrix = ["_backlight"] -rgb-backlight-matrix = ["_backlight"] +lighting = [] +underglow = ["lighting"] +simple-backlight = ["lighting"] +simple-backlight-matrix = ["lighting"] +rgb-backlight-matrix = ["lighting"] display = [] diff --git a/rumcake/src/backlight/drivers.rs b/rumcake/src/backlight/drivers.rs deleted file mode 100644 index 984befe..0000000 --- a/rumcake/src/backlight/drivers.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! A set of traits that backlighting drivers must implement. - -use core::fmt::Debug; - -use smart_leds::RGB8; - -use super::{BacklightDevice, BacklightMatrixDevice}; - -/// A trait that a driver must implement in order to support a simple (no matrix, one color) backlighting scheme. -pub trait SimpleBacklightDriver { - /// The type of error that the driver will return if [`SimpleBacklightDriver::write`] fails. - type DriverWriteError: Debug; - - /// Render out a frame buffer using the driver. - async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError>; - - /// The type of error that the driver will return if [`SimpleBacklightDriver::turn_on`] fails. - type DriverEnableError: Debug; - - /// Turn the LEDs on using the driver when the animator gets enabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this, and subsequently [`SimpleBacklightDriver::write`]. So, if your driver - /// doesn't need do anything special to turn the LEDs on, you may simply return `Ok(())`. - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; - - /// The type of error that the driver will return if [`SimpleBacklightDriver::turn_off`] fails. - type DriverDisableError: Debug; - - /// Turn the LEDs off using the driver when the animator is disabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this. However, the tick method will not call - /// [`SimpleBacklightDriver::write`] due to the animator being disabled, so you will need to - /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; -} - -/// A trait that a driver must implement in order to support a simple (no color) backlighting matrix scheme. -pub trait SimpleBacklightMatrixDriver { - /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::write`] fails. - type DriverWriteError: Debug; - - /// Render out a frame buffer using the driver. - async fn write( - &mut self, - buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError>; - - /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::turn_on`] fails. - type DriverEnableError: Debug; - - /// Turn the LEDs on using the driver when the animator gets enabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this, and subsequently [`SimpleBacklightMatrixDriver::write`]. So, if your - /// driver doesn't need do anything special to turn the LEDs on, you may simply return - /// `Ok(())`. - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; - - /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::turn_off`] fails. - type DriverDisableError: Debug; - - /// Turn the LEDs off using the driver when the animator is disabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this. However, the tick method will not call - /// [`SimpleBacklightMatrixDriver::write`] due to the animator being disabled, so you will need to - /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; -} - -/// A trait that a driver must implement in order to support an RGB backlighting matrix scheme. -pub trait RGBBacklightMatrixDriver { - /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::write`] fails. - type DriverWriteError: Debug; - - /// Render out a frame buffer using the driver. - async fn write( - &mut self, - buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError>; - - /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::turn_on`] fails. - type DriverEnableError: Debug; - - /// Turn the LEDs on using the driver when the animator gets enabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this, and subsequently [`RGBBacklightMatrixDriver::write`]. So, if your - /// driver doesn't need do anything special to turn the LEDs on, you may simply return - /// `Ok(())`. - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; - - /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::turn_off`] fails. - type DriverDisableError: Debug; - - /// Turn the LEDs off using the driver when the animator is disabled. - /// - /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called - /// directly after this. However, the tick method will not call - /// [`RGBBacklightMatrixDriver::write`] due to the animator being disabled, so you will need to - /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; -} diff --git a/rumcake/src/backlight/mod.rs b/rumcake/src/backlight/mod.rs deleted file mode 100644 index ee5b28b..0000000 --- a/rumcake/src/backlight/mod.rs +++ /dev/null @@ -1,577 +0,0 @@ -//! Backlighting features. -//! -//! To use backlighting features, keyboards must implement [`BacklightDevice`] -//! (and optionally [`BacklightMatrixDevice`], if a backlight matrix is desired), -//! along with the trait corresponding to a driver that implements one of -//! [`drivers::SimpleBacklightDriver`], [`drivers::SimpleBacklightMatrixDriver`] or -//! [`drivers::RGBBacklightMatrixDriver`], depending on the desired type of backlighting. - -#[cfg(all( - any( - all(feature = "simple-backlight", feature = "simple-backlight-matrix"), - all(feature = "simple-backlight", feature = "rgb-backlight-matrix"), - all(feature = "simple-backlight-matrix", feature = "rgb-backlight-matrix") - ), - not(doc) -))] -compile_error!("Exactly one of `simple-backlight`, `simple-backlight-matrix`, `rgb-backlight-matrix` must be enabled at a time. Please choose the one that you want to use."); - -use bitflags::bitflags; - -pub mod drivers; - -pub use rumcake_macros::{led_flags, led_layout, setup_backlight_matrix}; - -/// A trait that keyboards must implement to use backlight features. -pub trait BacklightDevice { - /// How fast the LEDs refresh to display a new animation frame. - /// - /// It is recommended to set this value to a value that your driver can handle, - /// otherwise your animations will appear to be slowed down. - /// - /// **This does not have any effect if the selected animation is static.** - const FPS: usize = 20; - - #[cfg(feature = "simple-backlight")] - simple_backlight::animations::backlight_effect_items!(); - - #[cfg(feature = "simple-backlight-matrix")] - simple_backlight_matrix::animations::backlight_effect_items!(); - - #[cfg(feature = "rgb-backlight-matrix")] - rgb_backlight_matrix::animations::backlight_effect_items!(); -} - -/// Struct that contains information about a lighting matrix of a given size. Includes information -/// about the physical layout of the LEDs, and the flags for each LED. -pub struct BacklightMatrix { - /// The **physical** position of each LED on your keyboard. - /// - /// A given X or Y coordinate value must fall between 0-255. If any matrix - /// positions are unused, you can use `None`. It is recommended to use the - /// [`led_layout`] macro to set this constant. - pub layout: [[Option<(u8, u8)>; C]; R], - - /// The flags of each LED on your keyboard. - /// - /// You can use any combination of [LEDFlags] for each LED. It is recommended - /// to use the [`led_flags`] macro to set this value. - pub flags: [[LEDFlags; C]; R], -} - -impl BacklightMatrix { - /// Create a new backlight matrix with the given LED information. - pub const fn new(layout: [[Option<(u8, u8)>; C]; R], flags: [[LEDFlags; C]; R]) -> Self { - Self { layout, flags } - } -} - -/// An additional trait that keyboards must implement to use a backlight matrix. -pub trait BacklightMatrixDevice: BacklightDevice { - /// The number of columns in your lighting matrix - /// - /// It is recommended to use the [`setup_backlight_matrix`] macro to set this value. - const LIGHTING_COLS: usize; - - /// The number of rows in your lighting matrix - /// - /// It is recommended to use the [`setup_backlight_matrix`] macro to set this value. - const LIGHTING_ROWS: usize; - - /// Function to return a reference to the [`BacklightMatrix`], containing information about - /// physical LED position, and LED flags. It is recommended to use the - /// [`setup_backlight_matrix`] macro to set this value. - fn get_backlight_matrix() -> BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }>; -} - -#[doc(hidden)] -pub struct EmptyBacklightMatrix; -impl crate::backlight::BacklightDevice for EmptyBacklightMatrix {} -impl crate::backlight::BacklightMatrixDevice for EmptyBacklightMatrix { - const LIGHTING_COLS: usize = 0; - const LIGHTING_ROWS: usize = 0; - fn get_backlight_matrix( - ) -> crate::backlight::BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }> { - const EMPTY_BACKLIGHT_MATRIX: crate::backlight::BacklightMatrix<0, 0> = - crate::backlight::BacklightMatrix::new([], []); - EMPTY_BACKLIGHT_MATRIX - } -} - -#[derive(Debug)] -struct LayoutBounds { - max: (u8, u8), - mid: (u8, u8), - min: (u8, u8), -} - -fn get_led_layout_bounds() -> LayoutBounds -where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, -{ - let mut bounds = LayoutBounds { - max: (0, 0), - mid: (0, 0), - min: (255, 255), - }; - - let mut row = 0; - while row < K::LIGHTING_ROWS { - let mut col = 0; - while col < K::LIGHTING_COLS { - if let Some((x, y)) = K::get_backlight_matrix().layout[row][col] { - bounds.min = ( - if x <= bounds.min.0 { x } else { bounds.min.0 }, - if y <= bounds.min.1 { y } else { bounds.min.1 }, - ); - bounds.max = ( - if x >= bounds.max.0 { x } else { bounds.max.0 }, - if y >= bounds.max.1 { y } else { bounds.max.1 }, - ); - } - col += 1; - } - row += 1; - } - - bounds.mid.0 = (bounds.max.0 - bounds.min.0) / 2 + bounds.min.0; - bounds.mid.1 = (bounds.max.1 - bounds.min.1) / 2 + bounds.min.1; - - bounds -} - -bitflags! { - /// Flags used to mark the purpose of an LED in a backlight matrix. - /// - /// Bits used for the flags correspond to QMK's implementation. - pub struct LEDFlags: u8 { - const NONE = 0b00000000; - const ALPHA = 0b00000001; - const KEYLIGHT = 0b00000100; - const INDICATOR = 0b00001000; - } -} - -macro_rules! backlight_task_fn { - ($name:tt, $gen:ident: $backlight_trait:tt $(+ $other_bounds:tt)*, $driver_type:ty $(, where $($wc:tt)+)?) => { - #[rumcake_macros::task] - pub async fn $name<$gen: $backlight_trait $(+ $other_bounds)*>( - _k: $gen, - driver: $driver_type, - ) $(where $($wc)+)? - { - let mut subscriber = MATRIX_EVENTS.subscriber().unwrap(); - let mut ticker = Ticker::every(Duration::from_millis(1000 / $gen::FPS as u64)); - - // The animator has a local copy of the backlight config state so that it doesn't have to lock the config every frame - let mut animator = BacklightAnimator::new(BACKLIGHT_CONFIG_STATE.get().await, driver); - match animator.config.enabled { - true => animator.turn_on().await, - false => animator.turn_off().await, - } - animator.tick().await; // Force a frame to be rendered in the event that the initial effect is static. - - loop { - let command = if !(animator.config.enabled && animator.config.effect.is_animated()) - { - // We want to wait for a command if the animator is not rendering any animated effects. This allows the task to sleep when the LEDs are static. - Some(BACKLIGHT_COMMAND_CHANNEL.receive().await) - } else { - #[cfg(feature = "vial")] - { - backlight_task_fn!(true, $name, $gen, animator, subscriber, ticker) - } - - #[cfg(not(feature = "vial"))] - { - backlight_task_fn!(false, $name, $gen, animator, subscriber, ticker) - } - }; - - // Process the command if one was received, otherwise continue to render - if let Some(command) = command { - animator.process_command(command).await; - - // Process commands until there are no more to process - while let Ok(command) = BACKLIGHT_COMMAND_CHANNEL.try_receive() { - animator.process_command(command).await; - } - - // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled - let toggled = BACKLIGHT_CONFIG_STATE - .update(|config| { - let toggled = config.enabled != animator.config.enabled; - **config = animator.config; - toggled - }) - .await; - - if toggled { - match animator.config.enabled { - true => animator.turn_on().await, - false => animator.turn_off().await, - } - } - - // Send commands to be consumed by the split peripherals - #[cfg(feature = "split-central")] - { - sync_to_peripherals(&animator).await; - } - - // Ignore any unprocessed matrix events - while subscriber.try_next_message_pure().is_some() {} - - // Reset the ticker so that it doesn't try to catch up on "missed" ticks. - ticker.reset(); - } - - animator.tick().await; - } - } - }; - (true, rgb_backlight_matrix_task, $gen:ident, $animator:ident, $subscriber:ident, $ticker:ident) => { - match select::select3( - $ticker.next(), - BACKLIGHT_COMMAND_CHANNEL.receive(), - crate::vial::VIAL_DIRECT_SET_CHANNEL.receive(), - ) - .await - { - select::Either3::First(()) => { - while let Some(event) = $subscriber.try_next_message_pure() { - if $animator.config.enabled && $animator.config.effect.is_reactive() { - $animator.register_event(event); - } - } - - None - } - select::Either3::Second(command) => Some(command), - select::Either3::Third((led, color)) => { - let col = led as usize % $gen::LIGHTING_COLS; - let row = led as usize / $gen::LIGHTING_COLS % $gen::LIGHTING_ROWS; - $animator.buf[row][col] = color; - continue; - } - } - }; - ($vial_enabled:literal, $name:tt, $gen:ident, $animator:ident, $subscriber:ident, $ticker:ident) => { - match select::select($ticker.next(), BACKLIGHT_COMMAND_CHANNEL.receive()).await { - select::Either::First(()) => { - while let Some(event) = $subscriber.try_next_message_pure() { - if $animator.config.enabled && $animator.config.effect.is_reactive() { - $animator.register_event(event); - } - } - - None - } - select::Either::Second(command) => Some(command), - } - }; -} - -macro_rules! backlight_module { - () => { - use crate::keyboard::MATRIX_EVENTS; - use crate::{LEDEffect, State}; - use embassy_futures::select; - use embassy_sync::channel::Channel; - use embassy_time::{Duration, Ticker}; - - pub mod animations; - - use crate::hw::mcu::RawMutex; - use animations::{BacklightAnimator, BacklightCommand, BacklightConfig}; - - /// Channel for sending backlight commands. - /// - /// Channel messages should be consumed by the [`backlight_task`], so user-level - /// level code should **not** attempt to receive messages from the channel, otherwise - /// commands may not be processed appropriately. You should only send to this channel. - pub static BACKLIGHT_COMMAND_CHANNEL: Channel = - Channel::new(); - - /// State that contains the current configuration for the backlight animator. - pub static BACKLIGHT_CONFIG_STATE: State = State::new( - BacklightConfig::default(), - &[ - #[cfg(feature = "storage")] - &storage::BACKLIGHT_CONFIG_STATE_LISTENER, - ], - ); - }; -} - -macro_rules! storage_module { - () => { - use core::any::TypeId; - - use defmt::{info, warn, Debug2Format}; - use embassy_futures::select; - use embassy_futures::select::Either; - use embassy_sync::signal::Signal; - use embassy_time::Duration; - use embassy_time::Timer; - - use crate::hw::mcu::RawMutex; - use crate::storage::{FlashStorage, StorageDevice}; - - use super::BacklightConfig; - use super::BACKLIGHT_CONFIG_STATE; - - pub(super) static BACKLIGHT_CONFIG_STATE_LISTENER: Signal = Signal::new(); - - pub(super) static BACKLIGHT_SAVE_SIGNAL: Signal = Signal::new(); - }; -} - -macro_rules! storage_task_fn { - ($name:tt, $key:ident) => { - #[rumcake_macros::task] - pub async fn $name( - _k: K, - database: &crate::storage::StorageService<'static, F>, - ) where - [(); F::ERASE_SIZE]:, - { - { - // Check stored backlight config metadata (type id) to see if it has changed - let metadata: [u8; core::mem::size_of::()] = - unsafe { core::mem::transmute(TypeId::of::()) }; - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::$key, - &metadata, - ) - .await; - - // Get backlight config from storage - if let Ok(config) = database - .read( - K::get_storage_buffer(), - crate::storage::StorageKey::$key, - ) - .await - { - info!( - "[BACKLIGHT] Obtained backlight config from storage: {}", - Debug2Format(&config) - ); - // Quietly update the config state so that we don't save the config to storage again - BACKLIGHT_CONFIG_STATE.quiet_set(config).await; - } else { - warn!("[BACKLIGHT] Could not get backlight config from storage, using default config.",); - }; - } - - let save = || async { - let _ = database - .write( - K::get_storage_buffer(), - crate::storage::StorageKey::$key, - BACKLIGHT_CONFIG_STATE.get().await, - ) - .await; - }; - - // Save the backlight config if it hasn't been changed in 5 seconds, or if a save was signalled - loop { - match select::select( - BACKLIGHT_SAVE_SIGNAL.wait(), - BACKLIGHT_CONFIG_STATE_LISTENER.wait(), - ) - .await - { - Either::First(_) => { - save().await; - } - Either::Second(_) => { - match select::select( - select::select( - Timer::after(Duration::from_secs(5)), - BACKLIGHT_SAVE_SIGNAL.wait(), - ), - BACKLIGHT_CONFIG_STATE_LISTENER.wait(), - ) - .await - { - Either::First(_) => { - save().await; - } - Either::Second(_) => { - // Re-signal, so that we skip the `wait()` call at the beginning of this loop - BACKLIGHT_CONFIG_STATE_LISTENER.signal(()); - } - } - } - }; - } - } - } -} - -#[cfg(feature = "simple-backlight")] -pub mod simple_backlight { - use super::drivers::SimpleBacklightDriver; - use super::BacklightDevice; - - #[cfg(feature = "split-central")] - async fn sync_to_peripherals( - animator: &animations::BacklightAnimator>, - ) { - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklight( - BacklightCommand::ResetTime, - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklight( - BacklightCommand::SetEffect(animator.config.effect), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklight( - BacklightCommand::SetValue(animator.config.val), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklight( - BacklightCommand::SetSpeed(animator.config.speed), - )) - .await; - } - - backlight_module!(); - - backlight_task_fn!( - simple_backlight_task, - D: BacklightDevice + 'static, - impl SimpleBacklightDriver + 'static - ); - - #[cfg(feature = "storage")] - pub mod storage { - storage_module!(); - - storage_task_fn!(simple_backlight_storage_task, SimpleBacklightConfig); - } -} - -#[cfg(feature = "simple-backlight-matrix")] -pub mod simple_backlight_matrix { - use super::drivers::SimpleBacklightMatrixDriver; - use super::BacklightMatrixDevice; - - #[cfg(feature = "split-central")] - async fn sync_to_peripherals( - animator: &animations::BacklightAnimator>, - ) where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, - { - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( - BacklightCommand::ResetTime, - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( - BacklightCommand::SetEffect(animator.config.effect), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( - BacklightCommand::SetValue(animator.config.val), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( - BacklightCommand::SetSpeed(animator.config.speed), - )) - .await; - } - - backlight_module!(); - - backlight_task_fn!( - simple_backlight_matrix_task, - D: BacklightMatrixDevice + 'static, - impl SimpleBacklightMatrixDriver + 'static, - where [(); D::LIGHTING_COLS]:, [(); D::LIGHTING_ROWS]:, - ); - - #[cfg(feature = "storage")] - pub mod storage { - storage_module!(); - - storage_task_fn!( - simple_backlight_matrix_storage_task, - SimpleBacklightMatrixConfig - ); - } -} - -#[cfg(feature = "rgb-backlight-matrix")] -pub mod rgb_backlight_matrix { - use super::drivers::RGBBacklightMatrixDriver; - use super::BacklightMatrixDevice; - - #[cfg(feature = "split-central")] - async fn sync_to_peripherals( - animator: &animations::BacklightAnimator>, - ) where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, - { - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::ResetTime, - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::SetEffect(animator.config.effect), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::SetValue(animator.config.val), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::SetSpeed(animator.config.speed), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::SetHue(animator.config.hue), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( - BacklightCommand::SetSaturation(animator.config.sat), - )) - .await; - } - - backlight_module!(); - - backlight_task_fn!( - rgb_backlight_matrix_task, - D: BacklightMatrixDevice + 'static, - impl RGBBacklightMatrixDriver + 'static, - where [(); D::LIGHTING_COLS]:, [(); D::LIGHTING_ROWS]:, - ); - - #[cfg(feature = "storage")] - pub mod storage { - storage_module!(); - - storage_task_fn!(rgb_backlight_matrix_storage_task, RGBBacklightMatrixConfig); - } -} diff --git a/rumcake/src/backlight/rgb_backlight_matrix/animations.rs b/rumcake/src/backlight/rgb_backlight_matrix/animations.rs deleted file mode 100644 index 382447e..0000000 --- a/rumcake/src/backlight/rgb_backlight_matrix/animations.rs +++ /dev/null @@ -1,526 +0,0 @@ -use crate::backlight::drivers::RGBBacklightMatrixDriver; -use crate::backlight::{ - get_led_layout_bounds, BacklightDevice, BacklightMatrixDevice, LayoutBounds, -}; -use crate::{Cycle, LEDEffect}; -use postcard::experimental::max_size::MaxSize; -use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; - -use defmt::{error, warn, Debug2Format}; -use keyberon::layout::Event; -use num_derive::FromPrimitive; -use rand::rngs::SmallRng; -use rand_core::SeedableRng; -use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; -use serde::{Deserialize, Serialize}; -use smart_leds::hsv::Hsv; -use smart_leds::RGB8; - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] -pub struct BacklightConfig { - pub enabled: bool, - pub effect: BacklightEffect, - pub hue: u8, - pub sat: u8, - pub val: u8, - pub speed: u8, -} - -impl BacklightConfig { - pub const fn default() -> Self { - BacklightConfig { - enabled: true, - effect: BacklightEffect::Solid, - hue: 0, - sat: 255, - val: 255, - speed: 86, - } - } -} - -impl Default for BacklightConfig { - fn default() -> Self { - Self::default() - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] -pub enum BacklightCommand { - Toggle, - TurnOn, - TurnOff, - NextEffect, - PrevEffect, - SetEffect(BacklightEffect), - SetHue(u8), - IncreaseHue(u8), - DecreaseHue(u8), - SetSaturation(u8), - IncreaseSaturation(u8), - DecreaseSaturation(u8), - SetValue(u8), - IncreaseValue(u8), - DecreaseValue(u8), - SetSpeed(u8), - IncreaseSpeed(u8), - DecreaseSpeed(u8), - #[cfg(feature = "storage")] - SaveConfig, - ResetTime, // normally used internally for syncing LEDs for split keyboards -} - -#[generate_items_from_enum_variants( - "const RGB_BACKLIGHT_MATRIX_{variant_shouty_snake_case}_ENABLED: bool = true" -)] -#[derive( - FromPrimitive, - Serialize, - Deserialize, - Debug, - Clone, - Copy, - LEDEffect, - Cycle, - PartialEq, - Eq, - MaxSize, -)] -pub enum BacklightEffect { - Solid, - AlphasMods, - GradientUpDown, - GradientLeftRight, - - #[animated] - Breathing, - - #[animated] - ColorbandSat, - - #[animated] - ColorbandVal, - - #[animated] - ColorbandPinWheelSat, - - #[animated] - ColorbandPinWheelVal, - - #[animated] - ColorbandSpiralSat, - - #[animated] - ColorbandSpiralVal, - - #[animated] - CycleAll, - - #[animated] - CycleLeftRight, - - #[animated] - CycleUpDown, - - #[animated] - RainbowMovingChevron, - - #[animated] - CycleOutIn, - - #[animated] - CycleOutInDual, - - #[animated] - CyclePinWheel, - - #[animated] - CycleSpiral, - - #[animated] - DualBeacon, - - #[animated] - RainbowBeacon, - - #[animated] - RainbowPinWheels, - - #[animated] - Raindrops, - - #[animated] - JellybeanRaindrops, - - #[animated] - HueBreathing, - - #[animated] - HuePendulum, - - #[animated] - HueWave, - - #[animated] - PixelRain, - - #[animated] - PixelFlow, - - #[animated] - PixelFractal, - - #[animated] - TypingHeatmap, - - #[animated] - DigitalRain, - - #[animated] - #[reactive] - SolidReactiveSimple, - - #[animated] - #[reactive] - SolidReactive, - - #[animated] - #[reactive] - SolidReactiveWide, - - #[animated] - #[reactive] - SolidReactiveMultiWide, - - #[animated] - #[reactive] - SolidReactiveCross, - - #[animated] - #[reactive] - SolidReactiveMultiCross, - - #[animated] - #[reactive] - SolidReactiveNexus, - - #[animated] - #[reactive] - SolidReactiveMultiNexus, - - #[animated] - #[reactive] - Splash, - - #[animated] - #[reactive] - MultiSplash, - - #[animated] - #[reactive] - SolidSplash, - - #[animated] - #[reactive] - SolidMultiSplash, - - #[cfg(feature = "vial")] - #[animated] - DirectSet, -} - -impl BacklightEffect { - pub(crate) fn is_enabled(&self) -> bool { - match self { - BacklightEffect::Solid => D::RGB_BACKLIGHT_MATRIX_SOLID_ENABLED, - BacklightEffect::AlphasMods => D::RGB_BACKLIGHT_MATRIX_ALPHAS_MODS_ENABLED, - BacklightEffect::GradientUpDown => D::RGB_BACKLIGHT_MATRIX_GRADIENT_UP_DOWN_ENABLED, - BacklightEffect::GradientLeftRight => { - D::RGB_BACKLIGHT_MATRIX_GRADIENT_LEFT_RIGHT_ENABLED - } - BacklightEffect::Breathing => D::RGB_BACKLIGHT_MATRIX_BREATHING_ENABLED, - BacklightEffect::ColorbandSat => D::RGB_BACKLIGHT_MATRIX_COLORBAND_SAT_ENABLED, - BacklightEffect::ColorbandVal => D::RGB_BACKLIGHT_MATRIX_COLORBAND_VAL_ENABLED, - BacklightEffect::ColorbandPinWheelSat => { - D::RGB_BACKLIGHT_MATRIX_COLORBAND_PIN_WHEEL_SAT_ENABLED - } - BacklightEffect::ColorbandPinWheelVal => { - D::RGB_BACKLIGHT_MATRIX_COLORBAND_PIN_WHEEL_VAL_ENABLED - } - BacklightEffect::ColorbandSpiralSat => { - D::RGB_BACKLIGHT_MATRIX_COLORBAND_SPIRAL_SAT_ENABLED - } - BacklightEffect::ColorbandSpiralVal => { - D::RGB_BACKLIGHT_MATRIX_COLORBAND_SPIRAL_VAL_ENABLED - } - BacklightEffect::CycleAll => D::RGB_BACKLIGHT_MATRIX_CYCLE_ALL_ENABLED, - BacklightEffect::CycleLeftRight => D::RGB_BACKLIGHT_MATRIX_CYCLE_LEFT_RIGHT_ENABLED, - BacklightEffect::CycleUpDown => D::RGB_BACKLIGHT_MATRIX_CYCLE_UP_DOWN_ENABLED, - BacklightEffect::RainbowMovingChevron => { - D::RGB_BACKLIGHT_MATRIX_RAINBOW_MOVING_CHEVRON_ENABLED - } - BacklightEffect::CycleOutIn => D::RGB_BACKLIGHT_MATRIX_CYCLE_OUT_IN_ENABLED, - BacklightEffect::CycleOutInDual => D::RGB_BACKLIGHT_MATRIX_CYCLE_OUT_IN_DUAL_ENABLED, - BacklightEffect::CyclePinWheel => D::RGB_BACKLIGHT_MATRIX_CYCLE_PIN_WHEEL_ENABLED, - BacklightEffect::CycleSpiral => D::RGB_BACKLIGHT_MATRIX_CYCLE_SPIRAL_ENABLED, - BacklightEffect::DualBeacon => D::RGB_BACKLIGHT_MATRIX_DUAL_BEACON_ENABLED, - BacklightEffect::RainbowBeacon => D::RGB_BACKLIGHT_MATRIX_RAINBOW_BEACON_ENABLED, - BacklightEffect::RainbowPinWheels => D::RGB_BACKLIGHT_MATRIX_RAINBOW_PIN_WHEELS_ENABLED, - BacklightEffect::Raindrops => D::RGB_BACKLIGHT_MATRIX_RAINDROPS_ENABLED, - BacklightEffect::JellybeanRaindrops => { - D::RGB_BACKLIGHT_MATRIX_JELLYBEAN_RAINDROPS_ENABLED - } - BacklightEffect::HueBreathing => D::RGB_BACKLIGHT_MATRIX_HUE_BREATHING_ENABLED, - BacklightEffect::HuePendulum => D::RGB_BACKLIGHT_MATRIX_HUE_PENDULUM_ENABLED, - BacklightEffect::HueWave => D::RGB_BACKLIGHT_MATRIX_HUE_WAVE_ENABLED, - BacklightEffect::PixelRain => D::RGB_BACKLIGHT_MATRIX_PIXEL_RAIN_ENABLED, - BacklightEffect::PixelFlow => D::RGB_BACKLIGHT_MATRIX_PIXEL_FLOW_ENABLED, - BacklightEffect::PixelFractal => D::RGB_BACKLIGHT_MATRIX_PIXEL_FRACTAL_ENABLED, - BacklightEffect::TypingHeatmap => D::RGB_BACKLIGHT_MATRIX_TYPING_HEATMAP_ENABLED, - BacklightEffect::DigitalRain => D::RGB_BACKLIGHT_MATRIX_DIGITAL_RAIN_ENABLED, - BacklightEffect::SolidReactiveSimple => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_SIMPLE_ENABLED - } - BacklightEffect::SolidReactive => D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_ENABLED, - BacklightEffect::SolidReactiveWide => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_WIDE_ENABLED - } - BacklightEffect::SolidReactiveMultiWide => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_MULTI_WIDE_ENABLED - } - BacklightEffect::SolidReactiveCross => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_CROSS_ENABLED - } - BacklightEffect::SolidReactiveMultiCross => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_MULTI_CROSS_ENABLED - } - BacklightEffect::SolidReactiveNexus => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_NEXUS_ENABLED - } - BacklightEffect::SolidReactiveMultiNexus => { - D::RGB_BACKLIGHT_MATRIX_SOLID_REACTIVE_MULTI_NEXUS_ENABLED - } - BacklightEffect::Splash => D::RGB_BACKLIGHT_MATRIX_SPLASH_ENABLED, - BacklightEffect::MultiSplash => D::RGB_BACKLIGHT_MATRIX_MULTI_SPLASH_ENABLED, - BacklightEffect::SolidSplash => D::RGB_BACKLIGHT_MATRIX_SOLID_SPLASH_ENABLED, - BacklightEffect::SolidMultiSplash => D::RGB_BACKLIGHT_MATRIX_SOLID_MULTI_SPLASH_ENABLED, - #[cfg(feature = "vial")] - BacklightEffect::DirectSet => D::RGB_BACKLIGHT_MATRIX_DIRECT_SET_ENABLED, - } - } -} - -pub(super) struct BacklightAnimator> -where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, -{ - pub(super) config: BacklightConfig, - pub(super) buf: [[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], // Stores the brightness/value of each LED - pub(super) last_presses: ConstGenericRingBuffer<((u8, u8), u32), 8>, // Stores the row and col of the last 8 key presses, and the time (in ticks) it was pressed - pub(super) tick: u32, - pub(super) driver: D, - pub(super) bounds: LayoutBounds, - pub(super) rng: SmallRng, -} - -impl> BacklightAnimator -where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, -{ - pub fn new(config: BacklightConfig, driver: D) -> Self { - Self { - config, - tick: 0, - driver, - last_presses: ConstGenericRingBuffer::new(), - buf: [[RGB8::new(0, 0, 0); K::LIGHTING_COLS]; K::LIGHTING_ROWS], - bounds: get_led_layout_bounds::(), - rng: SmallRng::seed_from_u64(1337), - } - } - - pub async fn turn_on(&mut self) { - if let Err(err) = self.driver.turn_on().await { - warn!("[BACKLIGHT] Animations have been enabled, but the backlight LEDs could not be turned on: {}", Debug2Format(&err)); - }; - } - - pub async fn turn_off(&mut self) { - if let Err(err) = self.driver.turn_off().await { - warn!("[BACKLIGHT] Animations have been disabled, but the backlight LEDs could not be turned off: {}", Debug2Format(&err)); - }; - } - - pub async fn process_command(&mut self, command: BacklightCommand) { - match command { - BacklightCommand::Toggle => { - self.config.enabled = !self.config.enabled; - } - BacklightCommand::TurnOn => { - self.config.enabled = true; - } - BacklightCommand::TurnOff => { - self.config.enabled = false; - } - BacklightCommand::NextEffect => { - while { - self.config.effect.increment(); - !self.config.effect.is_enabled::() - } {} - } - BacklightCommand::PrevEffect => { - while { - self.config.effect.decrement(); - !self.config.effect.is_enabled::() - } {} - } - BacklightCommand::SetEffect(effect) => { - self.config.effect = effect; - } - BacklightCommand::SetHue(hue) => { - self.config.hue = hue; - } - BacklightCommand::IncreaseHue(amount) => { - self.config.hue = self.config.hue.saturating_add(amount); - } - BacklightCommand::DecreaseHue(amount) => { - self.config.hue = self.config.hue.saturating_sub(amount); - } - BacklightCommand::SetSaturation(sat) => { - self.config.sat = sat; - } - BacklightCommand::IncreaseSaturation(amount) => { - self.config.sat = self.config.sat.saturating_add(amount); - } - BacklightCommand::DecreaseSaturation(amount) => { - self.config.sat = self.config.sat.saturating_sub(amount); - } - BacklightCommand::SetValue(val) => { - self.config.val = val; - } - BacklightCommand::IncreaseValue(amount) => { - self.config.val = self.config.val.saturating_add(amount); - } - BacklightCommand::DecreaseValue(amount) => { - self.config.val = self.config.val.saturating_sub(amount); - } - BacklightCommand::SetSpeed(speed) => { - self.config.speed = speed; - } - BacklightCommand::IncreaseSpeed(amount) => { - self.config.speed = self.config.speed.saturating_add(amount); - } - BacklightCommand::DecreaseSpeed(amount) => { - self.config.speed = self.config.speed.saturating_sub(amount); - } - #[cfg(feature = "storage")] - BacklightCommand::SaveConfig => { - super::storage::BACKLIGHT_SAVE_SIGNAL.signal(()); - } - BacklightCommand::ResetTime => { - self.tick = 0; - } - }; - } - - pub fn set_brightness_for_each_led(&mut self, calc: impl Fn(&mut Self, f32, u8) -> Hsv) { - unimplemented!() - } - - pub fn register_event(&mut self, event: Event) { - match event { - Event::Press(row, col) => { - match self - .last_presses - .iter_mut() - .find(|((pressed_row, pressed_col), _time)| { - *pressed_row == row && *pressed_col == col - }) { - Some(press) => { - press.1 = self.tick; - } - None => { - // Check if the matrix position corresponds to a LED position before pushing - if K::get_backlight_matrix() - .layout - .get(row as usize) - .and_then(|row| row.get(col as usize)) - .and_then(|pos| *pos) - .is_some() - { - self.last_presses.push(((row, col), self.tick)); - } - } - }; - } - Event::Release(_row, _col) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. - } - } - - pub async fn tick(&mut self) { - if !self.config.enabled { - return; - } - - // TODO: animations - match self.config.effect { - BacklightEffect::Solid => todo!(), - BacklightEffect::AlphasMods => todo!(), - BacklightEffect::GradientUpDown => todo!(), - BacklightEffect::GradientLeftRight => todo!(), - BacklightEffect::Breathing => todo!(), - BacklightEffect::ColorbandSat => todo!(), - BacklightEffect::ColorbandVal => todo!(), - BacklightEffect::ColorbandPinWheelSat => todo!(), - BacklightEffect::ColorbandPinWheelVal => todo!(), - BacklightEffect::ColorbandSpiralSat => todo!(), - BacklightEffect::ColorbandSpiralVal => todo!(), - BacklightEffect::CycleAll => todo!(), - BacklightEffect::CycleLeftRight => todo!(), - BacklightEffect::CycleUpDown => todo!(), - BacklightEffect::RainbowMovingChevron => todo!(), - BacklightEffect::CycleOutIn => todo!(), - BacklightEffect::CycleOutInDual => todo!(), - BacklightEffect::CyclePinWheel => todo!(), - BacklightEffect::CycleSpiral => todo!(), - BacklightEffect::DualBeacon => todo!(), - BacklightEffect::RainbowBeacon => todo!(), - BacklightEffect::RainbowPinWheels => todo!(), - BacklightEffect::Raindrops => todo!(), - BacklightEffect::JellybeanRaindrops => todo!(), - BacklightEffect::HueBreathing => todo!(), - BacklightEffect::HuePendulum => todo!(), - BacklightEffect::HueWave => todo!(), - BacklightEffect::PixelRain => todo!(), - BacklightEffect::PixelFlow => todo!(), - BacklightEffect::PixelFractal => todo!(), - BacklightEffect::TypingHeatmap => todo!(), - BacklightEffect::DigitalRain => todo!(), - BacklightEffect::SolidReactiveSimple => todo!(), - BacklightEffect::SolidReactive => todo!(), - BacklightEffect::SolidReactiveWide => todo!(), - BacklightEffect::SolidReactiveMultiWide => todo!(), - BacklightEffect::SolidReactiveCross => todo!(), - BacklightEffect::SolidReactiveMultiCross => todo!(), - BacklightEffect::SolidReactiveNexus => todo!(), - BacklightEffect::SolidReactiveMultiNexus => todo!(), - BacklightEffect::Splash => todo!(), - BacklightEffect::MultiSplash => todo!(), - BacklightEffect::SolidSplash => todo!(), - BacklightEffect::SolidMultiSplash => todo!(), - #[cfg(feature = "vial")] - BacklightEffect::DirectSet => {} // We just move onto calling the driver, since the frame buffer is updated by the backlight task - } - - if let Err(err) = self.driver.write(&self.buf).await { - error!( - "[BACKLIGHT] Couldn't update backlight colors: {}", - Debug2Format(&err) - ); - }; - - self.tick += 1; - } -} diff --git a/rumcake/src/backlight/simple_backlight/animations.rs b/rumcake/src/backlight/simple_backlight/animations.rs deleted file mode 100644 index e6f2290..0000000 --- a/rumcake/src/backlight/simple_backlight/animations.rs +++ /dev/null @@ -1,241 +0,0 @@ -use crate::backlight::drivers::SimpleBacklightDriver; -use crate::backlight::BacklightDevice; -use crate::math::{scale, sin}; -use crate::{Cycle, LEDEffect}; -use postcard::experimental::max_size::MaxSize; -use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; - -use core::marker::PhantomData; -use core::u8; -use defmt::{error, warn, Debug2Format}; -use keyberon::layout::Event; -use num_derive::FromPrimitive; -use rand::rngs::SmallRng; -use rand_core::SeedableRng; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] -pub struct BacklightConfig { - pub enabled: bool, - pub effect: BacklightEffect, - pub val: u8, - pub speed: u8, -} - -impl BacklightConfig { - pub const fn default() -> Self { - BacklightConfig { - enabled: true, - effect: BacklightEffect::Solid, - val: 255, - speed: 86, - } - } -} - -impl Default for BacklightConfig { - fn default() -> Self { - Self::default() - } -} - -#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] -pub enum BacklightCommand { - Toggle, - TurnOn, - TurnOff, - NextEffect, - PrevEffect, - SetEffect(BacklightEffect), - SetValue(u8), - IncreaseValue(u8), - DecreaseValue(u8), - SetSpeed(u8), - IncreaseSpeed(u8), - DecreaseSpeed(u8), - #[cfg(feature = "storage")] - SaveConfig, - ResetTime, // normally used internally for syncing LEDs for split keyboards -} - -#[generate_items_from_enum_variants( - "const SIMPLE_BACKLIGHT_{variant_shouty_snake_case}_ENABLED: bool = true" -)] -#[derive( - FromPrimitive, - Serialize, - Deserialize, - Debug, - Clone, - Copy, - LEDEffect, - Cycle, - PartialEq, - Eq, - MaxSize, -)] -pub enum BacklightEffect { - Solid, - - #[animated] - Breathing, - - #[animated] - #[reactive] - Reactive, -} - -impl BacklightEffect { - pub(crate) fn is_enabled(&self) -> bool { - match self { - BacklightEffect::Solid => D::SIMPLE_BACKLIGHT_SOLID_ENABLED, - BacklightEffect::Breathing => D::SIMPLE_BACKLIGHT_BREATHING_ENABLED, - BacklightEffect::Reactive => D::SIMPLE_BACKLIGHT_REACTIVE_ENABLED, - } - } -} - -pub(super) struct BacklightAnimator> { - pub(super) config: BacklightConfig, - pub(super) buf: u8, // Stores the current brightness/value. Different from `self.config.val`. - pub(super) time_of_last_press: u32, - pub(super) tick: u32, - pub(super) driver: D, - pub(super) rng: SmallRng, - pub(super) phantom: PhantomData, -} - -impl> BacklightAnimator { - pub fn new(config: BacklightConfig, driver: D) -> Self { - Self { - config, - tick: 0, - driver, - buf: 0, - time_of_last_press: 0, - rng: SmallRng::seed_from_u64(1337), - phantom: PhantomData, - } - } - - pub async fn turn_on(&mut self) { - if let Err(err) = self.driver.turn_on().await { - warn!("[BACKLIGHT] Animations have been enabled, but the backlight LEDs could not be turned on: {}", Debug2Format(&err)); - }; - } - - pub async fn turn_off(&mut self) { - if let Err(err) = self.driver.turn_off().await { - warn!("[BACKLIGHT] Animations have been disabled, but the backlight LEDs could not be turned off: {}", Debug2Format(&err)); - }; - } - - pub async fn process_command(&mut self, command: BacklightCommand) { - match command { - BacklightCommand::Toggle => { - self.config.enabled = !self.config.enabled; - } - BacklightCommand::TurnOn => { - self.config.enabled = true; - } - BacklightCommand::TurnOff => { - self.config.enabled = false; - } - BacklightCommand::NextEffect => { - while { - self.config.effect.increment(); - self.config.effect.is_enabled::() - } {} - } - BacklightCommand::PrevEffect => { - while { - self.config.effect.decrement(); - self.config.effect.is_enabled::() - } {} - } - BacklightCommand::SetEffect(effect) => { - self.config.effect = effect; - } - BacklightCommand::SetValue(val) => { - self.config.val = val; - } - BacklightCommand::IncreaseValue(amount) => { - self.config.val = self.config.val.saturating_add(amount); - } - BacklightCommand::DecreaseValue(amount) => { - self.config.val = self.config.val.saturating_sub(amount); - } - BacklightCommand::SetSpeed(speed) => { - self.config.speed = speed; - } - BacklightCommand::IncreaseSpeed(amount) => { - self.config.speed = self.config.speed.saturating_add(amount); - } - BacklightCommand::DecreaseSpeed(amount) => { - self.config.speed = self.config.speed.saturating_sub(amount); - } - #[cfg(feature = "storage")] - BacklightCommand::SaveConfig => { - super::storage::BACKLIGHT_SAVE_SIGNAL.signal(()); - } - BacklightCommand::ResetTime => { - self.tick = 0; - } - } - } - - pub fn set_brightness(&mut self, calc: impl Fn(&mut Self, u32) -> u8) { - let time = (self.tick << 8) - / (((K::FPS as u32) << 8) - / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); // `time` should increment by 255 every second - - self.buf = scale(calc(self, time), self.config.val) - } - - pub fn register_event(&mut self, event: Event) { - match event { - Event::Press(_row, _col) => { - self.time_of_last_press = (self.tick << 8) - / (((K::FPS as u32) << 8) - / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); - } - Event::Release(_row, _col) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. - } - } - - pub async fn tick(&mut self) { - if !self.config.enabled { - return; - } - - match self.config.effect { - BacklightEffect::Solid => { - if K::SIMPLE_BACKLIGHT_SOLID_ENABLED { - self.set_brightness(|_animator, _time| u8::MAX) - } - } - BacklightEffect::Breathing => { - if K::SIMPLE_BACKLIGHT_BREATHING_ENABLED { - self.set_brightness(|_animator, time| sin((time >> 2) as u8)) - } - } - BacklightEffect::Reactive => { - if K::SIMPLE_BACKLIGHT_REACTIVE_ENABLED { - self.set_brightness(|animator, time| { - // LED fades after one second - (u8::MAX as u32).saturating_sub(time - animator.time_of_last_press) as u8 - }) - } - } - } - - if let Err(err) = self.driver.write(self.buf).await { - error!( - "[BACKLIGHT] Couldn't update backlight: {}", - Debug2Format(&err) - ); - }; - - self.tick += 1; - } -} diff --git a/rumcake/src/bluetooth/mod.rs b/rumcake/src/bluetooth/mod.rs index e9c3109..69fc50a 100644 --- a/rumcake/src/bluetooth/mod.rs +++ b/rumcake/src/bluetooth/mod.rs @@ -1,20 +1,20 @@ //! Bluetooth host communication. //! -//! To use Bluetooth host communication, keyboards must implement [`rumcake::hw::mcu::BluetoothDevice`], -//! and [`BluetoothKeyboard`]. +//! To use Bluetooth host communication, keyboards must implement +//! [`rumcake::hw::platform::BluetoothDevice`], and [`BluetoothKeyboard`]. #[cfg(any(all(feature = "nrf", feature = "bluetooth"), doc))] pub mod nrf_ble; -use embassy_sync::channel::Channel; use embassy_sync::signal::Signal; -use crate::hw::mcu::RawMutex; -use crate::keyboard::{Keyboard, KeyboardLayout}; +use crate::hw::platform::RawMutex; +use crate::hw::HIDDevice; +use crate::keyboard::Keyboard; use crate::State; /// A trait that keyboards must implement to communicate with host devices over Bluetooth (LE). -pub trait BluetoothKeyboard: Keyboard + KeyboardLayout { +pub trait BluetoothKeyboard: Keyboard + HIDDevice { /// Vendor ID for the keyboard. const BLE_VID: u16; @@ -25,37 +25,6 @@ pub trait BluetoothKeyboard: Keyboard + KeyboardLayout { const BLE_PRODUCT_VERSION: &'static str = Self::HARDWARE_REVISION; } -#[derive(Debug, Clone, Copy)] -/// An enumeration of possible commands that will be processed by the bluetooth task. -pub enum BluetoothCommand { - #[cfg(feature = "usb")] - /// Switch between USB and Bluetooth operation. - /// - /// This will **NOT** disconnect your keyboard from your host device. It - /// will simply determine which device the HID reports get sent to. - ToggleOutput, - #[cfg(feature = "usb")] - /// Switch to USB operation. - /// - /// If your keyboard is connected to a bluetooth device, this will **NOT** disconnect your - /// keyboard from it. It will simply output the HID reports to the connected USB device. - OutputUSB, - #[cfg(feature = "usb")] - /// Switch to bluetooth operation. - /// - /// If your keyboard is connected to a USB device, this will **NOT** disconnect your keyboard - /// from it. It will simply output the HID reports to the connected bluetooth device. - OutputBluetooth, -} - -/// Channel for sending [`BluetoothCommand`]s. -/// -/// Channel messages should be consumed by the bluetooth task ([`nrf_ble::nrf_ble_task`] for -/// nRF5x-based keyboards), so user-level code should **not** attempt to receive messages from the -/// channel, otherwise commands may not be processed appropriately. You should only send to this -/// channel. -pub static BLUETOOTH_COMMAND_CHANNEL: Channel = Channel::new(); - pub(crate) static BLUETOOTH_CONNECTED_STATE: State = State::new(false, &[&crate::hw::BLUETOOTH_CONNECTED_STATE_LISTENER]); diff --git a/rumcake/src/bluetooth/nrf_ble.rs b/rumcake/src/bluetooth/nrf_ble.rs index 8ad6755..779515f 100644 --- a/rumcake/src/bluetooth/nrf_ble.rs +++ b/rumcake/src/bluetooth/nrf_ble.rs @@ -1,7 +1,6 @@ use core::cell::{Cell, RefCell}; use defmt::{debug, error, info, warn, Debug2Format}; -use embassy_futures::join; use embassy_futures::select::{self, select3, select4}; use heapless::Vec; use nrf_softdevice::ble::gatt_server::builder::ServiceBuilder; @@ -21,15 +20,12 @@ use static_cell::StaticCell; use usbd_human_interface_device::device::consumer::MultipleConsumerReport; use usbd_human_interface_device::device::keyboard::NKROBootKeyboardReport; -use crate::hw::mcu::BLUETOOTH_ADVERTISING_MUTEX; -use crate::hw::{ - HIDOutput, OutputMode, BATTERY_LEVEL_STATE, CURRENT_OUTPUT_STATE, OUTPUT_MODE_STATE, -}; -use crate::keyboard::{CONSUMER_REPORT_HID_SEND_CHANNEL, KEYBOARD_REPORT_HID_SEND_CHANNEL}; +use crate::hw::platform::BLUETOOTH_ADVERTISING_MUTEX; +use crate::hw::{HIDOutput, BATTERY_LEVEL_STATE, CURRENT_OUTPUT_STATE}; use crate::bluetooth::{ - BluetoothCommand, BluetoothKeyboard, BATTERY_LEVEL_LISTENER, BLUETOOTH_COMMAND_CHANNEL, - BLUETOOTH_CONNECTED_STATE, CURRENT_OUTPUT_STATE_LISTENER, + BluetoothKeyboard, BATTERY_LEVEL_LISTENER, BLUETOOTH_CONNECTED_STATE, + CURRENT_OUTPUT_STATE_LISTENER, }; #[derive(Clone, Copy)] @@ -739,48 +735,49 @@ where static BONDER: StaticCell = StaticCell::new(); let bonder = BONDER.init(Bonder::default()); - let connection_fut = async { - loop { - let advertisement = ConnectableAdvertisement::ScannableUndirected { - adv_data: &adv_data, - scan_data: &scan_data, - }; - - let connection = { - let _lock = BLUETOOTH_ADVERTISING_MUTEX.lock().await; - match advertise_pairable(sd, advertisement, &Default::default(), bonder).await { - Ok(connection) => { - info!("[BT_HID] Connection established with host device"); - BLUETOOTH_CONNECTED_STATE.set(true).await; - connection - } - Err(error) => { - warn!("[BT_HID] BLE advertising error: {}", Debug2Format(&error)); - continue; - } + loop { + let advertisement = ConnectableAdvertisement::ScannableUndirected { + adv_data: &adv_data, + scan_data: &scan_data, + }; + + let connection = { + let _lock = BLUETOOTH_ADVERTISING_MUTEX.lock().await; + match advertise_pairable(sd, advertisement, &Default::default(), bonder).await { + Ok(connection) => { + info!("[BT_HID] Connection established with host device"); + BLUETOOTH_CONNECTED_STATE.set(true).await; + connection + } + Err(error) => { + warn!("[BT_HID] BLE advertising error: {}", Debug2Format(&error)); + continue; } - }; + } + }; - let conn_fut = run(&connection, &server, |event| match event { - ServerEvent::Bas(bas_event) => match bas_event { - BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => { - debug!("[BT_HID] Battery value CCCD updated: {}", notifications); - } - }, - ServerEvent::Dis(dis_event) => match dis_event {}, - ServerEvent::Hids(hids_event) => match hids_event { - HIDServiceEvent::KeyboardReportCccdWrite { notifications } => { - debug!("[BT_HID] Keyboard report CCCD updated: {}", notifications); - } - HIDServiceEvent::ConsumerReportCccdWrite { notifications } => { - debug!("[BT_HID] Consumer report CCCD updated: {}", notifications); - } - HIDServiceEvent::ViaReportCccdWrite { notifications } => { - debug!("[BT_HID] Via report CCCD updated: {}", notifications); - } - HIDServiceEvent::ViaReportWrite(report) => { - #[cfg(feature = "via")] - match crate::via::VIA_REPORT_HID_RECEIVE_CHANNEL.try_send(report) { + let conn_fut = run(&connection, &server, |event| match event { + ServerEvent::Bas(bas_event) => match bas_event { + BatteryServiceEvent::BatteryLevelCccdWrite { notifications } => { + debug!("[BT_HID] Battery value CCCD updated: {}", notifications); + } + }, + ServerEvent::Dis(dis_event) => match dis_event {}, + ServerEvent::Hids(hids_event) => match hids_event { + HIDServiceEvent::KeyboardReportCccdWrite { notifications } => { + debug!("[BT_HID] Keyboard report CCCD updated: {}", notifications); + } + HIDServiceEvent::ConsumerReportCccdWrite { notifications } => { + debug!("[BT_HID] Consumer report CCCD updated: {}", notifications); + } + HIDServiceEvent::ViaReportCccdWrite { notifications } => { + debug!("[BT_HID] Via report CCCD updated: {}", notifications); + } + HIDServiceEvent::ViaReportWrite(report) => { + #[cfg(feature = "via")] + { + let channel = K::get_via_hid_receive_channel(); + match channel.try_send(report) { Ok(()) => { debug!("[BT_HID] Received Via report: {}", report); } @@ -792,196 +789,171 @@ where ); } } + } - #[cfg(not(feature = "via"))] - warn!("[BT_HID] Via is not enabled. Ignoring report: {}", report); + #[cfg(not(feature = "via"))] + warn!("[BT_HID] Via is not enabled. Ignoring report: {}", report); + } + HIDServiceEvent::HidControlWrite(val) => { + debug!("[BT_HID] Received HID control value: {=u8}", val); + } + }, + }); + + let adc_fut = async { + loop { + BATTERY_LEVEL_LISTENER.wait().await; + let pct = BATTERY_LEVEL_STATE.get().await; + + match server.bas.battery_level_notify(&connection, &pct) { + Ok(_) => { + debug!( + "[BT_HID] Notified connection of new battery level: {=u8}", + pct + ); } - HIDServiceEvent::HidControlWrite(val) => { - debug!("[BT_HID] Received HID control value: {=u8}", val); + Err(error) => { + error!( + "[BT_HID] Could not notify connection of new battery level ({=u8}): {}", + pct, + Debug2Format(&error) + ); } - }, - }); - - let adc_fut = async { - loop { - BATTERY_LEVEL_LISTENER.wait().await; - let pct = BATTERY_LEVEL_STATE.get().await; - - match server.bas.battery_level_notify(&connection, &pct) { - Ok(_) => { - debug!( - "[BT_HID] Notified connection of new battery level: {=u8}", - pct + } + } + }; + + let hid_fut = async { + let keyboard_report_channel = K::get_keyboard_report_send_channel(); + let consumer_report_channel = K::get_consumer_report_send_channel(); + + // Discard any reports that haven't been processed due to lack of a connection + while keyboard_report_channel.try_receive().is_ok() {} + while consumer_report_channel.try_receive().is_ok() {} + + #[cfg(feature = "via")] + let via_report_channel = K::get_via_hid_send_channel(); + + #[cfg(feature = "via")] + while via_report_channel.try_receive().is_ok() {} + + loop { + if matches!(CURRENT_OUTPUT_STATE.get().await, Some(HIDOutput::Bluetooth)) { + #[cfg(feature = "via")] + match select4( + CURRENT_OUTPUT_STATE_LISTENER.wait(), + keyboard_report_channel.receive(), + consumer_report_channel.receive(), + via_report_channel.receive(), + ) + .await + { + select::Either4::First(()) => {} + select::Either4::Second(report) => { + info!( + "[BT_HID] Writing NKRO HID report to bluetooth: {:?}", + Debug2Format(&report) ); + + if let Err(err) = + server.hids.keyboard_report_notify(&connection, report) + { + error!( + "[BT_HID] Couldn't write NKRO HID report: {:?}", + Debug2Format(&err) + ); + }; } - Err(error) => { - error!( - "[BT_HID] Could not notify connection of new battery level ({=u8}): {}", - pct, - Debug2Format(&error) + select::Either4::Third(report) => { + info!( + "[BT_HID] Writing consumer HID report to bluetooth: {:?}", + Debug2Format(&report) ); - } - } - } - }; - - let hid_fut = async { - // Discard any reports that haven't been processed due to lack of a connection - while KEYBOARD_REPORT_HID_SEND_CHANNEL.try_receive().is_ok() {} - while CONSUMER_REPORT_HID_SEND_CHANNEL.try_receive().is_ok() {} - - #[cfg(feature = "via")] - while crate::via::VIA_REPORT_HID_SEND_CHANNEL - .try_receive() - .is_ok() - {} - - loop { - if matches!(CURRENT_OUTPUT_STATE.get().await, Some(HIDOutput::Bluetooth)) { - #[cfg(feature = "via")] - match select4( - CURRENT_OUTPUT_STATE_LISTENER.wait(), - KEYBOARD_REPORT_HID_SEND_CHANNEL.receive(), - CONSUMER_REPORT_HID_SEND_CHANNEL.receive(), - crate::via::VIA_REPORT_HID_SEND_CHANNEL.receive(), - ) - .await - { - select::Either4::First(()) => {} - select::Either4::Second(report) => { - info!( - "[BT_HID] Writing NKRO HID report to bluetooth: {:?}", - Debug2Format(&report) - ); - if let Err(err) = - server.hids.keyboard_report_notify(&connection, report) - { - error!( - "[BT_HID] Couldn't write NKRO HID report: {:?}", - Debug2Format(&err) - ); - }; - } - select::Either4::Third(report) => { - info!( - "[BT_HID] Writing consumer HID report to bluetooth: {:?}", - Debug2Format(&report) + if let Err(err) = + server.hids.consumer_report_notify(&connection, report) + { + error!( + "[BT_HID] Couldn't write consumer HID report: {:?}", + Debug2Format(&err) ); + }; + } + select::Either4::Fourth(report) => { + info!( + "[BT_HID] Writing Via HID report to bluetooth: {:?}", + Debug2Format(&report) + ); - if let Err(err) = - server.hids.consumer_report_notify(&connection, report) - { - error!( - "[BT_HID] Couldn't write consumer HID report: {:?}", - Debug2Format(&err) - ); - }; - } - select::Either4::Fourth(report) => { - info!( - "[BT_HID] Writing Via HID report to bluetooth: {:?}", - Debug2Format(&report) + if let Err(err) = server.hids.via_report_notify(&connection, report) { + error!( + "[BT_HID] Couldn't write Via HID report: {:?}", + Debug2Format(&err) ); + }; + } + }; + + #[cfg(not(feature = "via"))] + match select3( + CURRENT_OUTPUT_STATE_LISTENER.wait(), + keyboard_report_channel.receive(), + consumer_report_channel.receive(), + ) + .await + { + select::Either3::First(()) => {} + select::Either3::Second(report) => { + info!( + "[BT_HID] Writing NKRO HID report to bluetooth: {:?}", + Debug2Format(&report) + ); - if let Err(err) = server.hids.via_report_notify(&connection, report) - { - error!( - "[BT_HID] Couldn't write Via HID report: {:?}", - Debug2Format(&err) - ); - }; - } - }; - - #[cfg(not(feature = "via"))] - match select3( - CURRENT_OUTPUT_STATE_LISTENER.wait(), - KEYBOARD_REPORT_HID_SEND_CHANNEL.receive(), - CONSUMER_REPORT_HID_SEND_CHANNEL.receive(), - ) - .await - { - select::Either3::First(()) => {} - select::Either3::Second(report) => { - info!( - "[BT_HID] Writing NKRO HID report to bluetooth: {:?}", - Debug2Format(&report) + if let Err(err) = + server.hids.keyboard_report_notify(&connection, report) + { + error!( + "[BT_HID] Couldn't write NKRO HID report: {:?}", + Debug2Format(&err) ); + }; + } + select::Either3::Third(report) => { + info!( + "[BT_HID] Writing consumer HID report to bluetooth: {:?}", + Debug2Format(&report) + ); - if let Err(err) = - server.hids.keyboard_report_notify(&connection, report) - { - error!( - "[BT_HID] Couldn't write NKRO HID report: {:?}", - Debug2Format(&err) - ); - }; - } - select::Either3::Third(report) => { - info!( - "[BT_HID] Writing consumer HID report to bluetooth: {:?}", - Debug2Format(&report) + if let Err(err) = + server.hids.consumer_report_notify(&connection, report) + { + error!( + "[BT_HID] Couldn't write consumer HID report: {:?}", + Debug2Format(&err) ); - - if let Err(err) = - server.hids.consumer_report_notify(&connection, report) - { - error!( - "[BT_HID] Couldn't write consumer HID report: {:?}", - Debug2Format(&err) - ); - }; - } - }; - } else { - CURRENT_OUTPUT_STATE_LISTENER.wait().await; - } - } - }; - - match select3(conn_fut, adc_fut, hid_fut).await { - select::Either3::First(error) => { - warn!( - "[BT_HID] Connection has been lost: {}", - Debug2Format(&error) - ); - BLUETOOTH_CONNECTED_STATE.set(false).await; - } - select::Either3::Second(_) => { - error!("[BT_HID] Battery task failed. This should not happen."); - } - select::Either3::Third(_) => { - error!("[BT_HID] HID task failed. This should not happen."); - } - }; - } - }; - - let command_fut = async { - loop { - let command = BLUETOOTH_COMMAND_CHANNEL.receive().await; - match command { - #[cfg(feature = "usb")] - BluetoothCommand::ToggleOutput => { - OUTPUT_MODE_STATE - .set(match OUTPUT_MODE_STATE.get().await { - OutputMode::Usb => OutputMode::Bluetooth, - OutputMode::Bluetooth => OutputMode::Usb, - }) - .await; - } - #[cfg(feature = "usb")] - BluetoothCommand::OutputUSB => { - OUTPUT_MODE_STATE.set(OutputMode::Usb).await; - } - #[cfg(feature = "usb")] - BluetoothCommand::OutputBluetooth => { - OUTPUT_MODE_STATE.set(OutputMode::Bluetooth).await; + }; + } + }; + } else { + CURRENT_OUTPUT_STATE_LISTENER.wait().await; } } - } - }; + }; - join::join(command_fut, connection_fut).await; + match select3(conn_fut, adc_fut, hid_fut).await { + select::Either3::First(error) => { + warn!( + "[BT_HID] Connection has been lost: {}", + Debug2Format(&error) + ); + BLUETOOTH_CONNECTED_STATE.set(false).await; + } + select::Either3::Second(_) => { + error!("[BT_HID] Battery task failed. This should not happen."); + } + select::Either3::Third(_) => { + error!("[BT_HID] HID task failed. This should not happen."); + } + }; + } } diff --git a/rumcake/src/display/drivers.rs b/rumcake/src/display.rs similarity index 63% rename from rumcake/src/display/drivers.rs rename to rumcake/src/display.rs index 3514cf4..371ecc9 100644 --- a/rumcake/src/display/drivers.rs +++ b/rumcake/src/display.rs @@ -1,8 +1,14 @@ -//! A set of traits that display drivers must implement, and utilities that can be used by drivers. +//! Display feature. +//! +//! To use the display feature, keyboards must implement [`DisplayDevice`], along +//! with the trait corresponding to the chosen driver (which should implement +//! [`drivers::DisplayDriver`]). -use super::DisplayDevice; use core::fmt::Debug; +use embassy_futures::select::{select, select_array, Either}; +use embassy_sync::signal::Signal; +use embassy_time::{Duration, Ticker, Timer}; use embedded_graphics::mono_font::ascii::FONT_6X10; use embedded_graphics::mono_font::{MonoTextStyle, MonoTextStyleBuilder}; use embedded_graphics::pixelcolor::BinaryColor; @@ -10,6 +16,24 @@ use embedded_text::alignment::HorizontalAlignment; use embedded_text::style::{HeightMode, TextBoxStyle, TextBoxStyleBuilder}; use heapless::String; +use crate::hw::platform::RawMutex; + +pub(crate) static OUTPUT_MODE_STATE_LISTENER: Signal = Signal::new(); +pub(crate) static BATTERY_LEVEL_LISTENER: Signal = Signal::new(); + +/// A trait that keyboards must implement to use a display. +pub trait DisplayDevice { + /// An FPS value of 0 will make the display update only when needed. + /// + /// Set this to a value higher than 0 if you are trying to display something with animations. + const FPS: usize = 0; + + /// How long the screen will stay on before it turns off due to screen inactivty. + /// + /// If set to 0, the screen will always stay on. + const TIMEOUT: usize = 30; +} + /// Default style for text. The default style uses [`BinaryColor`], and [`FONT_6X10`]. pub static DEFAULT_STYLE: MonoTextStyle<'_, BinaryColor> = MonoTextStyleBuilder::new() .font(&FONT_6X10) @@ -149,3 +173,68 @@ pub trait DisplayDriver { /// Called when the screen is being turned back on after being turned off. async fn turn_on(&mut self); } + +#[rumcake_macros::task] +pub async fn display_task(_k: K, mut display: impl DisplayDriver) { + let mut ticker = if K::FPS > 0 { + Some(Ticker::every(Duration::from_millis(1000 / K::FPS as u64))) + } else { + None + }; + + // Tracks the state of the display so that we don't repeatedly send extra "turn_on" commands. + display.turn_on().await; + let mut display_on = true; + + // Render a frame after turning on + display.on_update().await; + + loop { + let update_fut = async { + if let Some(ref mut ticker) = ticker { + ticker.next().await; + ((), 0) + } else { + let mut result = select_array([ + OUTPUT_MODE_STATE_LISTENER.wait(), + BATTERY_LEVEL_LISTENER.wait(), + ]) + .await; + result.1 += 1; + result + } + }; + + let timeout_timer = if K::TIMEOUT > 0 { + Some(Timer::after(Duration::from_secs(K::TIMEOUT as u64))) + } else { + None + }; + + if let Some(timer) = timeout_timer { + match select(update_fut, timer).await { + Either::First(((), idx)) => { + match idx { + 0 | 1 => { + // Turn the display on in the event of a tick, or change in USB state. + if !display_on { + display.turn_on().await; + display_on = true; + } + } + _ => {} + }; + + display.on_update().await; + } + Either::Second(_) => { + display.turn_off().await; + display_on = false; + } + }; + } else { + update_fut.await; + display.on_update().await; + } + } +} diff --git a/rumcake/src/display/mod.rs b/rumcake/src/display/mod.rs deleted file mode 100644 index f8b9f1a..0000000 --- a/rumcake/src/display/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! Display feature. -//! -//! To use the display feature, keyboards must implement [`DisplayDevice`], along -//! with the trait corresponding to the chosen driver (which should implement -//! [`drivers::DisplayDriver`]). - -use embassy_futures::select::{select, select_array, Either}; -use embassy_sync::signal::Signal; -use embassy_time::{Duration, Ticker, Timer}; - -pub mod drivers; - -use self::drivers::DisplayDriver; -use crate::hw::mcu::RawMutex; - -pub(crate) static OUTPUT_MODE_STATE_LISTENER: Signal = Signal::new(); -pub(crate) static BATTERY_LEVEL_LISTENER: Signal = Signal::new(); - -/// A trait that keyboards must implement to use a display. -pub trait DisplayDevice { - /// An FPS value of 0 will make the display update only when needed. - /// - /// Set this to a value higher than 0 if you are trying to display something with animations. - const FPS: usize = 0; - - /// How long the screen will stay on before it turns off due to screen inactivty. - /// - /// If set to 0, the screen will always stay on. - const TIMEOUT: usize = 30; -} - -#[rumcake_macros::task] -pub async fn display_task(_k: K, mut display: impl DisplayDriver) { - let mut ticker = if K::FPS > 0 { - Some(Ticker::every(Duration::from_millis(1000 / K::FPS as u64))) - } else { - None - }; - - // Tracks the state of the display so that we don't repeatedly send extra "turn_on" commands. - display.turn_on().await; - let mut display_on = true; - - // Render a frame after turning on - display.on_update().await; - - loop { - let update_fut = async { - if let Some(ref mut ticker) = ticker { - ticker.next().await; - ((), 0) - } else { - let mut result = select_array([ - OUTPUT_MODE_STATE_LISTENER.wait(), - BATTERY_LEVEL_LISTENER.wait(), - ]) - .await; - result.1 += 1; - result - } - }; - - let timeout_timer = if K::TIMEOUT > 0 { - Some(Timer::after(Duration::from_secs(K::TIMEOUT as u64))) - } else { - None - }; - - if let Some(timer) = timeout_timer { - match select(update_fut, timer).await { - Either::First(((), idx)) => { - match idx { - 0 | 1 => { - // Turn the display on in the event of a tick, or change in USB state. - if !display_on { - display.turn_on().await; - display_on = true; - } - } - _ => {} - }; - - display.on_update().await; - } - Either::Second(_) => { - display.turn_off().await; - display_on = false; - } - }; - } else { - update_fut.await; - display.on_update().await; - } - } -} diff --git a/rumcake/src/drivers/is31fl3731.rs b/rumcake/src/drivers/is31fl3731.rs index 3faf481..526ee64 100644 --- a/rumcake/src/drivers/is31fl3731.rs +++ b/rumcake/src/drivers/is31fl3731.rs @@ -1,13 +1,14 @@ //! Rumcake driver implementations for [gleich's IS31FL3731 driver](`is31fl3731`). //! //! This driver provides implementations for -//! [`SimpleBacklightDriver`](`crate::backlight::drivers::SimpleBacklightDriver`), -//! [`SimpleBacklightMatrixDriver`](`crate::backlight::drivers::SimpleBacklightMatrixDriver`), and -//! [`RGBBacklightMatrixDriver`](`crate::backlight::drivers::RGBBacklightMatrixDriver`) +//! [`SimpleBacklightDriver`](`crate::lighting::simple_backlight::SimpleBacklightDriver`), +//! [`SimpleBacklightMatrixDriver`](`crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDriver`), +//! and +//! [`RGBBacklightMatrixDriver`](`crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDriver`) //! //! To use this driver for backlighting, keyboards must implement -//! [`IS31FL3731BacklightDriver`](backlight::IS31FL3731BacklightDriver). The result of -//! [`setup_driver`] should be passed to a backlight task. +//! [`IS31FL3731BacklightDriver`](IS31FL3731BacklightDriver). The result of [`setup_driver`] should +//! be passed to a backlight task. pub use is31fl3731 as driver; @@ -172,7 +173,14 @@ pub enum Position { use core::fmt::Debug; use embassy_time::Delay; use embedded_hal_async::i2c::I2c; -use is31fl3731::IS31FL3731; +use is31fl3731::{gamma, Error, IS31FL3731}; +use smart_leds::RGB8; + +pub use rumcake_macros::{ + is31fl3731_get_led_from_matrix_coordinates as get_led_from_matrix_coordinates, + is31fl3731_get_led_from_rgb_matrix_coordinates as get_led_from_rgb_matrix_coordinates, + setup_is31fl3731, +}; /// Create an instance of the IS31FL3731 driver with the provided I2C peripheral, and address. pub async fn setup_driver( @@ -189,150 +197,144 @@ pub async fn setup_driver( driver } -#[cfg(feature = "_backlight")] -/// IS31FL3731 backlight driver implementations -pub mod backlight { - use is31fl3731::{gamma, Error, IS31FL3731}; - - use core::fmt::Debug; - use embedded_hal_async::i2c::I2c; - use smart_leds::RGB8; - - use crate::backlight::drivers::SimpleBacklightDriver; - use crate::backlight::drivers::{RGBBacklightMatrixDriver, SimpleBacklightMatrixDriver}; - use crate::backlight::BacklightMatrixDevice; - - pub use rumcake_macros::{ - is31fl3731_get_led_from_matrix_coordinates as get_led_from_matrix_coordinates, - is31fl3731_get_led_from_rgb_matrix_coordinates as get_led_from_rgb_matrix_coordinates, - }; - - /// A trait that keyboards must implement to use the IS31FL3731 driver for backlighting. - pub trait IS31FL3731BacklightDriver: BacklightMatrixDevice { - /// Convert matrix coordinates in the form of (col, row) to an IS31FL3731 [`Position`](super::Position). - /// - /// It is recommended to use [`is31fl3731_get_led_from_matrix_coordinates`] to implement this function. - fn get_led_from_matrix_coordinates(x: u8, y: u8) -> u8; - } +/// A trait that keyboards must implement to use the IS31FL3731 driver for backlighting. +pub trait IS31FL3731BacklightDriver { + /// Convert matrix coordinates in the form of (col, row) to an IS31FL3731 [`Position`](super::Position). + /// + /// It is recommended to use [`is31fl3731_get_led_from_matrix_coordinates`] to implement this function. + fn get_led_from_matrix_coordinates(x: u8, y: u8) -> u8; +} - impl, K: IS31FL3731BacklightDriver> - SimpleBacklightDriver for IS31FL3731 - { - type DriverWriteError = Error; +#[cfg(feature = "simple-backlight")] +impl< + I2CError: Debug, + I2C: I2c, + K: IS31FL3731BacklightDriver + crate::lighting::simple_backlight::SimpleBacklightDevice, + > crate::lighting::simple_backlight::SimpleBacklightDriver for IS31FL3731 +{ + type DriverWriteError = Error; - async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError> { - let payload = [gamma(brightness); 144]; + async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError> { + let payload = [gamma(brightness); 144]; - self.all_pixels(&payload).await?; + self.all_pixels(&payload).await?; - Ok(()) - } + Ok(()) + } - type DriverEnableError = Error; + type DriverEnableError = Error; - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - self.sleep(false).await?; + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + self.sleep(false).await?; - Ok(()) - } + Ok(()) + } - type DriverDisableError = Error; + type DriverDisableError = Error; - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.sleep(true).await?; + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.sleep(true).await?; - Ok(()) - } + Ok(()) } +} - impl, K: IS31FL3731BacklightDriver> - SimpleBacklightMatrixDriver for IS31FL3731 - { - type DriverWriteError = Error; - - async fn write( - &mut self, - buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError> { - let mut payload = [0; 144]; - - // Map the frame data to LED offsets and set the brightness of the LED in the payload - for (row_num, row) in buf.iter().enumerate() { - for (col_num, val) in row.iter().enumerate() { - let offset = K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8); - if offset != 255 { - payload[offset as usize] = gamma(*val); - } +#[cfg(feature = "simple-backlight-matrix")] +impl< + I2CError: Debug, + I2C: I2c, + K: IS31FL3731BacklightDriver + + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDevice, + > crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDriver for IS31FL3731 +{ + type DriverWriteError = Error; + + async fn write( + &mut self, + buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError> { + let mut payload = [0; 144]; + + // Map the frame data to LED offsets and set the brightness of the LED in the payload + for (row_num, row) in buf.iter().enumerate() { + for (col_num, val) in row.iter().enumerate() { + let offset = K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8); + if offset != 255 { + payload[offset as usize] = gamma(*val); } } + } - self.all_pixels(&payload).await?; + self.all_pixels(&payload).await?; - Ok(()) - } + Ok(()) + } - type DriverEnableError = Error; + type DriverEnableError = Error; - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - self.sleep(false).await?; + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + self.sleep(false).await?; - Ok(()) - } + Ok(()) + } - type DriverDisableError = Error; + type DriverDisableError = Error; - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.sleep(true).await?; + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.sleep(true).await?; - Ok(()) - } + Ok(()) } +} - impl, K: IS31FL3731BacklightDriver> - RGBBacklightMatrixDriver for IS31FL3731 - { - type DriverWriteError = Error; - - async fn write( - &mut self, - buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError> { - let mut payload = [0; 144]; - - // Map the frame data to LED offsets and set the brightness of the LED in the payload - for (row_num, row) in buf.iter().enumerate() { - for (col_num, color) in row.iter().enumerate() { - for (component, val) in color.iter().enumerate() { - let offset = K::get_led_from_matrix_coordinates( - col_num as u8 + (component * K::LIGHTING_COLS) as u8, - row_num as u8, - ); - if offset != 255 { - payload[offset as usize] = gamma(val); - } +#[cfg(feature = "rgb-backlight-matrix")] +impl< + I2CError: Debug, + I2C: I2c, + K: IS31FL3731BacklightDriver + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDevice, + > crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDriver for IS31FL3731 +{ + type DriverWriteError = Error; + + async fn write( + &mut self, + buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError> { + let mut payload = [0; 144]; + + // Map the frame data to LED offsets and set the brightness of the LED in the payload + for (row_num, row) in buf.iter().enumerate() { + for (col_num, color) in row.iter().enumerate() { + for (component, val) in color.iter().enumerate() { + let offset = K::get_led_from_matrix_coordinates( + col_num as u8 + (component * K::LIGHTING_COLS) as u8, + row_num as u8, + ); + if offset != 255 { + payload[offset as usize] = gamma(val); } } } + } - self.all_pixels(&payload).await?; + self.all_pixels(&payload).await?; - Ok(()) - } + Ok(()) + } - type DriverEnableError = Error; + type DriverEnableError = Error; - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - self.sleep(false).await?; + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + self.sleep(false).await?; - Ok(()) - } + Ok(()) + } - type DriverDisableError = Error; + type DriverDisableError = Error; - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.sleep(true).await?; + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.sleep(true).await?; - Ok(()) - } + Ok(()) } } diff --git a/rumcake/src/drivers/mod.rs b/rumcake/src/drivers/mod.rs index e3f1178..a038972 100644 --- a/rumcake/src/drivers/mod.rs +++ b/rumcake/src/drivers/mod.rs @@ -23,61 +23,61 @@ pub struct SerialSplitDriver { } #[cfg(feature = "split-central")] -impl crate::split::drivers::CentralDeviceDriver for SerialSplitDriver { +impl crate::split::central::CentralDeviceDriver for SerialSplitDriver { type DriverError = D::Error; async fn receive_message_from_peripherals( &mut self, ) -> Result< crate::split::MessageToCentral, - crate::split::drivers::CentralDeviceError, + crate::split::central::CentralDeviceError, > { let mut buffer = [0; crate::split::MESSAGE_TO_CENTRAL_BUFFER_SIZE]; self.serial.read_exact(&mut buffer).await?; postcard::from_bytes_cobs(&mut buffer) - .map_err(crate::split::drivers::CentralDeviceError::DeserializationError) + .map_err(crate::split::central::CentralDeviceError::DeserializationError) } async fn broadcast_message_to_peripherals( &mut self, message: crate::split::MessageToPeripheral, - ) -> Result<(), crate::split::drivers::CentralDeviceError> { + ) -> Result<(), crate::split::central::CentralDeviceError> { let mut buffer = [0; crate::split::MESSAGE_TO_PERIPHERAL_BUFFER_SIZE]; postcard::to_slice_cobs(&message, &mut buffer) - .map_err(crate::split::drivers::CentralDeviceError::SerializationError)?; + .map_err(crate::split::central::CentralDeviceError::SerializationError)?; self.serial .write_all(&buffer) .await - .map_err(crate::split::drivers::CentralDeviceError::DriverError) + .map_err(crate::split::central::CentralDeviceError::DriverError) } } #[cfg(feature = "split-peripheral")] -impl crate::split::drivers::PeripheralDeviceDriver for SerialSplitDriver { +impl crate::split::peripheral::PeripheralDeviceDriver for SerialSplitDriver { type DriverError = D::Error; async fn send_message_to_central( &mut self, event: crate::split::MessageToCentral, - ) -> Result<(), crate::split::drivers::PeripheralDeviceError> { + ) -> Result<(), crate::split::peripheral::PeripheralDeviceError> { let mut buffer = [0; crate::split::MESSAGE_TO_CENTRAL_BUFFER_SIZE]; postcard::to_slice_cobs(&event, &mut buffer) - .map_err(crate::split::drivers::PeripheralDeviceError::SerializationError)?; + .map_err(crate::split::peripheral::PeripheralDeviceError::SerializationError)?; self.serial .write_all(&buffer) .await - .map_err(crate::split::drivers::PeripheralDeviceError::DriverError) + .map_err(crate::split::peripheral::PeripheralDeviceError::DriverError) } async fn receive_message_from_central( &mut self, ) -> Result< crate::split::MessageToPeripheral, - crate::split::drivers::PeripheralDeviceError, + crate::split::peripheral::PeripheralDeviceError, > { let mut buffer = [0; crate::split::MESSAGE_TO_PERIPHERAL_BUFFER_SIZE]; self.serial.read_exact(&mut buffer).await?; postcard::from_bytes_cobs(&mut buffer) - .map_err(crate::split::drivers::PeripheralDeviceError::DeserializationError) + .map_err(crate::split::peripheral::PeripheralDeviceError::DeserializationError) } } diff --git a/rumcake/src/drivers/nrf_ble.rs b/rumcake/src/drivers/nrf_ble.rs index 39a79c2..cec212e 100644 --- a/rumcake/src/drivers/nrf_ble.rs +++ b/rumcake/src/drivers/nrf_ble.rs @@ -1,8 +1,8 @@ //! Rumcake driver implementations for [`nrf-softdevice`]. //! //! This driver provides implementations for -//! [`CentralDeviceDriver`](`crate::split::drivers::CentralDeviceDriver`), and -//! [`PeripheralDeviceDriver`](`crate::split::drivers::PeripheralDeviceDriver`). +//! [`CentralDeviceDriver`](`crate::split::central::CentralDeviceDriver`), and +//! [`PeripheralDeviceDriver`](`crate::split::peripheral::PeripheralDeviceDriver`). //! //! To use this driver for split keyboards, central devices must pass the Bluetooth addresses of //! the peripherals to [`nrf_ble_central_task`], and peripheral devices must implement pass the @@ -24,13 +24,15 @@ pub mod central { use nrf_softdevice::ble::{central, Address, AddressType}; use nrf_softdevice::{RawError, Softdevice}; - use crate::hw::mcu::RawMutex; - use crate::split::drivers::{CentralDeviceDriver, CentralDeviceError}; + use crate::hw::platform::RawMutex; + use crate::split::central::{CentralDeviceDriver, CentralDeviceError}; use crate::split::{ MessageToCentral, MessageToPeripheral, MESSAGE_TO_CENTRAL_BUFFER_SIZE, MESSAGE_TO_PERIPHERAL_BUFFER_SIZE, }; + pub use rumcake_macros::setup_nrf_ble_split_central; + pub struct NRFBLECentralDriver<'a> { publisher: Publisher<'a, RawMutex, MessageToPeripheral, 4, 4, 1>, } @@ -230,13 +232,15 @@ pub mod peripheral { use nrf_softdevice::ble::{Address, AddressType}; use nrf_softdevice::Softdevice; - use crate::hw::mcu::{RawMutex, BLUETOOTH_ADVERTISING_MUTEX}; - use crate::split::drivers::{PeripheralDeviceDriver, PeripheralDeviceError}; + use crate::hw::platform::{RawMutex, BLUETOOTH_ADVERTISING_MUTEX}; + use crate::split::peripheral::{PeripheralDeviceDriver, PeripheralDeviceError}; use crate::split::{ MessageToCentral, MessageToPeripheral, MESSAGE_TO_CENTRAL_BUFFER_SIZE, MESSAGE_TO_PERIPHERAL_BUFFER_SIZE, }; + pub use rumcake_macros::setup_nrf_ble_split_peripheral; + pub struct NRFBLEPeripheralDriver {} pub static BLE_MESSAGES_TO_CENTRAL: Channel = Channel::new(); diff --git a/rumcake/src/drivers/ssd1306.rs b/rumcake/src/drivers/ssd1306.rs index 8a7914b..e318241 100644 --- a/rumcake/src/drivers/ssd1306.rs +++ b/rumcake/src/drivers/ssd1306.rs @@ -1,7 +1,7 @@ //! Rumcake driver implementations for [jamwaffles's SSD1306 driver](`ssd1306`). //! //! This driver provides implementations for -//! [`DisplayDriver`](`crate::display::drivers::DisplayDriver`), +//! [`DisplayDriver`](`crate::display::DisplayDriver`), //! //! To use this driver for the display feature, keyboards must implement //! [`Ssd1306I2cDisplayDriver`](display::Ssd1306I2cDisplayDriver). The result of [`setup_driver`] @@ -18,6 +18,8 @@ use ssd1306::{I2CDisplayInterface, Ssd1306}; pub use ssd1306 as driver; +pub use rumcake_macros::setup_ssd1306; + /// Create an instance of the SSD1306 driver with the provided I2C peripheral, and default size and /// rotation. pub fn setup_driver, S: DisplaySize>( @@ -32,62 +34,63 @@ pub fn setup_driver, S: DisplaySize>( display } -#[cfg(feature = "display")] -/// SSD1306 display driver implementations -pub mod display { - use core::fmt::Debug; - - use embedded_graphics::pixelcolor::BinaryColor; - use embedded_graphics::prelude::DrawTarget; - use embedded_hal::blocking::i2c::Write; - use ssd1306::mode::BufferedGraphicsMode; - use ssd1306::prelude::I2CInterface; - use ssd1306::rotation::DisplayRotation; - use ssd1306::size::DisplaySize; - use ssd1306::Ssd1306; - - use crate::display::drivers::{on_update_default, DisplayDriver, Orientation}; - use crate::display::DisplayDevice; - - /// A trait that keyboards must implement to use the SSD1306 driver for displaying information. - pub trait Ssd1306I2cDisplayDriver: DisplayDevice { - /// Update the SSD1306 screen. The frame buffer gets cleared before this function is called. - /// After this function is called, the display will be flushed. So, an implementor simply - /// needs to create the graphics to display on the screen, and does not need to clear the - /// frame buffer or flush the data to the screen. - async fn on_update( - display: &mut Ssd1306< - I2CInterface>, - S, - BufferedGraphicsMode, - >, - ) { +/// A trait that keyboards must implement to use the SSD1306 driver for displaying information. +pub trait Ssd1306I2cDisplayDriver { + /// Update the SSD1306 screen. The frame buffer gets cleared before this function is called. + /// After this function is called, the display will be flushed. So, an implementor simply + /// needs to create the graphics to display on the screen, and does not need to clear the + /// frame buffer or flush the data to the screen. + async fn on_update( + display: &mut Ssd1306< + I2CInterface>, + S, + BufferedGraphicsMode, + >, + ) { + #[cfg(feature = "display")] + { match display.rotation() { DisplayRotation::Rotate0 | DisplayRotation::Rotate180 => { - on_update_default(display, Orientation::Horizontal, 8).await; + crate::display::on_update_default( + display, + crate::display::Orientation::Horizontal, + 8, + ) + .await; } DisplayRotation::Rotate90 | DisplayRotation::Rotate270 => { - on_update_default(display, Orientation::Vertical, 12).await; + crate::display::on_update_default( + display, + crate::display::Orientation::Vertical, + 12, + ) + .await; } } } } +} - impl, S: DisplaySize, K: Ssd1306I2cDisplayDriver> DisplayDriver - for Ssd1306, S, BufferedGraphicsMode> - { - async fn on_update(&mut self) { - self.clear(BinaryColor::Off).unwrap(); - K::on_update(self).await; - self.flush().unwrap(); - } +#[cfg(feature = "display")] +impl< + DI: Write, + S: DisplaySize, + K: Ssd1306I2cDisplayDriver + crate::display::DisplayDevice, + > crate::display::DisplayDriver for Ssd1306, S, BufferedGraphicsMode> +{ + async fn on_update(&mut self) { + use embedded_graphics::prelude::DrawTarget; + self.clear(embedded_graphics::pixelcolor::BinaryColor::Off) + .unwrap(); + K::on_update(self).await; + self.flush().unwrap(); + } - async fn turn_off(&mut self) { - self.set_display_on(false).unwrap(); - } + async fn turn_off(&mut self) { + self.set_display_on(false).unwrap(); + } - async fn turn_on(&mut self) { - self.set_display_on(true).unwrap(); - } + async fn turn_on(&mut self) { + self.set_display_on(true).unwrap(); } } diff --git a/rumcake/src/drivers/ws2812_bitbang.rs b/rumcake/src/drivers/ws2812_bitbang.rs index 7c3b6d9..71afd88 100644 --- a/rumcake/src/drivers/ws2812_bitbang.rs +++ b/rumcake/src/drivers/ws2812_bitbang.rs @@ -2,10 +2,11 @@ //! instructions to simulate delay. //! //! This driver provides implementations for -//! [`UnderglowDriver`](`crate::underglow::drivers::UnderglowDriver`), -//! [`SimpleBacklightDriver`](`crate::backlight::drivers::SimpleBacklightDriver`), -//! [`SimpleBacklightMatrixDriver`](`crate::backlight::drivers::SimpleBacklightMatrixDriver`), and -//! [`RGBBacklightMatrixDriver`](`crate::backlight::drivers::RGBBacklightMatrixDriver`) +//! [`UnderglowDriver`](`crate::lighting::underglow::UnderglowDriver`), +//! [`SimpleBacklightDriver`](`crate::lighting::simple_backlight::SimpleBacklightDriver`), +//! [`SimpleBacklightMatrixDriver`](`crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDriver`), +//! and +//! [`RGBBacklightMatrixDriver`](`crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDriver`) //! //! To use this driver, pass the result of [`setup_driver`] to an underglow task, or backlight //! task. If you want to use this driver as a backlight matrix, you will need to implement @@ -13,13 +14,16 @@ use driver::Ws2812; use embedded_hal::digital::v2::OutputPin; -pub use rumcake_macros::ws2812_bitbang_pin; +use smart_leds::gamma; +use smart_leds::RGB8; + +pub use rumcake_macros::{ + setup_ws2812_bitbang, ws2812_get_led_from_matrix_coordinates as get_led_from_matrix_coordinates, +}; pub mod driver { use core::arch::arm::__nop as nop; - use crate::hw::mcu::SYSCLK; - use embassy_time::Duration; use embedded_hal::digital::v2::OutputPin; use smart_leds::RGB8; @@ -43,17 +47,19 @@ pub mod driver { } } - const NOP_FUDGE: f64 = 0.6; - - const TICK_CONV_FACTOR: f64 = (SYSCLK as u64 / gcd(SYSCLK as u64, 1_000_000_000)) as f64 - / (1_000_000_000 / gcd(SYSCLK as u64, 1_000_000_000)) as f64; - - pub struct Ws2812 { + /// WS2812 driver. Const parameter C represents the clock speed of your chip, and F represents + /// a fudge value. These const parameters determine together determine how many `nop` + /// instructions are generated to simulate delay. + pub struct Ws2812 { pin: P, } - impl Ws2812

{ - pub fn new(mut pin: P) -> Ws2812

{ + impl Ws2812 { + const NOP_FUDGE: f64 = 0.01 * F as f64; + const TICK_CONV_FACTOR: f64 = (C as u64 / gcd(C as u64, 1_000_000_000)) as f64 + / (1_000_000_000 / gcd(C as u64, 1_000_000_000)) as f64; + + pub fn new(mut pin: P) -> Ws2812 { pin.set_low().ok(); Self { pin } } @@ -63,26 +69,26 @@ pub mod driver { for _ in 0..8 { if data & 0x80 == 0x80 { self.pin.set_high().ok(); - for _ in 0..(T1H as f64 * TICK_CONV_FACTOR * NOP_FUDGE) as i32 { + for _ in 0..(T1H as f64 * Self::TICK_CONV_FACTOR * Self::NOP_FUDGE) as i32 { unsafe { nop(); } } self.pin.set_low().ok(); - for _ in 0..(T1L as f64 * TICK_CONV_FACTOR * NOP_FUDGE) as i32 { + for _ in 0..(T1L as f64 * Self::TICK_CONV_FACTOR * Self::NOP_FUDGE) as i32 { unsafe { nop(); } } } else { self.pin.set_high().ok(); - for _ in 0..(T0H as f64 * TICK_CONV_FACTOR * NOP_FUDGE) as i32 { + for _ in 0..(T0H as f64 * Self::TICK_CONV_FACTOR * Self::NOP_FUDGE) as i32 { unsafe { nop(); } } self.pin.set_low().ok(); - for _ in 0..(T0L as f64 * TICK_CONV_FACTOR * NOP_FUDGE) as i32 { + for _ in 0..(T0L as f64 * Self::TICK_CONV_FACTOR * Self::NOP_FUDGE) as i32 { unsafe { nop(); } @@ -100,205 +106,207 @@ pub mod driver { } // Reset time - // Technically this isn't needed as long as the user sets a reasonable FPS value, but we'll keep it anyways. - embassy_time::block_for(Duration::from_micros(RES)); + for _ in 0..(RES as f64 * Self::TICK_CONV_FACTOR * Self::NOP_FUDGE) as i32 { + unsafe { + nop(); + } + } } } } /// Create an instance of the WS2812 bitbang driver with the provided output pin. -pub fn setup_driver(output_pin: impl OutputPin) -> Ws2812 { +pub fn setup_driver( + output_pin: impl OutputPin, +) -> Ws2812 { Ws2812::new(output_pin) } -#[cfg(feature = "underglow")] -/// WS2812 underglow driver implementations -pub mod underglow { - use embedded_hal::digital::v2::OutputPin; - use smart_leds::gamma; - use smart_leds::RGB8; - - use super::driver::Ws2812; - use crate::underglow::drivers::UnderglowDriver; - use crate::underglow::UnderglowDevice; - - impl UnderglowDriver for Ws2812

- where - [(); K::NUM_LEDS]:, - { - type DriverWriteError = (); +/// A trait that keyboards must implement to use the WS2812 driver for simple backlighting. +pub trait WS2812BitbangSimpleBacklightDriver { + /// Number of WS2812 LEDs powered by this driver. + const NUM_LEDS: usize; +} - async fn write( - &mut self, - colors: impl Iterator, - ) -> Result<(), Self::DriverWriteError> { - self.write_colors(gamma(colors)); +/// A trait that keyboards must implement to use the WS2812 driver for backlighting. +pub trait WS2812BitbangBacklightMatrixDriver { + /// Convert matrix coordinates in the form of (col, row) to a WS2812 LED index. + /// + /// It is recommended to use [`ws2812_get_led_from_matrix_coordinates`] to implement this + /// function. + fn get_led_from_matrix_coordinates(x: u8, y: u8) -> Option; +} - Ok(()) - } +#[cfg(feature = "underglow")] +impl + crate::lighting::underglow::UnderglowDriver for Ws2812 +where + [(); K::NUM_LEDS]:, +{ + type DriverWriteError = (); + + async fn write( + &mut self, + colors: impl Iterator, + ) -> Result<(), Self::DriverWriteError> { + self.write_colors(gamma(colors)); + + Ok(()) + } - type DriverEnableError = (); + type DriverEnableError = (); - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - // Don't need to do anything special, just let the next tick() get called. - Ok(()) - } + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + // Don't need to do anything special, just let the next tick() get called. + Ok(()) + } - type DriverDisableError = (); + type DriverDisableError = (); - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.write_colors([(0, 0, 0).into(); { K::NUM_LEDS }].iter().cloned()); - Ok(()) - } + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.write_colors([(0, 0, 0).into(); { K::NUM_LEDS }].iter().cloned()); + Ok(()) } } -#[cfg(feature = "_backlight")] -/// WS2812 underglow driver implementations -pub mod backlight { - use embedded_hal::digital::v2::OutputPin; - use smart_leds::gamma; - use smart_leds::RGB8; - - use super::driver::Ws2812; - use crate::backlight::drivers::SimpleBacklightDriver; - use crate::backlight::drivers::{RGBBacklightMatrixDriver, SimpleBacklightMatrixDriver}; - use crate::backlight::BacklightMatrixDevice; - - pub use rumcake_macros::ws2812_get_led_from_matrix_coordinates as get_led_from_matrix_coordinates; - - /// A trait that keyboards must implement to use the WS2812 driver for backlighting. - pub trait WS2812BitbangBacklightDriver: BacklightMatrixDevice { - /// Convert matrix coordinates in the form of (col, row) to a WS2812 LED index. - /// - /// It is recommended to use [`ws2812_get_led_from_matrix_coordinates`] to implement this function. - fn get_led_from_matrix_coordinates(x: u8, y: u8) -> Option; +#[cfg(feature = "simple-backlight")] +impl< + const C: u32, + const F: u8, + P: OutputPin, + K: WS2812BitbangSimpleBacklightDriver + + crate::lighting::simple_backlight::SimpleBacklightDevice, + > crate::lighting::simple_backlight::SimpleBacklightDriver for Ws2812 +where + [(); K::NUM_LEDS]:, +{ + type DriverWriteError = (); + + async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError> { + let brightnesses = [(brightness, brightness, brightness).into(); K::NUM_LEDS]; + + self.write_colors(gamma(brightnesses.iter().cloned())); + + Ok(()) } - impl SimpleBacklightDriver for Ws2812

- where - [(); K::LIGHTING_ROWS * K::LIGHTING_COLS]:, - { - type DriverWriteError = (); + type DriverEnableError = (); - async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError> { - let brightnesses = [(brightness, brightness, brightness).into(); { - K::LIGHTING_ROWS * K::LIGHTING_COLS - }]; - - self.write_colors(gamma(brightnesses.iter().cloned())); - - Ok(()) - } - - type DriverEnableError = (); - - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - // Don't need to do anything special, just let the next tick() get called. - Ok(()) - } + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + // Don't need to do anything special, just let the next tick() get called. + Ok(()) + } - type DriverDisableError = (); + type DriverDisableError = (); - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.write_colors( - [(0, 0, 0).into(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }] - .iter() - .cloned(), - ); - Ok(()) - } + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.write_colors([(0, 0, 0).into(); K::NUM_LEDS].iter().cloned()); + Ok(()) } +} - impl SimpleBacklightMatrixDriver for Ws2812

- where - [(); K::LIGHTING_ROWS * K::LIGHTING_COLS]:, - { - type DriverWriteError = (); - - async fn write( - &mut self, - buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError> { - let mut brightnesses = [RGB8::default(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }]; - - for (row_num, row) in buf.iter().enumerate() { - for (col_num, val) in row.iter().enumerate() { - if let Some(offset) = - K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8) - { - brightnesses[offset as usize] = (*val, *val, *val).into(); - } +#[cfg(feature = "simple-backlight-matrix")] +impl< + const C: u32, + const F: u8, + P: OutputPin, + K: WS2812BitbangBacklightMatrixDriver + + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDevice, + > crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixDriver for Ws2812 +where + [(); K::LIGHTING_ROWS * K::LIGHTING_COLS]:, +{ + type DriverWriteError = (); + + async fn write( + &mut self, + buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError> { + let mut brightnesses = [RGB8::default(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }]; + + for (row_num, row) in buf.iter().enumerate() { + for (col_num, val) in row.iter().enumerate() { + if let Some(offset) = + K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8) + { + brightnesses[offset as usize] = (*val, *val, *val).into(); } } + } - self.write_colors(gamma(brightnesses.iter().cloned())); + self.write_colors(gamma(brightnesses.iter().cloned())); - Ok(()) - } + Ok(()) + } - type DriverEnableError = (); + type DriverEnableError = (); - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - // Don't need to do anything special, just let the next tick() get called. - Ok(()) - } + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + // Don't need to do anything special, just let the next tick() get called. + Ok(()) + } - type DriverDisableError = (); + type DriverDisableError = (); - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.write_colors( - [(0, 0, 0).into(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }] - .iter() - .cloned(), - ); - Ok(()) - } + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.write_colors( + [(0, 0, 0).into(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }] + .iter() + .cloned(), + ); + Ok(()) } +} - impl RGBBacklightMatrixDriver for Ws2812

- where - [(); K::LIGHTING_ROWS * K::LIGHTING_COLS]:, - { - type DriverWriteError = (); - - async fn write( - &mut self, - buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], - ) -> Result<(), Self::DriverWriteError> { - let mut colors = [RGB8::default(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }]; - - for (row_num, row) in buf.iter().enumerate() { - for (col_num, val) in row.iter().enumerate() { - if let Some(offset) = - K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8) - { - colors[offset as usize] = *val; - } +#[cfg(feature = "rgb-backlight-matrix")] +impl< + const C: u32, + const F: u8, + P: OutputPin, + K: WS2812BitbangBacklightMatrixDriver + + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDevice, + > crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixDriver for Ws2812 +where + [(); K::LIGHTING_ROWS * K::LIGHTING_COLS]:, +{ + type DriverWriteError = (); + + async fn write( + &mut self, + buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError> { + let mut colors = [RGB8::default(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }]; + + for (row_num, row) in buf.iter().enumerate() { + for (col_num, val) in row.iter().enumerate() { + if let Some(offset) = + K::get_led_from_matrix_coordinates(col_num as u8, row_num as u8) + { + colors[offset as usize] = *val; } } + } - self.write_colors(gamma(colors.iter().cloned())); + self.write_colors(gamma(colors.iter().cloned())); - Ok(()) - } + Ok(()) + } - type DriverEnableError = (); + type DriverEnableError = (); - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { - // Don't need to do anything special, just let the next tick() get called. - Ok(()) - } + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError> { + // Don't need to do anything special, just let the next tick() get called. + Ok(()) + } - type DriverDisableError = (); + type DriverDisableError = (); - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { - self.write_colors( - [(0, 0, 0).into(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }] - .iter() - .cloned(), - ); - Ok(()) - } + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError> { + self.write_colors( + [(0, 0, 0).into(); { K::LIGHTING_ROWS * K::LIGHTING_COLS }] + .iter() + .cloned(), + ); + Ok(()) } } diff --git a/rumcake/src/hw/mcu/nrf.rs b/rumcake/src/hw/mcu/nrf.rs index 9332121..0ad6604 100644 --- a/rumcake/src/hw/mcu/nrf.rs +++ b/rumcake/src/hw/mcu/nrf.rs @@ -1,8 +1,8 @@ //! Utilities for interfacing with the hardware, specific to nRF5x-based MCUs. //! -//! Note that the contents of this nRF5x-version of `mcu` module may share some of the same members -//! of other versions of the `mcu` module. This is the case so that parts of `rumcake` can remain -//! hardware-agnostic. +//! Note that the contents of this nRF5x-version of `platform` module may share some of the same +//! members of other versions of the `platform` module. This is the case so that parts of `rumcake` +//! can remain hardware-agnostic. use core::cell::RefCell; use core::mem::MaybeUninit; @@ -30,7 +30,9 @@ use crate::hw::BATTERY_LEVEL_STATE; use crate::keyboard::MatrixSampler; pub use rumcake_macros::{ - input_pin, output_pin, setup_adc_sampler, setup_buffered_uarte, setup_i2c, setup_i2c_blocking, + nrf_input_pin as input_pin, nrf_output_pin as output_pin, + nrf_setup_adc_sampler as setup_adc_sampler, nrf_setup_buffered_uarte as setup_buffered_uarte, + nrf_setup_i2c as setup_i2c, }; pub use embassy_nrf; diff --git a/rumcake/src/hw/mcu/rp.rs b/rumcake/src/hw/mcu/rp.rs index 83935a5..ef87fff 100644 --- a/rumcake/src/hw/mcu/rp.rs +++ b/rumcake/src/hw/mcu/rp.rs @@ -1,8 +1,8 @@ //! Utilities for interfacing with the hardware, specific to RP-based MCUs. //! -//! Note that the contents of this RP-version of `mcu` module may share some of the same members -//! of other versions of the `mcu` module. This is the case so that parts of `rumcake` can remain -//! hardware-agnostic. +//! Note that the contents of this RP-version of `platform` module may share some of the same +//! members of other versions of the `platform` module. This is the case so that parts of `rumcake` +//! can remain hardware-agnostic. use core::cell::RefCell; use core::ops::DerefMut; @@ -21,7 +21,9 @@ use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::blocking_mutex::ThreadModeMutex; pub use rumcake_macros::{ - input_pin, output_pin, setup_adc_sampler, setup_buffered_uart, setup_dma_channel, setup_i2c, + rp_input_pin as input_pin, rp_output_pin as output_pin, + rp_setup_adc_sampler as setup_adc_sampler, rp_setup_buffered_uart as setup_buffered_uart, + rp_setup_i2c as setup_i2c, }; pub use embassy_rp; @@ -182,8 +184,9 @@ pub type Flash<'a, const FLASH_SIZE: usize> = HALFlash<'a, FLASH, Async, FLASH_S /// Construct an instance of [`Flash`]. This usually needs to be passed to /// [`crate::storage::Database::setup`], so that your device can use storage features. pub fn setup_internal_flash<'a, const FLASH_SIZE: usize>( - channel: impl crate::hw::mcu::embassy_rp::Peripheral

- + 'a, + channel: impl crate::hw::platform::embassy_rp::Peripheral< + P = impl crate::hw::platform::embassy_rp::dma::Channel, + > + 'a, ) -> Flash<'a, FLASH_SIZE> { unsafe { Flash::new(FLASH::steal(), channel) } } diff --git a/rumcake/src/hw/mcu/stm32.rs b/rumcake/src/hw/mcu/stm32.rs index 2c611d8..2b48a38 100644 --- a/rumcake/src/hw/mcu/stm32.rs +++ b/rumcake/src/hw/mcu/stm32.rs @@ -1,8 +1,8 @@ //! Utilities for interfacing with the hardware, specific to STM32-based MCUs. //! -//! Note that the contents of this STM32-version of `mcu` module may share some of the same members -//! of other versions of the `mcu` module. This is the case so that parts of `rumcake` can remain -//! hardware-agnostic. +//! Note that the contents of this STM32-version of `platform` module may share some of the same +//! members of other versions of the `platform` module. This is the case so that parts of `rumcake` +//! can remain hardware-agnostic. use core::cell::RefCell; use core::future::poll_fn; @@ -24,7 +24,9 @@ use embassy_sync::blocking_mutex::ThreadModeMutex; use static_cell::StaticCell; pub use rumcake_macros::{ - input_pin, output_pin, setup_adc_sampler, setup_buffered_uart, setup_i2c, + stm32_input_pin as input_pin, stm32_output_pin as output_pin, + stm32_setup_adc_sampler as setup_adc_sampler, stm32_setup_buffered_uart as setup_buffered_uart, + stm32_setup_i2c as setup_i2c, }; pub use embassy_stm32; diff --git a/rumcake/src/hw/mod.rs b/rumcake/src/hw/mod.rs index 61bdc81..19940bb 100644 --- a/rumcake/src/hw/mod.rs +++ b/rumcake/src/hw/mod.rs @@ -13,25 +13,29 @@ compile_error!("Please enable only one chip feature flag."); #[cfg_attr(feature = "stm32", path = "mcu/stm32.rs")] #[cfg_attr(feature = "nrf", path = "mcu/nrf.rs")] #[cfg_attr(feature = "rp", path = "mcu/rp.rs")] -pub mod mcu; +pub mod platform; -use crate::hw::mcu::jump_to_bootloader; +use crate::hw::platform::jump_to_bootloader; use crate::State; use core::cell::UnsafeCell; use core::mem::MaybeUninit; use core::ptr::read_volatile; use core::ptr::write_volatile; +use defmt::info; use embassy_futures::select; +use embassy_sync::channel::Channel; use embassy_sync::signal::Signal; use embassy_time::Timer; use embedded_hal::digital::v2::OutputPin; -use mcu::RawMutex; +use platform::RawMutex; +use usbd_human_interface_device::device::consumer::MultipleConsumerReport; +use usbd_human_interface_device::device::keyboard::NKROBootKeyboardReport; -/// State that contains the current battery level. `rumcake` may or may not use this -/// static internally, depending on what MCU is being used. The contents of this state -/// is usually set by a task in the [`mcu`] module. For example, on nRF5x-based MCUs, -/// this is controlled by a task called `adc_task`. +/// State that contains the current battery level. `rumcake` may or may not use this static +/// internally, depending on what MCU is being used. The contents of this state is usually set by a +/// task in the [`platform`] module. For example, on nRF5x-based MCUs, this is controlled by a task +/// called `adc_task`. pub static BATTERY_LEVEL_STATE: State = State::new( 100, &[ @@ -45,6 +49,7 @@ pub static BATTERY_LEVEL_STATE: State = State::new( /// Possible settings used to determine how the firmware will choose the destination for HID /// reports. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] pub enum OutputMode { Usb, Bluetooth, @@ -92,42 +97,90 @@ pub(crate) static OUTPUT_MODE_STATE_LISTENER: Signal = Signal::new pub(crate) static USB_RUNNING_STATE_LISTENER: Signal = Signal::new(); pub(crate) static BLUETOOTH_CONNECTED_STATE_LISTENER: Signal = Signal::new(); +pub(crate) static HARDWARE_COMMAND_CHANNEL: Channel = Channel::new(); + +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +/// An enumeration of possible commands that will be processed by the bluetooth task. +pub enum HardwareCommand { + /// Switch between USB and Bluetooth operation. + /// + /// This will **NOT** disconnect your keyboard from your host device. It + /// will simply determine which device the HID reports get sent to. + ToggleOutput = 0, + /// Switch to USB operation. + /// + /// If your keyboard is connected to a bluetooth device, this will **NOT** disconnect your + /// keyboard from it. It will simply output the HID reports to the connected USB device. + OutputUSB = 1, + /// Switch to bluetooth operation. + /// + /// If your keyboard is connected to a USB device, this will **NOT** disconnect your keyboard + /// from it. It will simply output the HID reports to the connected bluetooth device. + OutputBluetooth = 2, +} + #[rumcake_macros::task] pub async fn output_switcher() { - // This task doesn't need to run if only one of USB or Bluetooth is enabled. - loop { - let output = match OUTPUT_MODE_STATE.get().await { - #[cfg(feature = "usb")] - OutputMode::Usb => { - if crate::usb::USB_RUNNING_STATE.get().await { - Some(HIDOutput::Usb) - } else { - None + // This task doesn't need to run if only one of USB or Bluetooth is enabled, and there are no + // HardwareCommand members on the user's layout. + let switcher_fut = async { + loop { + let output = match OUTPUT_MODE_STATE.get().await { + #[cfg(feature = "usb")] + OutputMode::Usb => { + if crate::usb::USB_RUNNING_STATE.get().await { + Some(HIDOutput::Usb) + } else { + None + } } - } - #[cfg(feature = "bluetooth")] - OutputMode::Bluetooth => { - if crate::bluetooth::BLUETOOTH_CONNECTED_STATE.get().await { - Some(HIDOutput::Bluetooth) - } else { - None + #[cfg(feature = "bluetooth")] + OutputMode::Bluetooth => { + if crate::bluetooth::BLUETOOTH_CONNECTED_STATE.get().await { + Some(HIDOutput::Bluetooth) + } else { + None + } } - } - #[allow(unreachable_patterns)] - _ => None, - }; + #[allow(unreachable_patterns)] + _ => None, + }; - CURRENT_OUTPUT_STATE.set(output).await; - defmt::info!("[HW] Output updated: {:?}", defmt::Debug2Format(&output)); + CURRENT_OUTPUT_STATE.set(output).await; + info!("[HW] Output updated: {:?}", defmt::Debug2Format(&output)); - // Wait for a change in state before attempting to update the output again. - select::select3( - USB_RUNNING_STATE_LISTENER.wait(), - BLUETOOTH_CONNECTED_STATE_LISTENER.wait(), - OUTPUT_MODE_STATE_LISTENER.wait(), - ) - .await; - } + // Wait for a change in state before attempting to update the output again. + select::select3( + USB_RUNNING_STATE_LISTENER.wait(), + BLUETOOTH_CONNECTED_STATE_LISTENER.wait(), + OUTPUT_MODE_STATE_LISTENER.wait(), + ) + .await; + } + }; + + let command_fut = async { + loop { + match HARDWARE_COMMAND_CHANNEL.receive().await { + HardwareCommand::ToggleOutput => { + OUTPUT_MODE_STATE.set(match OUTPUT_MODE_STATE.get().await { + OutputMode::Usb => OutputMode::Bluetooth, + OutputMode::Bluetooth => OutputMode::Usb, + }); + } + HardwareCommand::OutputUSB => { + OUTPUT_MODE_STATE.set(OutputMode::Usb); + } + HardwareCommand::OutputBluetooth => { + OUTPUT_MODE_STATE.set(OutputMode::Bluetooth); + } + } + } + }; + + select::select(switcher_fut, command_fut).await; + info!("[HW] Output switching task has failed. This should not happen."); } const BOOTLOADER_MAGIC: u32 = 0xDEADBEEF; @@ -209,3 +262,29 @@ impl, const P: usize> Multiplexer { Ok(()) } } + +pub trait HIDDevice { + fn get_keyboard_report_send_channel() -> &'static Channel { + static KEYBOARD_REPORT_HID_SEND_CHANNEL: Channel = + Channel::new(); + &KEYBOARD_REPORT_HID_SEND_CHANNEL + } + + fn get_consumer_report_send_channel() -> &'static Channel { + static CONSUMER_REPORT_HID_SEND_CHANNEL: Channel = + Channel::new(); + &CONSUMER_REPORT_HID_SEND_CHANNEL + } + + #[cfg(feature = "via")] + fn get_via_hid_send_channel() -> &'static Channel { + static VIA_REPORT_HID_SEND_CHANNEL: Channel = Channel::new(); + &VIA_REPORT_HID_SEND_CHANNEL + } + + #[cfg(feature = "via")] + fn get_via_hid_receive_channel() -> &'static Channel { + static VIA_REPORT_HID_RECEIVE_CHANNEL: Channel = Channel::new(); + &VIA_REPORT_HID_RECEIVE_CHANNEL + } +} diff --git a/rumcake/src/keyboard.rs b/rumcake/src/keyboard.rs index 60709fd..8751d6b 100644 --- a/rumcake/src/keyboard.rs +++ b/rumcake/src/keyboard.rs @@ -8,7 +8,7 @@ use core::ops::Range; use defmt::{debug, info, warn, Debug2Format}; use embassy_sync::channel::Channel; -use embassy_sync::mutex::{Mutex, MutexGuard}; +use embassy_sync::mutex::Mutex; use embassy_sync::pubsub::{PubSubBehavior, PubSubChannel}; use embassy_time::{Duration, Ticker, Timer}; use embedded_hal::digital::v2::{InputPin, OutputPin}; @@ -26,8 +26,8 @@ use usbd_human_interface_device::{ #[cfg(feature = "media-keycodes")] pub use usbd_human_interface_device::page::Consumer; -use crate::hw::mcu::RawMutex; -use crate::hw::CURRENT_OUTPUT_STATE; +use crate::hw::platform::RawMutex; +use crate::hw::{HIDDevice, CURRENT_OUTPUT_STATE}; pub use rumcake_macros::{ build_analog_matrix, build_direct_pin_matrix, build_layout, build_standard_matrix, remap_matrix, @@ -53,6 +53,14 @@ pub trait Keyboard { /// A trait that must be implemented on a device that communicates with the host device. pub trait KeyboardLayout { + /// Get a reference to a channel that can receive matrix events from other tasks to be + /// processed into keycodes. + fn get_matrix_events_channel() -> &'static Channel { + static POLLED_EVENTS_CHANNEL: Channel = Channel::new(); + + &POLLED_EVENTS_CHANNEL + } + const NUM_ENCODERS: usize = 0; // Only for VIA compatibility, no proper encoder support. This is the default if not set in QMK /// Number of columns in the layout. @@ -89,33 +97,46 @@ pub trait KeyboardLayout { /// `press` is set to `true` if the event was a key press. Otherwise, it will be `false`. `id` /// corresponds to the `id` used in your keyboard layout. fn on_custom_keycode(_id: u8, _press: bool) {} + + #[cfg(feature = "simple-backlight")] + type SimpleBacklightDeviceType: crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice = + crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "simple-backlight-matrix")] + type SimpleBacklightMatrixDeviceType: crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice = crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "rgb-backlight-matrix")] + type RGBBacklightMatrixDeviceType: crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice = crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "underglow")] + type UnderglowDeviceType: crate::lighting::underglow::private::MaybeUnderglowDevice = + crate::lighting::private::EmptyLightingDevice; } /// A mutex-guaraded [`keyberon::layout::Layout`]. This also stores the original layout, so that it /// can be reset to it's initial state if modifications are made to it. pub struct Layout { - layout: once_cell::sync::OnceCell>>, + pub(crate) layout: Mutex>, } impl Layout { - pub const fn new() -> Self { + pub const fn new(layout: KeyberonLayout) -> Self { Self { - layout: once_cell::sync::OnceCell::new(), + layout: Mutex::new(layout), } } - - pub fn init(&self, layers: &'static mut Layers) { - self.layout - .get_or_init(|| Mutex::new(KeyberonLayout::new(layers))); - } - - pub async fn lock(&self) -> MutexGuard> { - self.layout.get().unwrap().lock().await - } } /// A trait that must be implemented for any device that needs to poll a switch matrix. pub trait KeyboardMatrix { + /// The layout to send matrix events to. + type Layout: private::MaybeKeyboardLayout = private::EmptyKeyboardLayout; + + #[cfg(feature = "split-peripheral")] + /// The peripheral device in a split keyboard setup to send matrix events to. + type PeripheralDeviceType: crate::split::peripheral::private::MaybePeripheralDevice = + crate::split::peripheral::private::EmptyPeripheralDevice; + /// Debounce setting. const DEBOUNCE_MS: u16 = 5; @@ -195,37 +216,40 @@ pub fn setup_analog_keyboard_matrix { @@ -336,15 +360,13 @@ where } } -/// Channel with keyboard events polled from the swtich matrix -/// -/// The coordinates received will be remapped according to the implementation of -/// [`KeyboardMatrix::remap_to_layout`]. -pub(crate) static POLLED_EVENTS_CHANNEL: Channel = Channel::new(); - #[rumcake_macros::task] pub async fn matrix_poll(_k: K) { let matrix = K::get_matrix(); + let layout_channel = ::get_matrix_events_channel(); + + #[cfg(feature = "split-peripheral")] + let peripheral_channel = ::get_matrix_events_channel(); loop { { @@ -366,7 +388,14 @@ pub async fn matrix_poll(_k: K) { Debug2Format(&remapped_event) ); - POLLED_EVENTS_CHANNEL.send(remapped_event).await; + if let Some(layout_channel) = layout_channel { + layout_channel.send(remapped_event).await + }; + + #[cfg(feature = "split-peripheral")] + if let Some(peripheral_channel) = peripheral_channel { + peripheral_channel.send(remapped_event).await + }; } } Timer::after(Duration::from_micros(500)).await; @@ -382,24 +411,8 @@ pub async fn matrix_poll(_k: K) { /// slots will be used. pub static MATRIX_EVENTS: PubSubChannel = PubSubChannel::new(); -/// Channel for sending NKRO HID keyboard reports. -/// -/// Channel messages should be consumed by the bluetooth task or USB task, so user-level code -/// should **not** attempt to receive messages from the channel, otherwise commands may not be -/// processed appropriately. You should only send to this channel. -pub static KEYBOARD_REPORT_HID_SEND_CHANNEL: Channel = - Channel::new(); - -/// Channel for sending consumer HID reports. -/// -/// Channel messages should be consumed by the bluetooth task or USB task, so user-level code -/// should **not** attempt to receive messages from the channel, otherwise commands may not be -/// processed appropriately. You should only send to this channel. -pub static CONSUMER_REPORT_HID_SEND_CHANNEL: Channel = - Channel::new(); - #[rumcake_macros::task] -pub async fn layout_collect(_k: K) +pub async fn layout_collect(_k: K) where [(); K::LAYERS]:, [(); K::LAYOUT_COLS]:, @@ -412,12 +425,18 @@ where let mut codes = [Consumer::Unassigned; 4]; let mut ticker = Ticker::every(Duration::from_millis(1)); + let matrix_channel = K::get_matrix_events_channel(); + + #[cfg(feature = "media-keycodes")] + let consumer_report_channel = K::get_consumer_report_send_channel(); + + let keyboard_report = K::get_keyboard_report_send_channel(); loop { let keys = { - let mut layout = layout.lock().await; + let mut layout = layout.layout.lock().await; - if let Ok(event) = POLLED_EVENTS_CHANNEL.try_receive() { + if let Ok(event) = matrix_channel.try_receive() { layout.event(event); MATRIX_EVENTS.publish_immediate(event); // Just immediately publish since we don't want to hold up any key events to be converted into keycodes. }; @@ -439,53 +458,49 @@ where { *c = keycode; } - CONSUMER_REPORT_HID_SEND_CHANNEL + consumer_report_channel .send(MultipleConsumerReport { codes }) .await; } #[cfg(feature = "underglow")] Keycode::Underglow(command) => { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(command) - .await; - #[cfg(feature = "storage")] - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SaveConfig) - .await; + if let Some(channel) = ::get_command_channel() { + channel.send(command).await; + #[cfg(feature = "storage")] + channel + .send(crate::lighting::underglow::UnderglowCommand::SaveConfig) + .await; + } } #[cfg(feature = "simple-backlight")] Keycode::SimpleBacklight(command) => { - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; - #[cfg(feature = "storage")] - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight::animations::BacklightCommand::SaveConfig) + if let Some(channel) = ::get_command_channel() { + channel.send(command).await; + #[cfg(feature = "storage")] + channel.send(crate::lighting::simple_backlight::SimpleBacklightCommand::SaveConfig) .await; + } } #[cfg(feature = "simple-backlight-matrix")] Keycode::SimpleBacklightMatrix(command) => { - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; - #[cfg(feature = "storage")] - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::SaveConfig) + if let Some(channel) = ::get_command_channel() { + channel.send(command).await; + #[cfg(feature = "storage")] + channel.send(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::SaveConfig) .await; + } } #[cfg(feature = "rgb-backlight-matrix")] Keycode::RGBBacklightMatrix(command) => { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; - #[cfg(feature = "storage")] - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SaveConfig) + if let Some(channel) = ::get_command_channel() { + channel.send(command).await; + #[cfg(feature = "storage")] + channel.send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SaveConfig) .await; + } } - #[cfg(feature = "bluetooth")] - Keycode::Bluetooth(command) => { - crate::bluetooth::BLUETOOTH_COMMAND_CHANNEL + Keycode::Hardware(command) => { + crate::hw::HARDWARE_COMMAND_CHANNEL .send(command) .await; } @@ -499,7 +514,7 @@ where if let Some(c) = codes.iter_mut().find(|c| **c == keycode) { *c = Consumer::Unassigned; } - CONSUMER_REPORT_HID_SEND_CHANNEL + consumer_report_channel .send(MultipleConsumerReport { codes }) .await; } @@ -530,7 +545,7 @@ where // are both not connected, this channel can become filled, so we discard the report in // that case. if CURRENT_OUTPUT_STATE.get().await.is_some() { - KEYBOARD_REPORT_HID_SEND_CHANNEL + keyboard_report .send(NKROBootKeyboardReport::new(keys)) .await; } else { @@ -541,3 +556,27 @@ where ticker.next().await; } } + +pub(crate) mod private { + use embassy_sync::channel::Channel; + use keyberon::layout::Event; + + use crate::hw::platform::RawMutex; + + use super::KeyboardLayout; + + pub struct EmptyKeyboardLayout; + impl MaybeKeyboardLayout for EmptyKeyboardLayout {} + + pub trait MaybeKeyboardLayout { + fn get_matrix_events_channel() -> Option<&'static Channel> { + None + } + } + + impl MaybeKeyboardLayout for T { + fn get_matrix_events_channel() -> Option<&'static Channel> { + Some(T::get_matrix_events_channel()) + } + } +} diff --git a/rumcake/src/lib.rs b/rumcake/src/lib.rs index 6bd7f9f..86225f0 100644 --- a/rumcake/src/lib.rs +++ b/rumcake/src/lib.rs @@ -8,7 +8,7 @@ use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::signal::Signal; -use crate::hw::mcu::RawMutex; +use crate::hw::platform::RawMutex; pub(crate) trait StaticArray { const LEN: usize; @@ -109,12 +109,6 @@ impl<'a, T: Clone + PartialEq> State<'a, T> { } } -// TODO: remove re-exports - -pub use embedded_hal; -pub use embedded_hal_async; -pub use embedded_io_async; -pub use embedded_storage_async; pub use keyberon; pub use once_cell; @@ -126,11 +120,8 @@ mod math; #[cfg(feature = "storage")] pub mod storage; -#[cfg(feature = "underglow")] -pub mod underglow; - -#[cfg(feature = "_backlight")] -pub mod backlight; +#[cfg(feature = "lighting")] +pub mod lighting; #[cfg(feature = "usb")] pub mod usb; @@ -158,25 +149,10 @@ pub mod tasks { pub use crate::hw::__output_switcher; pub use crate::keyboard::{__layout_collect, __matrix_poll}; - #[cfg(feature = "simple-backlight")] - pub use crate::backlight::simple_backlight::__simple_backlight_task; - #[cfg(all(feature = "storage", feature = "simple-backlight"))] - pub use crate::backlight::simple_backlight::storage::__simple_backlight_storage_task; - - #[cfg(feature = "simple-backlight-matrix")] - pub use crate::backlight::simple_backlight_matrix::__simple_backlight_matrix_task; - #[cfg(all(feature = "storage", feature = "simple-backlight-matrix"))] - pub use crate::backlight::simple_backlight_matrix::storage::__simple_backlight_matrix_storage_task; - - #[cfg(feature = "rgb-backlight-matrix")] - pub use crate::backlight::rgb_backlight_matrix::__rgb_backlight_matrix_task; - #[cfg(all(feature = "storage", feature = "rgb-backlight-matrix"))] - pub use crate::backlight::rgb_backlight_matrix::storage::__rgb_backlight_matrix_storage_task; - - #[cfg(feature = "underglow")] - pub use crate::underglow::__underglow_task; - #[cfg(all(feature = "underglow", feature = "storage"))] - pub use crate::underglow::storage::__underglow_storage_task; + #[cfg(all(feature = "lighting", feature = "storage"))] + pub use crate::lighting::__lighting_storage_task; + #[cfg(feature = "lighting")] + pub use crate::lighting::__lighting_task; #[cfg(feature = "display")] pub use crate::display::__display_task; @@ -190,13 +166,9 @@ pub mod tasks { pub use crate::usb::__usb_hid_via_write_task; #[cfg(feature = "via")] pub use crate::via::__via_process_task; - #[cfg(all(feature = "via", feature = "storage"))] - pub use crate::via::storage::__via_storage_task; #[cfg(feature = "vial")] pub use crate::vial::__vial_process_task; - #[cfg(all(feature = "vial", feature = "storage"))] - pub use crate::vial::storage::__vial_storage_task; #[cfg(feature = "split-central")] pub use crate::split::central::__central_task; @@ -205,10 +177,10 @@ pub mod tasks { pub use crate::split::peripheral::__peripheral_task; #[cfg(feature = "nrf")] - pub use crate::hw::mcu::__adc_task; + pub use crate::hw::platform::__adc_task; #[cfg(feature = "nrf-ble")] - pub use crate::hw::mcu::__softdevice_task; + pub use crate::hw::platform::__softdevice_task; #[cfg(all(feature = "nrf", feature = "bluetooth"))] pub use crate::bluetooth::nrf_ble::__nrf_ble_task; diff --git a/rumcake/src/lighting/mod.rs b/rumcake/src/lighting/mod.rs new file mode 100644 index 0000000..b7c9225 --- /dev/null +++ b/rumcake/src/lighting/mod.rs @@ -0,0 +1,382 @@ +use bitflags::bitflags; +use embassy_futures::select::{select, select3, Either, Either3}; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Ticker}; +use keyberon::layout::Event; + +use crate::hw::platform::RawMutex; +use crate::keyboard::MATRIX_EVENTS; +use crate::State; + +pub use rumcake_macros::{led_flags, led_layout, setup_backlight_matrix}; + +#[cfg(feature = "rgb-backlight-matrix")] +pub mod rgb_backlight_matrix; +#[cfg(feature = "simple-backlight")] +pub mod simple_backlight; +#[cfg(feature = "simple-backlight-matrix")] +pub mod simple_backlight_matrix; +#[cfg(feature = "underglow")] +pub mod underglow; + +/// Struct that contains information about a lighting matrix of a given size. Includes information +/// about the physical layout of the LEDs, and the flags for each LED. +pub struct BacklightMatrix { + /// The **physical** position of each LED on your keyboard. + /// + /// A given X or Y coordinate value must fall between 0-255. If any matrix + /// positions are unused, you can use `None`. It is recommended to use the + /// [`led_layout`] macro to set this constant. + pub layout: [[Option<(u8, u8)>; C]; R], + + /// The flags of each LED on your keyboard. + /// + /// You can use any combination of [LEDFlags] for each LED. It is recommended + /// to use the [`led_flags`] macro to set this value. + pub flags: [[LEDFlags; C]; R], +} + +impl BacklightMatrix { + /// Create a new backlight matrix with the given LED information. + pub const fn new(layout: [[Option<(u8, u8)>; C]; R], flags: [[LEDFlags; C]; R]) -> Self { + Self { layout, flags } + } +} + +/// An additional trait that keyboards must implement to use a backlight matrix. +pub trait BacklightMatrixDevice { + /// The number of columns in your lighting matrix + /// + /// It is recommended to use the [`setup_backlight_matrix`] macro to set this value. + const LIGHTING_COLS: usize; + + /// The number of rows in your lighting matrix + /// + /// It is recommended to use the [`setup_backlight_matrix`] macro to set this value. + const LIGHTING_ROWS: usize; + + /// Function to return a reference to the [`BacklightMatrix`], containing information about + /// physical LED position, and LED flags. It is recommended to use the + /// [`setup_backlight_matrix`] macro to set this value. + fn get_backlight_matrix() -> BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }>; +} + +pub(crate) mod private { + use super::{BacklightMatrix, BacklightMatrixDevice}; + + pub struct EmptyLightingDevice; + impl BacklightMatrixDevice for EmptyLightingDevice { + const LIGHTING_COLS: usize = 0; + + const LIGHTING_ROWS: usize = 0; + + fn get_backlight_matrix( + ) -> BacklightMatrix<{ Self::LIGHTING_COLS }, { Self::LIGHTING_ROWS }> { + unreachable!("EmptyLightingDevice should not be used with an animator.") + } + } + #[cfg(feature = "simple-backlight")] + impl super::simple_backlight::private::MaybeSimpleBacklightDevice for EmptyLightingDevice {} + #[cfg(feature = "simple-backlight-matrix")] + impl super::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice + for EmptyLightingDevice + { + } + #[cfg(feature = "rgb-backlight-matrix")] + impl super::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice for EmptyLightingDevice {} + #[cfg(feature = "underglow")] + impl super::underglow::private::MaybeUnderglowDevice for EmptyLightingDevice {} +} + +#[derive(Debug)] +struct LayoutBounds { + max: (u8, u8), + mid: (u8, u8), + min: (u8, u8), +} + +fn get_led_layout_bounds() -> LayoutBounds +where + [(); K::LIGHTING_COLS]:, + [(); K::LIGHTING_ROWS]:, +{ + let mut bounds = LayoutBounds { + max: (0, 0), + mid: (0, 0), + min: (255, 255), + }; + + let mut row = 0; + while row < K::LIGHTING_ROWS { + let mut col = 0; + while col < K::LIGHTING_COLS { + if let Some((x, y)) = K::get_backlight_matrix().layout[row][col] { + bounds.min = ( + if x <= bounds.min.0 { x } else { bounds.min.0 }, + if y <= bounds.min.1 { y } else { bounds.min.1 }, + ); + bounds.max = ( + if x >= bounds.max.0 { x } else { bounds.max.0 }, + if y >= bounds.max.1 { y } else { bounds.max.1 }, + ); + } + col += 1; + } + row += 1; + } + + bounds.mid.0 = (bounds.max.0 - bounds.min.0) / 2 + bounds.min.0; + bounds.mid.1 = (bounds.max.1 - bounds.min.1) / 2 + bounds.min.1; + + bounds +} + +bitflags! { + /// Flags used to mark the purpose of an LED in a backlight matrix. + /// + /// Bits used for the flags correspond to QMK's implementation. + pub struct LEDFlags: u8 { + const NONE = 0b00000000; + const ALPHA = 0b00000001; + const KEYLIGHT = 0b00000100; + const INDICATOR = 0b00001000; + } +} + +/// Trait which can be used to implement an animator that can be used with the lighting task. +pub trait Animator { + /// Type used to control the animator. + type CommandType; + + /// Type used to describe the current state of the animator's configuration + type ConfigType: Clone + PartialEq; + + /// Type used in the animator's buffer + type BufferUpdateArgs; + + /// Controls the frame rate of animated effects. + const FPS: usize; + + /// Initialize the animator if it needs to be. By default, this does not do anything. + async fn initialize(&mut self) {} + + /// Render a frame using the animator. This method isn't necessarily always called repeatedly. + /// See [`Animator::is_waiting_for_command`]. + async fn tick(&mut self); + + /// If the animator is not rendering any animated effects, then we can optimize for power usage + /// by not repeatedly calling the tick method. This should return `true` if the animator can + /// wait for a command before rendering the next frame. The default implementation always + /// returns false, causing the lighting task to always repeatedly call the tick method. + fn is_waiting_for_command(&self) -> bool { + false + } + + /// Register matrix events if the animator can generate animations that react to key events. + fn register_matrix_event(&mut self, event: Event) {} + + /// Process a command to control the animator. The commands that are passed to this method are + /// usually sourced from another task via the channel provided to the lighting task. For + /// example, commands can come from Via, or your keyboard layout. + fn process_command(&mut self, command: Self::CommandType); + + /// Perform some tasks after processing a batch of commands. This is can be used to notify + /// other tasks about changes to the animator's state. By default this does nothing. + async fn handle_state_change(&mut self) {} + + /// Update the buffer with the provided arguments. This is used when an additional channel is + /// provided to the lighting task, which allows other tasks to modify the animator's buffer + /// directly. + fn update_buffer(&mut self, args: Self::BufferUpdateArgs) {} + + /// Get a reference to a channel that can receive commands from other tasks to control the + /// animator. + fn get_command_channel() -> &'static Channel; + + /// Get a reference to a state object that can be used to notify other tasks about changes to + /// the animator's configuration. + fn get_state() -> &'static State<'static, Self::ConfigType>; +} + +#[rumcake_macros::task] +pub async fn lighting_task( + mut animator: A, + buf_channel: Option<&Channel>, +) { + let mut subscriber = MATRIX_EVENTS.subscriber().unwrap(); + let channel = A::get_command_channel(); + let mut ticker = Ticker::every(Duration::from_millis(1000 / A::FPS as u64)); + + // Initialize the driver if it needs to be + animator.initialize().await; + + // Render the first frame. This is usually needed if the animator starts on a static effect + animator.tick().await; + + loop { + let command = if animator.is_waiting_for_command() { + // We want to wait for a command if the animator is not rendering any animated effects. This allows the task to sleep when the LEDs are static. + Some(channel.receive().await) + } else if let Some(buf_channel) = buf_channel { + match select3(ticker.next(), channel.receive(), buf_channel.receive()).await { + Either3::First(()) => { + while let Some(event) = subscriber.try_next_message_pure() { + animator.register_matrix_event(event); + } + + None + } + Either3::Second(command) => Some(command), + Either3::Third(args) => { + animator.update_buffer(args); + continue; + } + } + } else { + match select(ticker.next(), channel.receive()).await { + Either::First(()) => { + while let Some(event) = subscriber.try_next_message_pure() { + animator.register_matrix_event(event); + } + + None + } + Either::Second(command) => Some(command), + } + }; + + if let Some(command) = command { + animator.process_command(command); + + // Process commands until there are no more to process + while let Ok(command) = channel.try_receive() { + animator.process_command(command); + } + + animator.handle_state_change().await; + + // Ignore any unprocessed matrix events + while subscriber.try_next_message_pure().is_some() {} + + // Reset the ticker so that it doesn't try to catch up on "missed" ticks. + ticker.reset(); + } + + animator.tick().await; + } +} + +#[cfg(feature = "storage")] +pub use storage::*; + +#[cfg(feature = "storage")] +mod storage { + use core::any::TypeId; + use core::fmt::Debug; + + use defmt::{info, warn, Debug2Format}; + use embassy_futures::select; + use embassy_futures::select::Either; + use embassy_sync::signal::Signal; + use embassy_time::Duration; + use embassy_time::Timer; + use serde::de::DeserializeOwned; + use serde::Serialize; + + use crate::hw::platform::RawMutex; + use crate::storage::StorageKey; + use crate::storage::{FlashStorage, StorageDevice}; + + use super::Animator; + + pub trait AnimatorStorage { + type Animator: Animator; + const STORAGE_KEY: StorageKey; + fn get_state_listener() -> &'static Signal; + fn get_save_signal() -> &'static Signal; + } + + /// Obtains the lighting config from storage. If it fails to get data, it will return a default + /// config. + pub async fn initialize_lighting_data< + K: StorageDevice + 'static, + A: AnimatorStorage + 'static, + F: FlashStorage, + >( + _animator_storage: &A, + database: &crate::storage::StorageService<'_, F, K>, + ) -> ::ConfigType + where + [(); F::ERASE_SIZE]:, + ::ConfigType: core::default::Default + DeserializeOwned + Debug, + { + // Check stored animator config metadata (type id) to see if it has changed + let metadata: [u8; core::mem::size_of::()] = + unsafe { core::mem::transmute(TypeId::of::<::ConfigType>()) }; + let _ = database.check_metadata(A::STORAGE_KEY, &metadata).await; + + // Get animator config from storage + if let Ok(config) = database.read(A::STORAGE_KEY).await { + info!( + "[LIGHTING] Obtained {} from storage: {}", + Debug2Format(&A::STORAGE_KEY), + Debug2Format(&config) + ); + config + } else { + warn!( + "[LIGHTING] Could not get {} from storage, using default config.", + Debug2Format(&A::STORAGE_KEY), + ); + Default::default() + } + } + + #[rumcake_macros::task] + pub async fn lighting_storage_task< + K: StorageDevice, + F: FlashStorage, + A: AnimatorStorage + 'static, + >( + _animator_storage: A, + database: &crate::storage::StorageService<'_, F, K>, + ) where + ::ConfigType: Debug + DeserializeOwned + Serialize, + [(); F::ERASE_SIZE]:, + { + let save_signal = A::get_save_signal(); + let config_state = A::Animator::get_state(); + let config_state_listener = A::get_state_listener(); + + let save = || async { + let _ = database + .write(A::STORAGE_KEY, config_state.get().await) + .await; + }; + + // Save the animator config if it hasn't been changed in 5 seconds, or if a save was signalled + loop { + match select::select(save_signal.wait(), config_state_listener.wait()).await { + Either::First(_) => { + save().await; + } + Either::Second(_) => { + match select::select( + select::select(Timer::after(Duration::from_secs(5)), save_signal.wait()), + config_state_listener.wait(), + ) + .await + { + Either::First(_) => { + save().await; + } + Either::Second(_) => { + // Re-signal, so that we skip the `wait()` call at the beginning of this loop + config_state_listener.signal(()); + } + } + } + }; + } + } +} diff --git a/rumcake/src/lighting/rgb_backlight_matrix.rs b/rumcake/src/lighting/rgb_backlight_matrix.rs new file mode 100644 index 0000000..566906b --- /dev/null +++ b/rumcake/src/lighting/rgb_backlight_matrix.rs @@ -0,0 +1,796 @@ +use core::fmt::Debug; + +use defmt::{error, warn, Debug2Format}; +use embassy_sync::channel::Channel; +use keyberon::layout::Event; +use num_derive::FromPrimitive; +use postcard::experimental::max_size::MaxSize; +use rand::rngs::SmallRng; +use rand_core::SeedableRng; +use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; +use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; +use serde::{Deserialize, Serialize}; +use smart_leds::hsv::Hsv; +use smart_leds::RGB8; + +use crate::hw::platform::RawMutex; +use crate::lighting::{get_led_layout_bounds, Animator, BacklightMatrixDevice, LayoutBounds}; +use crate::{Cycle, LEDEffect, State}; + +/// A trait that keyboards must implement to use backlight features. +pub trait RGBBacklightMatrixDevice: BacklightMatrixDevice { + /// How fast the LEDs refresh to display a new animation frame. + /// + /// It is recommended to set this value to a value that your driver can handle, + /// otherwise your animations will appear to be slowed down. + /// + /// **This does not have any effect if the selected animation is static.** + const FPS: usize = 20; + + /// Get a reference to a channel that can receive commands to control the underglow animator + /// from other tasks. + #[inline(always)] + fn get_command_channel() -> &'static Channel { + static RGB_BACKLIGHT_MATRIX_COMMAND_CHANNEL: Channel< + RawMutex, + RGBBacklightMatrixCommand, + 2, + > = Channel::new(); + + &RGB_BACKLIGHT_MATRIX_COMMAND_CHANNEL + } + + /// Get a reference to a state object that can be used to notify other tasks about changes to + /// the underglow configuration. Note that updating the state object will not control the + /// output of the underglow animator. + #[inline(always)] + fn get_state() -> &'static State<'static, RGBBacklightMatrixConfig> { + static RGB_BACKLIGHT_MATRIX_CONFIG_STATE: State = State::new( + RGBBacklightMatrixConfig::default(), + &[ + #[cfg(feature = "storage")] + &RGB_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER, + ], + ); + + &RGB_BACKLIGHT_MATRIX_CONFIG_STATE + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_state_listener() -> &'static embassy_sync::signal::Signal { + &RGB_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_save_signal() -> &'static embassy_sync::signal::Signal { + &RGB_BACKLIGHT_MATRIX_SAVE_SIGNAL + } + + #[cfg(feature = "split-central")] + type CentralDevice: crate::split::central::private::MaybeCentralDevice = + crate::split::central::private::EmptyCentralDevice; + + rgb_backlight_matrix_effect_items!(); +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + + use crate::hw::platform::RawMutex; + use crate::lighting::BacklightMatrixDevice; + use crate::State; + + use super::{ + RGBBacklightMatrixCommand, RGBBacklightMatrixConfig, RGBBacklightMatrixDevice, + RGBBacklightMatrixEffect, + }; + + pub trait MaybeRGBBacklightMatrixDevice: BacklightMatrixDevice { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> + { + None + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, RGBBacklightMatrixConfig>> { + None + } + + #[inline(always)] + fn is_effect_enabled(_effect: RGBBacklightMatrixEffect) -> bool { + false + } + } + + impl MaybeRGBBacklightMatrixDevice for T { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> + { + Some(T::get_command_channel()) + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, RGBBacklightMatrixConfig>> { + Some(T::get_state()) + } + + #[inline(always)] + fn is_effect_enabled(effect: RGBBacklightMatrixEffect) -> bool { + effect.is_enabled::() + } + } +} + +/// A trait that a driver must implement in order to support an RGB backlighting matrix scheme. +pub trait RGBBacklightMatrixDriver { + /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::write`] fails. + type DriverWriteError: Debug; + + /// Render out a frame buffer using the driver. + async fn write( + &mut self, + buf: &[[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError>; + + /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::turn_on`] fails. + type DriverEnableError: Debug; + + /// Turn the LEDs on using the driver when the animator gets enabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this, and subsequently [`RGBBacklightMatrixDriver::write`]. So, if your + /// driver doesn't need do anything special to turn the LEDs on, you may simply return + /// `Ok(())`. + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; + + /// The type of error that the driver will return if [`RGBBacklightMatrixDriver::turn_off`] fails. + type DriverDisableError: Debug; + + /// Turn the LEDs off using the driver when the animator is disabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this. However, the tick method will not call + /// [`RGBBacklightMatrixDriver::write`] due to the animator being disabled, so you will need to + /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] +pub struct RGBBacklightMatrixConfig { + pub enabled: bool, + pub effect: RGBBacklightMatrixEffect, + pub hue: u8, + pub sat: u8, + pub val: u8, + pub speed: u8, +} + +impl RGBBacklightMatrixConfig { + pub const fn default() -> Self { + RGBBacklightMatrixConfig { + enabled: true, + effect: RGBBacklightMatrixEffect::Solid, + hue: 0, + sat: 255, + val: 255, + speed: 86, + } + } +} + +impl Default for RGBBacklightMatrixConfig { + fn default() -> Self { + Self::default() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] +#[non_exhaustive] +#[repr(u8)] +pub enum RGBBacklightMatrixCommand { + Toggle = 0, + TurnOn = 1, + TurnOff = 2, + NextEffect = 3, + PrevEffect = 4, + SetEffect(RGBBacklightMatrixEffect) = 5, + SetHue(u8) = 6, + IncreaseHue(u8) = 7, + DecreaseHue(u8) = 8, + SetSaturation(u8) = 9, + IncreaseSaturation(u8) = 10, + DecreaseSaturation(u8) = 11, + SetValue(u8) = 12, + IncreaseValue(u8) = 13, + DecreaseValue(u8) = 14, + SetSpeed(u8) = 15, + IncreaseSpeed(u8) = 16, + DecreaseSpeed(u8) = 17, + #[cfg(feature = "storage")] + SaveConfig = 18, + ResetTime = 19, // normally used internally for syncing LEDs for split keyboards +} + +#[generate_items_from_enum_variants("const {variant_shouty_snake_case}_ENABLED: bool = true")] +#[derive( + FromPrimitive, + Serialize, + Deserialize, + Debug, + Clone, + Copy, + LEDEffect, + Cycle, + PartialEq, + Eq, + MaxSize, +)] +pub enum RGBBacklightMatrixEffect { + Solid, + AlphasMods, + GradientUpDown, + GradientLeftRight, + + #[animated] + Breathing, + + #[animated] + ColorbandSat, + + #[animated] + ColorbandVal, + + #[animated] + ColorbandPinWheelSat, + + #[animated] + ColorbandPinWheelVal, + + #[animated] + ColorbandSpiralSat, + + #[animated] + ColorbandSpiralVal, + + #[animated] + CycleAll, + + #[animated] + CycleLeftRight, + + #[animated] + CycleUpDown, + + #[animated] + RainbowMovingChevron, + + #[animated] + CycleOutIn, + + #[animated] + CycleOutInDual, + + #[animated] + CyclePinWheel, + + #[animated] + CycleSpiral, + + #[animated] + DualBeacon, + + #[animated] + RainbowBeacon, + + #[animated] + RainbowPinWheels, + + #[animated] + Raindrops, + + #[animated] + JellybeanRaindrops, + + #[animated] + HueBreathing, + + #[animated] + HuePendulum, + + #[animated] + HueWave, + + #[animated] + PixelRain, + + #[animated] + PixelFlow, + + #[animated] + PixelFractal, + + #[animated] + TypingHeatmap, + + #[animated] + DigitalRain, + + #[animated] + #[reactive] + SolidReactiveSimple, + + #[animated] + #[reactive] + SolidReactive, + + #[animated] + #[reactive] + SolidReactiveWide, + + #[animated] + #[reactive] + SolidReactiveMultiWide, + + #[animated] + #[reactive] + SolidReactiveCross, + + #[animated] + #[reactive] + SolidReactiveMultiCross, + + #[animated] + #[reactive] + SolidReactiveNexus, + + #[animated] + #[reactive] + SolidReactiveMultiNexus, + + #[animated] + #[reactive] + Splash, + + #[animated] + #[reactive] + MultiSplash, + + #[animated] + #[reactive] + SolidSplash, + + #[animated] + #[reactive] + SolidMultiSplash, + + #[cfg(feature = "vial")] + #[animated] + DirectSet, +} + +impl RGBBacklightMatrixEffect { + pub(crate) fn is_enabled(&self) -> bool { + match self { + RGBBacklightMatrixEffect::Solid => D::SOLID_ENABLED, + RGBBacklightMatrixEffect::AlphasMods => D::ALPHAS_MODS_ENABLED, + RGBBacklightMatrixEffect::GradientUpDown => D::GRADIENT_UP_DOWN_ENABLED, + RGBBacklightMatrixEffect::GradientLeftRight => D::GRADIENT_LEFT_RIGHT_ENABLED, + RGBBacklightMatrixEffect::Breathing => D::BREATHING_ENABLED, + RGBBacklightMatrixEffect::ColorbandSat => D::COLORBAND_SAT_ENABLED, + RGBBacklightMatrixEffect::ColorbandVal => D::COLORBAND_VAL_ENABLED, + RGBBacklightMatrixEffect::ColorbandPinWheelSat => D::COLORBAND_PIN_WHEEL_SAT_ENABLED, + RGBBacklightMatrixEffect::ColorbandPinWheelVal => D::COLORBAND_PIN_WHEEL_VAL_ENABLED, + RGBBacklightMatrixEffect::ColorbandSpiralSat => D::COLORBAND_SPIRAL_SAT_ENABLED, + RGBBacklightMatrixEffect::ColorbandSpiralVal => D::COLORBAND_SPIRAL_VAL_ENABLED, + RGBBacklightMatrixEffect::CycleAll => D::CYCLE_ALL_ENABLED, + RGBBacklightMatrixEffect::CycleLeftRight => D::CYCLE_LEFT_RIGHT_ENABLED, + RGBBacklightMatrixEffect::CycleUpDown => D::CYCLE_UP_DOWN_ENABLED, + RGBBacklightMatrixEffect::RainbowMovingChevron => D::RAINBOW_MOVING_CHEVRON_ENABLED, + RGBBacklightMatrixEffect::CycleOutIn => D::CYCLE_OUT_IN_ENABLED, + RGBBacklightMatrixEffect::CycleOutInDual => D::CYCLE_OUT_IN_DUAL_ENABLED, + RGBBacklightMatrixEffect::CyclePinWheel => D::CYCLE_PIN_WHEEL_ENABLED, + RGBBacklightMatrixEffect::CycleSpiral => D::CYCLE_SPIRAL_ENABLED, + RGBBacklightMatrixEffect::DualBeacon => D::DUAL_BEACON_ENABLED, + RGBBacklightMatrixEffect::RainbowBeacon => D::RAINBOW_BEACON_ENABLED, + RGBBacklightMatrixEffect::RainbowPinWheels => D::RAINBOW_PIN_WHEELS_ENABLED, + RGBBacklightMatrixEffect::Raindrops => D::RAINDROPS_ENABLED, + RGBBacklightMatrixEffect::JellybeanRaindrops => D::JELLYBEAN_RAINDROPS_ENABLED, + RGBBacklightMatrixEffect::HueBreathing => D::HUE_BREATHING_ENABLED, + RGBBacklightMatrixEffect::HuePendulum => D::HUE_PENDULUM_ENABLED, + RGBBacklightMatrixEffect::HueWave => D::HUE_WAVE_ENABLED, + RGBBacklightMatrixEffect::PixelRain => D::PIXEL_RAIN_ENABLED, + RGBBacklightMatrixEffect::PixelFlow => D::PIXEL_FLOW_ENABLED, + RGBBacklightMatrixEffect::PixelFractal => D::PIXEL_FRACTAL_ENABLED, + RGBBacklightMatrixEffect::TypingHeatmap => D::TYPING_HEATMAP_ENABLED, + RGBBacklightMatrixEffect::DigitalRain => D::DIGITAL_RAIN_ENABLED, + RGBBacklightMatrixEffect::SolidReactiveSimple => D::SOLID_REACTIVE_SIMPLE_ENABLED, + RGBBacklightMatrixEffect::SolidReactive => D::SOLID_REACTIVE_ENABLED, + RGBBacklightMatrixEffect::SolidReactiveWide => D::SOLID_REACTIVE_WIDE_ENABLED, + RGBBacklightMatrixEffect::SolidReactiveMultiWide => { + D::SOLID_REACTIVE_MULTI_WIDE_ENABLED + } + RGBBacklightMatrixEffect::SolidReactiveCross => D::SOLID_REACTIVE_CROSS_ENABLED, + RGBBacklightMatrixEffect::SolidReactiveMultiCross => { + D::SOLID_REACTIVE_MULTI_CROSS_ENABLED + } + RGBBacklightMatrixEffect::SolidReactiveNexus => D::SOLID_REACTIVE_NEXUS_ENABLED, + RGBBacklightMatrixEffect::SolidReactiveMultiNexus => { + D::SOLID_REACTIVE_MULTI_NEXUS_ENABLED + } + RGBBacklightMatrixEffect::Splash => D::SPLASH_ENABLED, + RGBBacklightMatrixEffect::MultiSplash => D::MULTI_SPLASH_ENABLED, + RGBBacklightMatrixEffect::SolidSplash => D::SOLID_SPLASH_ENABLED, + RGBBacklightMatrixEffect::SolidMultiSplash => D::SOLID_MULTI_SPLASH_ENABLED, + #[cfg(feature = "vial")] + RGBBacklightMatrixEffect::DirectSet => D::DIRECT_SET_ENABLED, + } + } +} + +pub struct RGBBacklightMatrixAnimator> +where + [(); K::LIGHTING_COLS]:, + [(); K::LIGHTING_ROWS]:, +{ + config: RGBBacklightMatrixConfig, + buf: [[RGB8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], // Stores the brightness/value of each LED + last_presses: ConstGenericRingBuffer<((u8, u8), u32), 8>, // Stores the row and col of the last 8 key presses, and the time (in ticks) it was pressed + tick: u32, + driver: D, + bounds: LayoutBounds, + rng: SmallRng, +} + +impl> + RGBBacklightMatrixAnimator +where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, +{ + pub fn new(config: RGBBacklightMatrixConfig, driver: R) -> Self { + Self { + config, + tick: 0, + driver, + last_presses: ConstGenericRingBuffer::new(), + buf: [[RGB8::new(0, 0, 0); D::LIGHTING_COLS]; D::LIGHTING_ROWS], + bounds: get_led_layout_bounds::(), + rng: SmallRng::seed_from_u64(1337), + } + } + + pub async fn turn_on(&mut self) { + if let Err(err) = self.driver.turn_on().await { + warn!("[BACKLIGHT] Animations have been enabled, but the backlight LEDs could not be turned on: {}", Debug2Format(&err)); + }; + } + + pub async fn turn_off(&mut self) { + if let Err(err) = self.driver.turn_off().await { + warn!("[BACKLIGHT] Animations have been disabled, but the backlight LEDs could not be turned off: {}", Debug2Format(&err)); + }; + } + + pub fn process_command(&mut self, command: RGBBacklightMatrixCommand) { + match command { + RGBBacklightMatrixCommand::Toggle => { + self.config.enabled = !self.config.enabled; + } + RGBBacklightMatrixCommand::TurnOn => { + self.config.enabled = true; + } + RGBBacklightMatrixCommand::TurnOff => { + self.config.enabled = false; + } + RGBBacklightMatrixCommand::NextEffect => { + while { + self.config.effect.increment(); + !self.config.effect.is_enabled::() + } {} + } + RGBBacklightMatrixCommand::PrevEffect => { + while { + self.config.effect.decrement(); + !self.config.effect.is_enabled::() + } {} + } + RGBBacklightMatrixCommand::SetEffect(effect) => { + self.config.effect = effect; + } + RGBBacklightMatrixCommand::SetHue(hue) => { + self.config.hue = hue; + } + RGBBacklightMatrixCommand::IncreaseHue(amount) => { + self.config.hue = self.config.hue.saturating_add(amount); + } + RGBBacklightMatrixCommand::DecreaseHue(amount) => { + self.config.hue = self.config.hue.saturating_sub(amount); + } + RGBBacklightMatrixCommand::SetSaturation(sat) => { + self.config.sat = sat; + } + RGBBacklightMatrixCommand::IncreaseSaturation(amount) => { + self.config.sat = self.config.sat.saturating_add(amount); + } + RGBBacklightMatrixCommand::DecreaseSaturation(amount) => { + self.config.sat = self.config.sat.saturating_sub(amount); + } + RGBBacklightMatrixCommand::SetValue(val) => { + self.config.val = val; + } + RGBBacklightMatrixCommand::IncreaseValue(amount) => { + self.config.val = self.config.val.saturating_add(amount); + } + RGBBacklightMatrixCommand::DecreaseValue(amount) => { + self.config.val = self.config.val.saturating_sub(amount); + } + RGBBacklightMatrixCommand::SetSpeed(speed) => { + self.config.speed = speed; + } + RGBBacklightMatrixCommand::IncreaseSpeed(amount) => { + self.config.speed = self.config.speed.saturating_add(amount); + } + RGBBacklightMatrixCommand::DecreaseSpeed(amount) => { + self.config.speed = self.config.speed.saturating_sub(amount); + } + #[cfg(feature = "storage")] + RGBBacklightMatrixCommand::SaveConfig => { + // storage::BACKLIGHT_SAVE_SIGNAL.signal(()); + } + RGBBacklightMatrixCommand::ResetTime => { + self.tick = 0; + } + }; + } + + pub fn set_brightness_for_each_led(&mut self, calc: impl Fn(&mut Self, f32, u8) -> Hsv) { + unimplemented!() + } + + pub fn register_event(&mut self, event: Event) { + match event { + Event::Press(row, col) => { + match self + .last_presses + .iter_mut() + .find(|((pressed_row, pressed_col), _time)| { + *pressed_row == row && *pressed_col == col + }) { + Some(press) => { + press.1 = self.tick; + } + None => { + // Check if the matrix position corresponds to a LED position before pushing + if D::get_backlight_matrix() + .layout + .get(row as usize) + .and_then(|row| row.get(col as usize)) + .and_then(|pos| *pos) + .is_some() + { + self.last_presses.push(((row, col), self.tick)); + } + } + }; + } + Event::Release(_row, _col) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. + } + } + + pub async fn tick(&mut self) { + if !self.config.enabled { + return; + } + + // TODO: animations + match self.config.effect { + RGBBacklightMatrixEffect::Solid => todo!(), + RGBBacklightMatrixEffect::AlphasMods => todo!(), + RGBBacklightMatrixEffect::GradientUpDown => todo!(), + RGBBacklightMatrixEffect::GradientLeftRight => todo!(), + RGBBacklightMatrixEffect::Breathing => todo!(), + RGBBacklightMatrixEffect::ColorbandSat => todo!(), + RGBBacklightMatrixEffect::ColorbandVal => todo!(), + RGBBacklightMatrixEffect::ColorbandPinWheelSat => todo!(), + RGBBacklightMatrixEffect::ColorbandPinWheelVal => todo!(), + RGBBacklightMatrixEffect::ColorbandSpiralSat => todo!(), + RGBBacklightMatrixEffect::ColorbandSpiralVal => todo!(), + RGBBacklightMatrixEffect::CycleAll => todo!(), + RGBBacklightMatrixEffect::CycleLeftRight => todo!(), + RGBBacklightMatrixEffect::CycleUpDown => todo!(), + RGBBacklightMatrixEffect::RainbowMovingChevron => todo!(), + RGBBacklightMatrixEffect::CycleOutIn => todo!(), + RGBBacklightMatrixEffect::CycleOutInDual => todo!(), + RGBBacklightMatrixEffect::CyclePinWheel => todo!(), + RGBBacklightMatrixEffect::CycleSpiral => todo!(), + RGBBacklightMatrixEffect::DualBeacon => todo!(), + RGBBacklightMatrixEffect::RainbowBeacon => todo!(), + RGBBacklightMatrixEffect::RainbowPinWheels => todo!(), + RGBBacklightMatrixEffect::Raindrops => todo!(), + RGBBacklightMatrixEffect::JellybeanRaindrops => todo!(), + RGBBacklightMatrixEffect::HueBreathing => todo!(), + RGBBacklightMatrixEffect::HuePendulum => todo!(), + RGBBacklightMatrixEffect::HueWave => todo!(), + RGBBacklightMatrixEffect::PixelRain => todo!(), + RGBBacklightMatrixEffect::PixelFlow => todo!(), + RGBBacklightMatrixEffect::PixelFractal => todo!(), + RGBBacklightMatrixEffect::TypingHeatmap => todo!(), + RGBBacklightMatrixEffect::DigitalRain => todo!(), + RGBBacklightMatrixEffect::SolidReactiveSimple => todo!(), + RGBBacklightMatrixEffect::SolidReactive => todo!(), + RGBBacklightMatrixEffect::SolidReactiveWide => todo!(), + RGBBacklightMatrixEffect::SolidReactiveMultiWide => todo!(), + RGBBacklightMatrixEffect::SolidReactiveCross => todo!(), + RGBBacklightMatrixEffect::SolidReactiveMultiCross => todo!(), + RGBBacklightMatrixEffect::SolidReactiveNexus => todo!(), + RGBBacklightMatrixEffect::SolidReactiveMultiNexus => todo!(), + RGBBacklightMatrixEffect::Splash => todo!(), + RGBBacklightMatrixEffect::MultiSplash => todo!(), + RGBBacklightMatrixEffect::SolidSplash => todo!(), + RGBBacklightMatrixEffect::SolidMultiSplash => todo!(), + #[cfg(feature = "vial")] + RGBBacklightMatrixEffect::DirectSet => {} // We just move onto calling the driver, since the frame buffer is updated by the backlight task + } + + if let Err(err) = self.driver.write(&self.buf).await { + error!( + "[BACKLIGHT] Couldn't update backlight colors: {}", + Debug2Format(&err) + ); + }; + + self.tick += 1; + } + + #[cfg(feature = "storage")] + pub fn create_storage_instance(&self) -> RGBBacklightMatrixStorage { + RGBBacklightMatrixStorage { + _device_phantom: core::marker::PhantomData, + _driver_phantom: core::marker::PhantomData, + } + } +} + +impl> Animator + for RGBBacklightMatrixAnimator +where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, +{ + type CommandType = RGBBacklightMatrixCommand; + + type ConfigType = RGBBacklightMatrixConfig; + + type BufferUpdateArgs = (u8, RGB8); + + const FPS: usize = D::FPS; + + async fn initialize(&mut self) { + self.config = D::get_state().get().await; + + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + async fn tick(&mut self) { + self.tick().await + } + + fn is_waiting_for_command(&self) -> bool { + !(self.config.enabled && self.config.effect.is_animated()) + } + + fn process_command(&mut self, command: Self::CommandType) { + self.process_command(command) + } + + async fn handle_state_change(&mut self) { + // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled + let toggled = D::get_state() + .update(|config| { + let toggled = config.enabled != self.config.enabled; + **config = self.config; + toggled + }) + .await; + + if toggled { + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + // Send commands to be consumed by the split peripherals + #[cfg(feature = "split-central")] + { + use crate::split::central::private::MaybeCentralDevice; + if let Some(channel) = D::CentralDevice::get_message_to_peripheral_channel() { + channel + .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( + RGBBacklightMatrixCommand::ResetTime, + )) + .await; + channel + .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( + RGBBacklightMatrixCommand::SetEffect(self.config.effect), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( + RGBBacklightMatrixCommand::SetValue(self.config.val), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::RGBBacklightMatrix( + RGBBacklightMatrixCommand::SetSpeed(self.config.speed), + )) + .await; + } + } + } + + fn update_buffer(&mut self, (led, color): Self::BufferUpdateArgs) { + let col = led as usize % D::LIGHTING_COLS; + let row = led as usize / D::LIGHTING_COLS % D::LIGHTING_ROWS; + self.buf[row][col] = color; + } + + #[inline(always)] + fn get_command_channel() -> &'static Channel { + D::get_command_channel() + } + + #[inline(always)] + fn get_state() -> &'static State<'static, Self::ConfigType> { + D::get_state() + } +} + +#[cfg(feature = "storage")] +use storage::*; + +#[cfg(feature = "storage")] +mod storage { + use embassy_sync::signal::Signal; + + use crate::hw::platform::RawMutex; + + use super::{RGBBacklightMatrixAnimator, RGBBacklightMatrixDevice, RGBBacklightMatrixDriver}; + + pub(super) static RGB_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER: Signal = + Signal::new(); + pub(super) static RGB_BACKLIGHT_MATRIX_SAVE_SIGNAL: Signal = Signal::new(); + + pub struct RGBBacklightMatrixStorage { + pub(super) _driver_phantom: core::marker::PhantomData, + pub(super) _device_phantom: core::marker::PhantomData, + } + + impl> + crate::lighting::AnimatorStorage for RGBBacklightMatrixStorage + where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, + { + type Animator = RGBBacklightMatrixAnimator; + + const STORAGE_KEY: crate::storage::StorageKey = + crate::storage::StorageKey::RGBBacklightMatrixConfig; + + #[inline(always)] + fn get_state_listener() -> &'static Signal { + D::get_state_listener() + } + + #[inline(always)] + fn get_save_signal() -> &'static Signal { + D::get_save_signal() + } + } +} diff --git a/rumcake/src/lighting/simple_backlight.rs b/rumcake/src/lighting/simple_backlight.rs new file mode 100644 index 0000000..0d54d9a --- /dev/null +++ b/rumcake/src/lighting/simple_backlight.rs @@ -0,0 +1,497 @@ +use core::fmt::Debug; +use core::marker::PhantomData; +use core::u8; + +use defmt::{error, warn, Debug2Format}; +use embassy_sync::channel::Channel; +use keyberon::layout::Event; +use num_derive::FromPrimitive; +use postcard::experimental::max_size::MaxSize; +use rand::rngs::SmallRng; +use rand_core::SeedableRng; +use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; +use serde::{Deserialize, Serialize}; + +use crate::hw::platform::RawMutex; +use crate::lighting::Animator; +use crate::math::{scale, sin}; +use crate::{Cycle, LEDEffect, State}; + +pub trait SimpleBacklightDevice { + /// How fast the LEDs refresh to display a new animation frame. + /// + /// It is recommended to set this value to a value that your driver can handle, + /// otherwise your animations will appear to be slowed down. + /// + /// **This does not have any effect if the selected animation is static.** + const FPS: usize = 20; + + /// Get a reference to a channel that can receive commands to control the simple backlight + /// animator from other tasks. + #[inline(always)] + fn get_command_channel() -> &'static Channel { + static SIMPLE_BACKLIGHT_COMMAND_CHANNEL: Channel = + Channel::new(); + + &SIMPLE_BACKLIGHT_COMMAND_CHANNEL + } + + /// Get a reference to a state object that can be used to notify other tasks about changes to + /// the simple backlight configuration. Note that updating the state object will not control + /// the output of the simple backlight animator. + #[inline(always)] + fn get_state() -> &'static State<'static, SimpleBacklightConfig> { + static SIMPLE_BACKLIGHT_CONFIG_STATE: State = State::new( + SimpleBacklightConfig::default(), + &[ + #[cfg(feature = "storage")] + &SIMPLE_BACKLIGHT_CONFIG_STATE_LISTENER, + ], + ); + + &SIMPLE_BACKLIGHT_CONFIG_STATE + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_state_listener() -> &'static embassy_sync::signal::Signal { + &SIMPLE_BACKLIGHT_CONFIG_STATE_LISTENER + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_save_signal() -> &'static embassy_sync::signal::Signal { + &SIMPLE_BACKLIGHT_SAVE_SIGNAL + } + + #[cfg(feature = "split-central")] + type CentralDevice: crate::split::central::private::MaybeCentralDevice = + crate::split::central::private::EmptyCentralDevice; + + simple_backlight_effect_items!(); +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + + use crate::hw::platform::RawMutex; + use crate::State; + + use super::{SimpleBacklightCommand, SimpleBacklightConfig, SimpleBacklightDevice}; + + pub trait MaybeSimpleBacklightDevice { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> { + None + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, SimpleBacklightConfig>> { + None + } + } + + impl MaybeSimpleBacklightDevice for T { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> { + Some(T::get_command_channel()) + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, SimpleBacklightConfig>> { + Some(T::get_state()) + } + } +} + +/// A trait that a driver must implement in order to support a simple (no matrix, one color) backlighting scheme. +pub trait SimpleBacklightDriver { + /// The type of error that the driver will return if [`SimpleBacklightDriver::write`] fails. + type DriverWriteError: Debug; + + /// Render out a frame buffer using the driver. + async fn write(&mut self, brightness: u8) -> Result<(), Self::DriverWriteError>; + + /// The type of error that the driver will return if [`SimpleBacklightDriver::turn_on`] fails. + type DriverEnableError: Debug; + + /// Turn the LEDs on using the driver when the animator gets enabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this, and subsequently [`SimpleBacklightDriver::write`]. So, if your driver + /// doesn't need do anything special to turn the LEDs on, you may simply return `Ok(())`. + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; + + /// The type of error that the driver will return if [`SimpleBacklightDriver::turn_off`] fails. + type DriverDisableError: Debug; + + /// Turn the LEDs off using the driver when the animator is disabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this. However, the tick method will not call + /// [`SimpleBacklightDriver::write`] due to the animator being disabled, so you will need to + /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] +pub struct SimpleBacklightConfig { + pub enabled: bool, + pub effect: SimpleBacklightEffect, + pub val: u8, + pub speed: u8, +} + +impl SimpleBacklightConfig { + pub const fn default() -> Self { + SimpleBacklightConfig { + enabled: true, + effect: SimpleBacklightEffect::Solid, + val: 255, + speed: 86, + } + } +} + +impl Default for SimpleBacklightConfig { + fn default() -> Self { + Self::default() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] +#[non_exhaustive] +#[repr(u8)] +pub enum SimpleBacklightCommand { + Toggle = 0, + TurnOn = 1, + TurnOff = 2, + NextEffect = 3, + PrevEffect = 4, + SetEffect(SimpleBacklightEffect) = 5, + SetValue(u8) = 6, + IncreaseValue(u8) = 7, + DecreaseValue(u8) = 8, + SetSpeed(u8) = 9, + IncreaseSpeed(u8) = 10, + DecreaseSpeed(u8) = 11, + #[cfg(feature = "storage")] + SaveConfig = 12, + ResetTime = 13, // normally used internally for syncing LEDs for split keyboards +} + +#[generate_items_from_enum_variants("const {variant_shouty_snake_case}_ENABLED: bool = true")] +#[derive( + FromPrimitive, + Serialize, + Deserialize, + Debug, + Clone, + Copy, + LEDEffect, + Cycle, + PartialEq, + Eq, + MaxSize, +)] +pub enum SimpleBacklightEffect { + Solid, + + #[animated] + Breathing, + + #[animated] + #[reactive] + Reactive, +} + +impl SimpleBacklightEffect { + pub(crate) fn is_enabled(&self) -> bool { + match self { + SimpleBacklightEffect::Solid => D::SOLID_ENABLED, + SimpleBacklightEffect::Breathing => D::BREATHING_ENABLED, + SimpleBacklightEffect::Reactive => D::REACTIVE_ENABLED, + } + } +} + +pub struct SimpleBacklightAnimator> { + config: SimpleBacklightConfig, + buf: u8, // Stores the current brightness/value. Different from `self.config.val`. + time_of_last_press: u32, + tick: u32, + driver: R, + rng: SmallRng, + phantom: PhantomData, +} + +impl> SimpleBacklightAnimator { + pub fn new(config: SimpleBacklightConfig, driver: R) -> Self { + Self { + config, + tick: 0, + driver, + buf: 0, + time_of_last_press: 0, + rng: SmallRng::seed_from_u64(1337), + phantom: PhantomData, + } + } + + pub async fn turn_on(&mut self) { + if let Err(err) = self.driver.turn_on().await { + warn!("[SIMPLE_BACKLIGHT] Animations have been enabled, but the backlight LEDs could not be turned on: {}", Debug2Format(&err)); + }; + } + + pub async fn turn_off(&mut self) { + if let Err(err) = self.driver.turn_off().await { + warn!("[SIMPLE_BACKLIGHT] Animations have been disabled, but the backlight LEDs could not be turned off: {}", Debug2Format(&err)); + }; + } + + pub fn process_command(&mut self, command: SimpleBacklightCommand) { + match command { + SimpleBacklightCommand::Toggle => { + self.config.enabled = !self.config.enabled; + } + SimpleBacklightCommand::TurnOn => { + self.config.enabled = true; + } + SimpleBacklightCommand::TurnOff => { + self.config.enabled = false; + } + SimpleBacklightCommand::NextEffect => { + while { + self.config.effect.increment(); + self.config.effect.is_enabled::() + } {} + } + SimpleBacklightCommand::PrevEffect => { + while { + self.config.effect.decrement(); + self.config.effect.is_enabled::() + } {} + } + SimpleBacklightCommand::SetEffect(effect) => { + self.config.effect = effect; + } + SimpleBacklightCommand::SetValue(val) => { + self.config.val = val; + } + SimpleBacklightCommand::IncreaseValue(amount) => { + self.config.val = self.config.val.saturating_add(amount); + } + SimpleBacklightCommand::DecreaseValue(amount) => { + self.config.val = self.config.val.saturating_sub(amount); + } + SimpleBacklightCommand::SetSpeed(speed) => { + self.config.speed = speed; + } + SimpleBacklightCommand::IncreaseSpeed(amount) => { + self.config.speed = self.config.speed.saturating_add(amount); + } + SimpleBacklightCommand::DecreaseSpeed(amount) => { + self.config.speed = self.config.speed.saturating_sub(amount); + } + #[cfg(feature = "storage")] + SimpleBacklightCommand::SaveConfig => { + // storage::SIMPLE_BACKLIGHT_SAVE_SIGNAL.signal(()); + } + SimpleBacklightCommand::ResetTime => { + self.tick = 0; + } + } + } + + pub fn set_brightness(&mut self, calc: impl Fn(&mut Self, u32) -> u8) { + let time = (self.tick << 8) + / (((D::FPS as u32) << 8) + / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); // `time` should increment by 255 every second + + self.buf = scale(calc(self, time), self.config.val) + } + + pub fn register_event(&mut self, event: Event) { + match event { + Event::Press(_row, _col) => { + self.time_of_last_press = (self.tick << 8) + / (((D::FPS as u32) << 8) + / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); + } + Event::Release(_row, _col) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. + } + } + + pub async fn tick(&mut self) { + if !self.config.enabled { + return; + } + + match self.config.effect { + SimpleBacklightEffect::Solid => { + if D::SOLID_ENABLED { + self.set_brightness(|_animator, _time| u8::MAX) + } + } + SimpleBacklightEffect::Breathing => { + if D::BREATHING_ENABLED { + self.set_brightness(|_animator, time| sin((time >> 2) as u8)) + } + } + SimpleBacklightEffect::Reactive => { + if D::REACTIVE_ENABLED { + self.set_brightness(|animator, time| { + // LED fades after one second + (u8::MAX as u32).saturating_sub(time - animator.time_of_last_press) as u8 + }) + } + } + } + + if let Err(err) = self.driver.write(self.buf).await { + error!( + "[BACKLIGHT] Couldn't update backlight: {}", + Debug2Format(&err) + ); + }; + + self.tick += 1; + } + + #[cfg(feature = "storage")] + pub fn create_storage_instance(&self) -> SimpleBacklightStorage { + SimpleBacklightStorage { + _device_phantom: core::marker::PhantomData, + _driver_phantom: core::marker::PhantomData, + } + } +} + +impl> Animator + for SimpleBacklightAnimator +{ + type CommandType = SimpleBacklightCommand; + + type ConfigType = SimpleBacklightConfig; + + type BufferUpdateArgs = (); + + const FPS: usize = D::FPS; + + async fn initialize(&mut self) { + self.config = D::get_state().get().await; + + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + async fn tick(&mut self) { + self.tick().await + } + + fn is_waiting_for_command(&self) -> bool { + !(self.config.enabled && self.config.effect.is_animated()) + } + + fn process_command(&mut self, command: Self::CommandType) { + self.process_command(command) + } + + async fn handle_state_change(&mut self) { + // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled + let toggled = D::get_state() + .update(|config| { + let toggled = config.enabled != self.config.enabled; + **config = self.config; + toggled + }) + .await; + + if toggled { + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + // Send commands to be consumed by the split peripherals + #[cfg(feature = "split-central")] + { + use crate::split::central::private::MaybeCentralDevice; + if let Some(channel) = D::CentralDevice::get_message_to_peripheral_channel() { + channel + .send(crate::split::MessageToPeripheral::SimpleBacklight( + SimpleBacklightCommand::ResetTime, + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklight( + SimpleBacklightCommand::SetEffect(self.config.effect), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklight( + SimpleBacklightCommand::SetValue(self.config.val), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklight( + SimpleBacklightCommand::SetSpeed(self.config.speed), + )) + .await; + } + } + } + + #[inline(always)] + fn get_command_channel() -> &'static Channel { + D::get_command_channel() + } + + #[inline(always)] + fn get_state() -> &'static State<'static, Self::ConfigType> { + D::get_state() + } +} + +#[cfg(feature = "storage")] +pub use storage::*; + +#[cfg(feature = "storage")] +mod storage { + use embassy_sync::signal::Signal; + + use crate::hw::platform::RawMutex; + + use super::{SimpleBacklightAnimator, SimpleBacklightDevice, SimpleBacklightDriver}; + + pub(super) static SIMPLE_BACKLIGHT_CONFIG_STATE_LISTENER: Signal = Signal::new(); + pub(super) static SIMPLE_BACKLIGHT_SAVE_SIGNAL: Signal = Signal::new(); + + pub struct SimpleBacklightStorage { + pub(super) _driver_phantom: core::marker::PhantomData, + pub(super) _device_phantom: core::marker::PhantomData, + } + + impl> crate::lighting::AnimatorStorage + for SimpleBacklightStorage + { + type Animator = SimpleBacklightAnimator; + + const STORAGE_KEY: crate::storage::StorageKey = + crate::storage::StorageKey::SimpleBacklightConfig; + + #[inline(always)] + fn get_state_listener() -> &'static Signal { + D::get_state_listener() + } + + #[inline(always)] + fn get_save_signal() -> &'static Signal { + D::get_save_signal() + } + } +} diff --git a/rumcake/src/backlight/simple_backlight_matrix/animations.rs b/rumcake/src/lighting/simple_backlight_matrix.rs similarity index 52% rename from rumcake/src/backlight/simple_backlight_matrix/animations.rs rename to rumcake/src/lighting/simple_backlight_matrix.rs index 4bb96c4..e7c3ea7 100644 --- a/rumcake/src/backlight/simple_backlight_matrix/animations.rs +++ b/rumcake/src/lighting/simple_backlight_matrix.rs @@ -1,69 +1,203 @@ -use crate::backlight::drivers::SimpleBacklightMatrixDriver; -use crate::backlight::{ - get_led_layout_bounds, BacklightDevice, BacklightMatrixDevice, LEDFlags, LayoutBounds, -}; -use crate::math::{atan2f, cos, scale, sin, sqrtf}; -use crate::{Cycle, LEDEffect}; -use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; - use core::f32::consts::PI; +use core::fmt::Debug; use core::u8; + use defmt::{error, warn, Debug2Format}; +use embassy_sync::channel::Channel; use keyberon::layout::Event; use num_derive::FromPrimitive; use postcard::experimental::max_size::MaxSize; use rand::rngs::SmallRng; use rand_core::{RngCore, SeedableRng}; use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; +use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; use serde::{Deserialize, Serialize}; +use crate::hw::platform::RawMutex; +use crate::lighting::{ + get_led_layout_bounds, Animator, BacklightMatrixDevice, LEDFlags, LayoutBounds, +}; +use crate::math::{atan2f, cos, scale, sin, sqrtf}; +use crate::{Cycle, LEDEffect, State}; + +/// A trait that keyboards must implement to use backlight features. +pub trait SimpleBacklightMatrixDevice: BacklightMatrixDevice { + /// How fast the LEDs refresh to display a new animation frame. + /// + /// It is recommended to set this value to a value that your driver can handle, + /// otherwise your animations will appear to be slowed down. + /// + /// **This does not have any effect if the selected animation is static.** + const FPS: usize = 20; + + /// Get a reference to a channel that can receive commands to control the underglow animator + /// from other tasks. + #[inline(always)] + fn get_command_channel() -> &'static Channel { + static SIMPLE_BACKLIGHT_MATRIX_COMMAND_CHANNEL: Channel< + RawMutex, + SimpleBacklightMatrixCommand, + 2, + > = Channel::new(); + + &SIMPLE_BACKLIGHT_MATRIX_COMMAND_CHANNEL + } + + /// Get a reference to a state object that can be used to notify other tasks about changes to + /// the underglow configuration. Note that updating the state object will not control the + /// output of the underglow animator. + #[inline(always)] + fn get_state() -> &'static State<'static, SimpleBacklightMatrixConfig> { + static SIMPLE_BACKLIGHT_MATRIX_CONFIG_STATE: State = + State::new( + SimpleBacklightMatrixConfig::default(), + &[ + #[cfg(feature = "storage")] + &SIMPLE_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER, + ], + ); + + &SIMPLE_BACKLIGHT_MATRIX_CONFIG_STATE + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_state_listener() -> &'static embassy_sync::signal::Signal { + &SIMPLE_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_save_signal() -> &'static embassy_sync::signal::Signal { + &SIMPLE_BACKLIGHT_MATRIX_SAVE_SIGNAL + } + + #[cfg(feature = "split-central")] + type CentralDevice: crate::split::central::private::MaybeCentralDevice = + crate::split::central::private::EmptyCentralDevice; + + simple_backlight_matrix_effect_items!(); +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + + use crate::hw::platform::RawMutex; + use crate::lighting::BacklightMatrixDevice; + use crate::State; + + use super::{ + SimpleBacklightMatrixCommand, SimpleBacklightMatrixConfig, SimpleBacklightMatrixDevice, + }; + + pub trait MaybeSimpleBacklightMatrixDevice: BacklightMatrixDevice { + #[inline(always)] + fn get_command_channel( + ) -> Option<&'static Channel> { + None + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, SimpleBacklightMatrixConfig>> { + None + } + } + + impl MaybeSimpleBacklightMatrixDevice for T { + #[inline(always)] + fn get_command_channel( + ) -> Option<&'static Channel> { + Some(T::get_command_channel()) + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, SimpleBacklightMatrixConfig>> { + Some(T::get_state()) + } + } +} + +/// A trait that a driver must implement in order to support a simple (no color) backlighting matrix scheme. +pub trait SimpleBacklightMatrixDriver { + /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::write`] fails. + type DriverWriteError: Debug; + + /// Render out a frame buffer using the driver. + async fn write( + &mut self, + buf: &[[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + ) -> Result<(), Self::DriverWriteError>; + + /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::turn_on`] fails. + type DriverEnableError: Debug; + + /// Turn the LEDs on using the driver when the animator gets enabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this, and subsequently [`SimpleBacklightMatrixDriver::write`]. So, if your + /// driver doesn't need do anything special to turn the LEDs on, you may simply return + /// `Ok(())`. + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; + + /// The type of error that the driver will return if [`SimpleBacklightMatrixDriver::turn_off`] fails. + type DriverDisableError: Debug; + + /// Turn the LEDs off using the driver when the animator is disabled. + /// + /// The animator's [`tick()`](super::animations::BacklightAnimator::tick) method gets called + /// directly after this. However, the tick method will not call + /// [`SimpleBacklightMatrixDriver::write`] due to the animator being disabled, so you will need to + /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] -pub struct BacklightConfig { +pub struct SimpleBacklightMatrixConfig { pub enabled: bool, - pub effect: BacklightEffect, + pub effect: SimpleBacklightMatrixEffect, pub val: u8, pub speed: u8, } -impl BacklightConfig { +impl SimpleBacklightMatrixConfig { pub const fn default() -> Self { - BacklightConfig { + SimpleBacklightMatrixConfig { enabled: true, - effect: BacklightEffect::Solid, + effect: SimpleBacklightMatrixEffect::Solid, val: 255, speed: 86, } } } -impl Default for BacklightConfig { +impl Default for SimpleBacklightMatrixConfig { fn default() -> Self { Self::default() } } #[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] -pub enum BacklightCommand { - Toggle, - TurnOn, - TurnOff, - NextEffect, - PrevEffect, - SetEffect(BacklightEffect), - SetValue(u8), - IncreaseValue(u8), - DecreaseValue(u8), - SetSpeed(u8), - IncreaseSpeed(u8), - DecreaseSpeed(u8), +#[non_exhaustive] +#[repr(u8)] +pub enum SimpleBacklightMatrixCommand { + Toggle = 0, + TurnOn = 1, + TurnOff = 2, + NextEffect = 3, + PrevEffect = 4, + SetEffect(SimpleBacklightMatrixEffect) = 5, + SetValue(u8) = 6, + IncreaseValue(u8) = 7, + DecreaseValue(u8) = 8, + SetSpeed(u8) = 9, + IncreaseSpeed(u8) = 10, + DecreaseSpeed(u8) = 11, #[cfg(feature = "storage")] - SaveConfig, - ResetTime, // normally used internally for syncing LEDs for split keyboards + SaveConfig = 12, + ResetTime = 13, // normally used internally for syncing LEDs for split keyboards } -#[generate_items_from_enum_variants( - "const SIMPLE_BACKLIGHT_MATRIX_{variant_shouty_snake_case}_ENABLED: bool = true" -)] +#[generate_items_from_enum_variants("const {variant_shouty_snake_case}_ENABLED: bool = true")] #[derive( FromPrimitive, Serialize, @@ -77,7 +211,7 @@ pub enum BacklightCommand { Eq, MaxSize, )] -pub enum BacklightEffect { +pub enum SimpleBacklightMatrixEffect { Solid, AlphasMods, GradientUpDown, @@ -137,62 +271,63 @@ pub enum BacklightEffect { ReactiveSplash, } -impl BacklightEffect { - pub(crate) fn is_enabled(&self) -> bool { +impl SimpleBacklightMatrixEffect { + pub(crate) fn is_enabled(&self) -> bool { match self { - BacklightEffect::Solid => D::SIMPLE_BACKLIGHT_MATRIX_SOLID_ENABLED, - BacklightEffect::AlphasMods => D::SIMPLE_BACKLIGHT_MATRIX_ALPHAS_MODS_ENABLED, - BacklightEffect::GradientUpDown => D::SIMPLE_BACKLIGHT_MATRIX_GRADIENT_UP_DOWN_ENABLED, - BacklightEffect::GradientLeftRight => { - D::SIMPLE_BACKLIGHT_MATRIX_GRADIENT_LEFT_RIGHT_ENABLED - } - BacklightEffect::Breathing => D::SIMPLE_BACKLIGHT_MATRIX_BREATHING_ENABLED, - BacklightEffect::Band => D::SIMPLE_BACKLIGHT_MATRIX_BAND_ENABLED, - BacklightEffect::BandPinWheel => D::SIMPLE_BACKLIGHT_MATRIX_BAND_PIN_WHEEL_ENABLED, - BacklightEffect::BandSpiral => D::SIMPLE_BACKLIGHT_MATRIX_BAND_SPIRAL_ENABLED, - BacklightEffect::CycleLeftRight => D::SIMPLE_BACKLIGHT_MATRIX_CYCLE_LEFT_RIGHT_ENABLED, - BacklightEffect::CycleUpDown => D::SIMPLE_BACKLIGHT_MATRIX_CYCLE_UP_DOWN_ENABLED, - BacklightEffect::CycleOutIn => D::SIMPLE_BACKLIGHT_MATRIX_CYCLE_OUT_IN_ENABLED, - BacklightEffect::Raindrops => D::SIMPLE_BACKLIGHT_MATRIX_RAINDROPS_ENABLED, - BacklightEffect::DualBeacon => D::SIMPLE_BACKLIGHT_MATRIX_DUAL_BEACON_ENABLED, - BacklightEffect::WaveLeftRight => D::SIMPLE_BACKLIGHT_MATRIX_WAVE_LEFT_RIGHT_ENABLED, - BacklightEffect::WaveUpDown => D::SIMPLE_BACKLIGHT_MATRIX_WAVE_UP_DOWN_ENABLED, - BacklightEffect::Reactive => D::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_ENABLED, - BacklightEffect::ReactiveWide => D::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_WIDE_ENABLED, - BacklightEffect::ReactiveCross => D::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_CROSS_ENABLED, - BacklightEffect::ReactiveNexus => D::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_NEXUS_ENABLED, - BacklightEffect::ReactiveSplash => D::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_SPLASH_ENABLED, + SimpleBacklightMatrixEffect::Solid => D::SOLID_ENABLED, + SimpleBacklightMatrixEffect::AlphasMods => D::ALPHAS_MODS_ENABLED, + SimpleBacklightMatrixEffect::GradientUpDown => D::GRADIENT_UP_DOWN_ENABLED, + SimpleBacklightMatrixEffect::GradientLeftRight => D::GRADIENT_LEFT_RIGHT_ENABLED, + SimpleBacklightMatrixEffect::Breathing => D::BREATHING_ENABLED, + SimpleBacklightMatrixEffect::Band => D::BAND_ENABLED, + SimpleBacklightMatrixEffect::BandPinWheel => D::BAND_PIN_WHEEL_ENABLED, + SimpleBacklightMatrixEffect::BandSpiral => D::BAND_SPIRAL_ENABLED, + SimpleBacklightMatrixEffect::CycleLeftRight => D::CYCLE_LEFT_RIGHT_ENABLED, + SimpleBacklightMatrixEffect::CycleUpDown => D::CYCLE_UP_DOWN_ENABLED, + SimpleBacklightMatrixEffect::CycleOutIn => D::CYCLE_OUT_IN_ENABLED, + SimpleBacklightMatrixEffect::Raindrops => D::RAINDROPS_ENABLED, + SimpleBacklightMatrixEffect::DualBeacon => D::DUAL_BEACON_ENABLED, + SimpleBacklightMatrixEffect::WaveLeftRight => D::WAVE_LEFT_RIGHT_ENABLED, + SimpleBacklightMatrixEffect::WaveUpDown => D::WAVE_UP_DOWN_ENABLED, + SimpleBacklightMatrixEffect::Reactive => D::REACTIVE_ENABLED, + SimpleBacklightMatrixEffect::ReactiveWide => D::REACTIVE_WIDE_ENABLED, + SimpleBacklightMatrixEffect::ReactiveCross => D::REACTIVE_CROSS_ENABLED, + SimpleBacklightMatrixEffect::ReactiveNexus => D::REACTIVE_NEXUS_ENABLED, + SimpleBacklightMatrixEffect::ReactiveSplash => D::REACTIVE_SPLASH_ENABLED, } } } -pub(super) struct BacklightAnimator> -where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, +pub struct SimpleBacklightMatrixAnimator< + D: SimpleBacklightMatrixDevice, + R: SimpleBacklightMatrixDriver, +> where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, { - pub(super) config: BacklightConfig, - pub(super) buf: [[u8; K::LIGHTING_COLS]; K::LIGHTING_ROWS], // Stores the brightness/value of each LED - pub(super) last_presses: ConstGenericRingBuffer<((u8, u8), u32), 8>, // Stores the row and col of the last 8 key presses, and the time (in ticks) it was pressed - pub(super) tick: u32, - pub(super) driver: D, - pub(super) bounds: LayoutBounds, - pub(super) rng: SmallRng, + config: SimpleBacklightMatrixConfig, + buf: [[u8; D::LIGHTING_COLS]; D::LIGHTING_ROWS], // Stores the brightness/value of each LED + last_presses: ConstGenericRingBuffer<((u8, u8), u32), 8>, // Stores the row and col of the last 8 key presses, and the time (in ticks) it was pressed + tick: u32, + driver: R, + bounds: LayoutBounds, + rng: SmallRng, } -impl> BacklightAnimator +impl> + SimpleBacklightMatrixAnimator where - [(); K::LIGHTING_COLS]:, - [(); K::LIGHTING_ROWS]:, + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, { - pub fn new(config: BacklightConfig, driver: D) -> Self { + pub fn new(config: SimpleBacklightMatrixConfig, driver: R) -> Self { Self { config, tick: 0, driver, - buf: [[0; K::LIGHTING_COLS]; K::LIGHTING_ROWS], + buf: [[0; D::LIGHTING_COLS]; D::LIGHTING_ROWS], last_presses: ConstGenericRingBuffer::new(), - bounds: get_led_layout_bounds::(), + bounds: get_led_layout_bounds::(), rng: SmallRng::seed_from_u64(1337), } } @@ -209,55 +344,55 @@ where }; } - pub async fn process_command(&mut self, command: BacklightCommand) { + pub fn process_command(&mut self, command: SimpleBacklightMatrixCommand) { match command { - BacklightCommand::Toggle => { + SimpleBacklightMatrixCommand::Toggle => { self.config.enabled = !self.config.enabled; } - BacklightCommand::TurnOn => { + SimpleBacklightMatrixCommand::TurnOn => { self.config.enabled = true; } - BacklightCommand::TurnOff => { + SimpleBacklightMatrixCommand::TurnOff => { self.config.enabled = false; } - BacklightCommand::NextEffect => { + SimpleBacklightMatrixCommand::NextEffect => { while { self.config.effect.increment(); - self.config.effect.is_enabled::() + !self.config.effect.is_enabled::() } {} } - BacklightCommand::PrevEffect => { + SimpleBacklightMatrixCommand::PrevEffect => { while { self.config.effect.decrement(); - self.config.effect.is_enabled::() + !self.config.effect.is_enabled::() } {} } - BacklightCommand::SetEffect(effect) => { + SimpleBacklightMatrixCommand::SetEffect(effect) => { self.config.effect = effect; } - BacklightCommand::SetValue(val) => { + SimpleBacklightMatrixCommand::SetValue(val) => { self.config.val = val; } - BacklightCommand::IncreaseValue(amount) => { + SimpleBacklightMatrixCommand::IncreaseValue(amount) => { self.config.val = self.config.val.saturating_add(amount); } - BacklightCommand::DecreaseValue(amount) => { + SimpleBacklightMatrixCommand::DecreaseValue(amount) => { self.config.val = self.config.val.saturating_sub(amount); } - BacklightCommand::SetSpeed(speed) => { + SimpleBacklightMatrixCommand::SetSpeed(speed) => { self.config.speed = speed; } - BacklightCommand::IncreaseSpeed(amount) => { + SimpleBacklightMatrixCommand::IncreaseSpeed(amount) => { self.config.speed = self.config.speed.saturating_add(amount); } - BacklightCommand::DecreaseSpeed(amount) => { + SimpleBacklightMatrixCommand::DecreaseSpeed(amount) => { self.config.speed = self.config.speed.saturating_sub(amount); } #[cfg(feature = "storage")] - BacklightCommand::SaveConfig => { - super::storage::BACKLIGHT_SAVE_SIGNAL.signal(()); + SimpleBacklightMatrixCommand::SaveConfig => { + // storage::BACKLIGHT_SAVE_SIGNAL.signal(()); } - BacklightCommand::ResetTime => { + SimpleBacklightMatrixCommand::ResetTime => { self.tick = 0; } }; @@ -268,12 +403,12 @@ where calc: impl Fn(&mut Self, u32, (u8, u8), (u8, u8)) -> u8, ) { let time = (self.tick << 8) - / (((K::FPS as u32) << 8) + / (((D::FPS as u32) << 8) / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); // `time` should increment by 255 every second - for row in 0..K::LIGHTING_ROWS { - for col in 0..K::LIGHTING_COLS { - if let Some(position) = K::get_backlight_matrix().layout[row][col] { + for row in 0..D::LIGHTING_ROWS { + for col in 0..D::LIGHTING_COLS { + if let Some(position) = D::get_backlight_matrix().layout[row][col] { self.buf[row][col] = scale( calc(self, time, (row as u8, col as u8), position), self.config.val, @@ -285,7 +420,7 @@ where pub fn register_event(&mut self, event: Event) { let time = (self.tick << 8) - / (((K::FPS as u32) << 8) + / (((D::FPS as u32) << 8) / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); match event { @@ -301,7 +436,7 @@ where } None => { // Check if the matrix position corresponds to a LED position before pushing - if K::get_backlight_matrix() + if D::get_backlight_matrix() .layout .get(row as usize) .and_then(|row| row.get(col as usize)) @@ -323,15 +458,15 @@ where } match self.config.effect { - BacklightEffect::Solid => { - if K::SIMPLE_BACKLIGHT_MATRIX_SOLID_ENABLED { + SimpleBacklightMatrixEffect::Solid => { + if D::SOLID_ENABLED { self.set_brightness_for_each_led(|_animator, _time, _coord, _pos| u8::MAX) } } - BacklightEffect::AlphasMods => { - if K::SIMPLE_BACKLIGHT_MATRIX_ALPHAS_MODS_ENABLED { + SimpleBacklightMatrixEffect::AlphasMods => { + if D::ALPHAS_MODS_ENABLED { self.set_brightness_for_each_led(|animator, _time, (row, col), _pos| { - if K::get_backlight_matrix().flags[row as usize][col as usize] + if D::get_backlight_matrix().flags[row as usize][col as usize] .contains(LEDFlags::ALPHA) { u8::MAX @@ -341,8 +476,8 @@ where }) } } - BacklightEffect::GradientUpDown => { - if K::SIMPLE_BACKLIGHT_MATRIX_GRADIENT_UP_DOWN_ENABLED { + SimpleBacklightMatrixEffect::GradientUpDown => { + if D::GRADIENT_UP_DOWN_ENABLED { let size = self.bounds.max.1 - self.bounds.min.1; self.set_brightness_for_each_led(|animator, _time, _coord, (_x, y)| { // Calculate the brightness for each LED based on it's Y position @@ -355,8 +490,8 @@ where }) } } - BacklightEffect::GradientLeftRight => { - if K::SIMPLE_BACKLIGHT_MATRIX_GRADIENT_LEFT_RIGHT_ENABLED { + SimpleBacklightMatrixEffect::GradientLeftRight => { + if D::GRADIENT_LEFT_RIGHT_ENABLED { let size = self.bounds.max.0 - self.bounds.min.0; self.set_brightness_for_each_led(|animator, _time, _coord, (x, _y)| { // Calculate the brightness for each LED based on it's X position @@ -369,15 +504,15 @@ where }) } } - BacklightEffect::Breathing => { - if K::SIMPLE_BACKLIGHT_MATRIX_BREATHING_ENABLED { + SimpleBacklightMatrixEffect::Breathing => { + if D::BREATHING_ENABLED { self.set_brightness_for_each_led(|_animator, time, _coord, _pos| { sin((time >> 2) as u8) // 4 seconds for one full cycle }) } } - BacklightEffect::Band => { - if K::SIMPLE_BACKLIGHT_MATRIX_BAND_ENABLED { + SimpleBacklightMatrixEffect::Band => { + if D::BAND_ENABLED { let size = self.bounds.max.0 - self.bounds.min.0; self.set_brightness_for_each_led(|animator, time, _coord, (x, _y)| { let pos = scale(time as u8, size); @@ -385,8 +520,8 @@ where }) } } - BacklightEffect::BandPinWheel => { - if K::SIMPLE_BACKLIGHT_MATRIX_BAND_PIN_WHEEL_ENABLED { + SimpleBacklightMatrixEffect::BandPinWheel => { + if D::BAND_PIN_WHEEL_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (x, y)| { // Base speed: 1 half-cycle every second let pos = time as u8; @@ -396,8 +531,8 @@ where }) } } - BacklightEffect::BandSpiral => { - if K::SIMPLE_BACKLIGHT_MATRIX_BAND_SPIRAL_ENABLED { + SimpleBacklightMatrixEffect::BandSpiral => { + if D::BAND_SPIRAL_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (x, y)| { // Base speed: 1 half-cycle every second let pos = time as u8; @@ -410,24 +545,24 @@ where }) } } - BacklightEffect::CycleLeftRight => { - if K::SIMPLE_BACKLIGHT_MATRIX_CYCLE_LEFT_RIGHT_ENABLED { + SimpleBacklightMatrixEffect::CycleLeftRight => { + if D::CYCLE_LEFT_RIGHT_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (x, _y)| { // Base speed: 1 cycle every second (x - animator.bounds.min.0).wrapping_sub(time as u8) }) } } - BacklightEffect::CycleUpDown => { - if K::SIMPLE_BACKLIGHT_MATRIX_CYCLE_UP_DOWN_ENABLED { + SimpleBacklightMatrixEffect::CycleUpDown => { + if D::CYCLE_UP_DOWN_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (_x, y)| { // Base speed: 1 cycle every second (y - animator.bounds.min.1).wrapping_sub(time as u8) }) } } - BacklightEffect::CycleOutIn => { - if K::SIMPLE_BACKLIGHT_MATRIX_CYCLE_OUT_IN_ENABLED { + SimpleBacklightMatrixEffect::CycleOutIn => { + if D::CYCLE_OUT_IN_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (x, y)| { // Base speed: 1 cycle every second let d = sqrtf( @@ -440,17 +575,17 @@ where }) } } - BacklightEffect::Raindrops => { - if K::SIMPLE_BACKLIGHT_MATRIX_RAINDROPS_ENABLED { - let adjusted_fps = (((K::FPS as u32) << 8) + SimpleBacklightMatrixEffect::Raindrops => { + if D::RAINDROPS_ENABLED { + let adjusted_fps = (((D::FPS as u32) << 8) / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))) as u8; // Randomly choose an LED to light up every 0.05 seconds if self.tick % (1 + scale(adjusted_fps, 13)) as u32 == 0 { let rand = self.rng.next_u32(); - let row = rand as u8 % K::LIGHTING_ROWS as u8; - let col = (rand >> 8) as u8 % K::LIGHTING_COLS as u8; + let row = rand as u8 % D::LIGHTING_ROWS as u8; + let col = (rand >> 8) as u8 % D::LIGHTING_COLS as u8; self.buf[row as usize][col as usize] = u8::MAX } @@ -461,8 +596,8 @@ where }) } } - BacklightEffect::DualBeacon => { - if K::SIMPLE_BACKLIGHT_MATRIX_DUAL_BEACON_ENABLED { + SimpleBacklightMatrixEffect::DualBeacon => { + if D::DUAL_BEACON_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (x, y)| { // Base speed: 1 cycle every second let pos = time as u8; @@ -474,8 +609,8 @@ where }) } } - BacklightEffect::WaveLeftRight => { - if K::SIMPLE_BACKLIGHT_MATRIX_WAVE_LEFT_RIGHT_ENABLED { + SimpleBacklightMatrixEffect::WaveLeftRight => { + if D::WAVE_LEFT_RIGHT_ENABLED { let size = self.bounds.max.0 - self.bounds.min.0; self.set_brightness_for_each_led(|animator, time, _coord, (x, _y)| { // Base speed: 1 cycle every second @@ -486,8 +621,8 @@ where }) } } - BacklightEffect::WaveUpDown => { - if K::SIMPLE_BACKLIGHT_MATRIX_WAVE_UP_DOWN_ENABLED { + SimpleBacklightMatrixEffect::WaveUpDown => { + if D::WAVE_UP_DOWN_ENABLED { let size = self.bounds.max.1 - self.bounds.min.1; self.set_brightness_for_each_led(|animator, time, _coord, (_x, y)| { // Base speed: 1 cycle every second @@ -498,8 +633,8 @@ where }) } } - BacklightEffect::Reactive => { - if K::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_ENABLED { + SimpleBacklightMatrixEffect::Reactive => { + if D::REACTIVE_ENABLED { self.set_brightness_for_each_led(|animator, time, (row, col), _pos| { // Base speed: LED fades after one second let time_of_last_press = animator.last_presses.iter().find( @@ -516,14 +651,14 @@ where }) } } - BacklightEffect::ReactiveWide => { - if K::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_WIDE_ENABLED { + SimpleBacklightMatrixEffect::ReactiveWide => { + if D::REACTIVE_WIDE_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (led_x, led_y)| { animator.last_presses.iter().fold( 0, |brightness: u8, ((pressed_row, pressed_col), press_time)| { // Base speed: LED fades after one second - if let Some((key_x, key_y)) = K::get_backlight_matrix().layout + if let Some((key_x, key_y)) = D::get_backlight_matrix().layout [*pressed_row as usize] [*pressed_col as usize] { @@ -545,13 +680,13 @@ where }) } } - BacklightEffect::ReactiveCross => { - if K::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_CROSS_ENABLED { + SimpleBacklightMatrixEffect::ReactiveCross => { + if D::REACTIVE_CROSS_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (led_x, led_y)| { animator.last_presses.iter().fold( 0, |brightness: u8, ((pressed_row, pressed_col), press_time)| { - if let Some((key_x, key_y)) = K::get_backlight_matrix().layout + if let Some((key_x, key_y)) = D::get_backlight_matrix().layout [*pressed_row as usize] [*pressed_col as usize] { @@ -574,13 +709,13 @@ where }) } } - BacklightEffect::ReactiveNexus => { - if K::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_NEXUS_ENABLED { + SimpleBacklightMatrixEffect::ReactiveNexus => { + if D::REACTIVE_NEXUS_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (led_x, led_y)| { animator.last_presses.iter().fold( 0, |brightness: u8, ((pressed_row, pressed_col), press_time)| { - if let Some((key_x, key_y)) = K::get_backlight_matrix().layout + if let Some((key_x, key_y)) = D::get_backlight_matrix().layout [*pressed_row as usize] [*pressed_col as usize] { @@ -609,13 +744,13 @@ where }) } } - BacklightEffect::ReactiveSplash => { - if K::SIMPLE_BACKLIGHT_MATRIX_REACTIVE_SPLASH_ENABLED { + SimpleBacklightMatrixEffect::ReactiveSplash => { + if D::REACTIVE_SPLASH_ENABLED { self.set_brightness_for_each_led(|animator, time, _coord, (led_x, led_y)| { animator.last_presses.iter().fold( 0, |brightness: u8, ((pressed_row, pressed_col), press_time)| { - if let Some((key_x, key_y)) = K::get_backlight_matrix().layout + if let Some((key_x, key_y)) = D::get_backlight_matrix().layout [*pressed_row as usize] [*pressed_col as usize] { @@ -652,4 +787,149 @@ where self.tick += 1; } + + #[cfg(feature = "storage")] + pub fn create_storage_instance(&self) -> SimpleBacklightMatrixStorage { + SimpleBacklightMatrixStorage { + _device_phantom: core::marker::PhantomData, + _driver_phantom: core::marker::PhantomData, + } + } +} + +impl> Animator + for SimpleBacklightMatrixAnimator +where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, +{ + type CommandType = SimpleBacklightMatrixCommand; + + type ConfigType = SimpleBacklightMatrixConfig; + + type BufferUpdateArgs = (); + + const FPS: usize = D::FPS; + + async fn initialize(&mut self) { + self.config = D::get_state().get().await; + + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + async fn tick(&mut self) { + self.tick().await + } + + fn is_waiting_for_command(&self) -> bool { + !(self.config.enabled && self.config.effect.is_animated()) + } + + fn process_command(&mut self, command: Self::CommandType) { + self.process_command(command) + } + + async fn handle_state_change(&mut self) { + // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled + let toggled = D::get_state() + .update(|config| { + let toggled = config.enabled != self.config.enabled; + **config = self.config; + toggled + }) + .await; + + if toggled { + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + // Send commands to be consumed by the split peripherals + #[cfg(feature = "split-central")] + { + use crate::split::central::private::MaybeCentralDevice; + if let Some(channel) = D::CentralDevice::get_message_to_peripheral_channel() { + channel + .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( + SimpleBacklightMatrixCommand::ResetTime, + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( + SimpleBacklightMatrixCommand::SetEffect(self.config.effect), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( + SimpleBacklightMatrixCommand::SetValue(self.config.val), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::SimpleBacklightMatrix( + SimpleBacklightMatrixCommand::SetSpeed(self.config.speed), + )) + .await; + } + } + } + + #[inline(always)] + fn get_command_channel() -> &'static Channel { + D::get_command_channel() + } + + #[inline(always)] + fn get_state() -> &'static State<'static, Self::ConfigType> { + D::get_state() + } +} + +#[cfg(feature = "storage")] +use storage::*; + +#[cfg(feature = "storage")] +mod storage { + use embassy_sync::signal::Signal; + + use crate::hw::platform::RawMutex; + + use super::{ + SimpleBacklightMatrixAnimator, SimpleBacklightMatrixDevice, SimpleBacklightMatrixDriver, + }; + + pub(super) static SIMPLE_BACKLIGHT_MATRIX_CONFIG_STATE_LISTENER: Signal = + Signal::new(); + pub(super) static SIMPLE_BACKLIGHT_MATRIX_SAVE_SIGNAL: Signal = Signal::new(); + + pub struct SimpleBacklightMatrixStorage { + pub(super) _driver_phantom: core::marker::PhantomData, + pub(super) _device_phantom: core::marker::PhantomData, + } + + impl> + crate::lighting::AnimatorStorage for SimpleBacklightMatrixStorage + where + [(); D::LIGHTING_COLS]:, + [(); D::LIGHTING_ROWS]:, + { + type Animator = SimpleBacklightMatrixAnimator; + + const STORAGE_KEY: crate::storage::StorageKey = + crate::storage::StorageKey::SimpleBacklightMatrixConfig; + + #[inline(always)] + fn get_state_listener() -> &'static Signal { + D::get_state_listener() + } + + #[inline(always)] + fn get_save_signal() -> &'static Signal { + D::get_save_signal() + } + } } diff --git a/rumcake/src/underglow/animations.rs b/rumcake/src/lighting/underglow.rs similarity index 60% rename from rumcake/src/underglow/animations.rs rename to rumcake/src/lighting/underglow.rs index d70e4e4..322ed60 100644 --- a/rumcake/src/underglow/animations.rs +++ b/rumcake/src/lighting/underglow.rs @@ -1,19 +1,155 @@ -use super::drivers::UnderglowDriver; -use super::UnderglowDevice; -use crate::math::{scale, sin}; -use crate::{Cycle, LEDEffect}; -use postcard::experimental::max_size::MaxSize; -use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; +use core::fmt::Debug; use defmt::{error, warn, Debug2Format}; +use embassy_sync::channel::Channel; use keyberon::layout::Event; use num_derive::FromPrimitive; +use postcard::experimental::max_size::MaxSize; use rand::rngs::SmallRng; use rand_core::{RngCore, SeedableRng}; +use rumcake_macros::{generate_items_from_enum_variants, Cycle, LEDEffect}; use serde::{Deserialize, Serialize}; use smart_leds::hsv::{hsv2rgb, Hsv}; use smart_leds::RGB8; +use crate::hw::platform::RawMutex; +use crate::math::{scale, sin}; +use crate::{Cycle, LEDEffect, State}; + +use super::Animator; + +/// A trait that keyboards must implement to use the underglow animator. +pub trait UnderglowDevice { + /// How fast the LEDs refresh to display a new animation frame. + /// + /// It is recommended to set this value to a value that your driver can handle, + /// otherwise your animations will appear to be slowed down. + /// + /// **This does not have any effect if the selected animation is static.** + const FPS: usize = 30; + + /// The number of LEDs used for underglow. + /// + /// This number will be used to determine the size of the frame buffer for underglow + /// animations. + const NUM_LEDS: usize; + + /// Get a reference to a channel that can receive commands to control the underglow animator + /// from other tasks. + #[inline(always)] + fn get_command_channel() -> &'static Channel { + /// Channel for sending underglow commands. + static UNDERGLOW_COMMAND_CHANNEL: Channel = Channel::new(); + + &UNDERGLOW_COMMAND_CHANNEL + } + + /// Get a reference to a state object that can be used to notify other tasks about changes to + /// the underglow configuration. Note that updating the state object will not control the + /// output of the underglow animator. + #[inline(always)] + fn get_state() -> &'static State<'static, UnderglowConfig> { + /// State that contains the current configuration for the underglow animator. Updating this state + /// does not control the output of the animator. + static UNDERGLOW_CONFIG_STATE: State = State::new( + UnderglowConfig::default(), + &[ + #[cfg(feature = "storage")] + &UNDERGLOW_CONFIG_STATE_LISTENER, + ], + ); + + &UNDERGLOW_CONFIG_STATE + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_state_listener() -> &'static embassy_sync::signal::Signal { + &UNDERGLOW_CONFIG_STATE_LISTENER + } + + #[cfg(feature = "storage")] + #[inline(always)] + fn get_save_signal() -> &'static embassy_sync::signal::Signal { + &UNDERGLOW_SAVE_SIGNAL + } + + #[cfg(feature = "split-central")] + type CentralDevice: crate::split::central::private::MaybeCentralDevice = + crate::split::central::private::EmptyCentralDevice; + + // Effect settings + underglow_effect_items!(); +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + + use crate::hw::platform::RawMutex; + use crate::State; + + use super::{UnderglowCommand, UnderglowConfig, UnderglowDevice}; + + pub trait MaybeUnderglowDevice { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> { + None + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, UnderglowConfig>> { + None + } + } + + impl MaybeUnderglowDevice for T { + #[inline(always)] + fn get_command_channel() -> Option<&'static Channel> { + Some(T::get_command_channel()) + } + + #[inline(always)] + fn get_state() -> Option<&'static State<'static, UnderglowConfig>> { + Some(T::get_state()) + } + } +} + +/// A trait that a driver must implement in order to power an underglow animator. +/// +/// This is an async version of the [`smart_leds::SmartLedsWrite`] trait. +pub trait UnderglowDriver { + /// The type of error that the driver will return if [`UnderglowDriver::write`] fails. + type DriverWriteError: Debug; + + /// Render out a frame buffer using the driver. + async fn write( + &mut self, + iterator: impl Iterator, + ) -> Result<(), Self::DriverWriteError>; + + /// The type of error that the driver will return if [`UnderglowDriver::turn_on`] fails. + type DriverEnableError: Debug; + + /// Turn the LEDs on using the driver when the animator gets enabled. + /// + /// The animator's [`tick()`](UnderglowAnimator::tick) method gets called directly after this, + /// and subsequently [`UnderglowDriver::write`]. So, if your driver doesn't need do anything + /// special to turn the LEDs on, you may simply return `Ok(())`. + async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; + + /// The type of error that the driver will return if [`UnderglowDriver::turn_off`] fails. + type DriverDisableError: Debug; + + /// Turn the LEDs off using the driver when the animator is disabled. + /// + /// The animator's [`tick()`](UnderglowAnimator::tick) method gets called directly after this. + /// However, the tick method will not call [`UnderglowDriver::write`] due to the animator being + /// disabled, so you will need to turn off the LEDs somehow. For example, you can write a + /// brightness of 0 to all LEDs. + async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; +} + #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, MaxSize)] pub struct UnderglowConfig { pub enabled: bool, @@ -44,28 +180,30 @@ impl Default for UnderglowConfig { } #[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] +#[non_exhaustive] +#[repr(u8)] pub enum UnderglowCommand { - Toggle, - TurnOn, - TurnOff, - NextEffect, - PrevEffect, - SetEffect(UnderglowEffect), - SetHue(u8), - IncreaseHue(u8), - DecreaseHue(u8), - SetSaturation(u8), - IncreaseSaturation(u8), - DecreaseSaturation(u8), - SetValue(u8), - IncreaseValue(u8), - DecreaseValue(u8), - SetSpeed(u8), - IncreaseSpeed(u8), - DecreaseSpeed(u8), + Toggle = 0, + TurnOn = 1, + TurnOff = 2, + NextEffect = 3, + PrevEffect = 4, + SetEffect(UnderglowEffect) = 5, + SetHue(u8) = 6, + IncreaseHue(u8) = 7, + DecreaseHue(u8) = 8, + SetSaturation(u8) = 9, + IncreaseSaturation(u8) = 10, + DecreaseSaturation(u8) = 11, + SetValue(u8) = 12, + IncreaseValue(u8) = 13, + DecreaseValue(u8) = 14, + SetSpeed(u8) = 15, + IncreaseSpeed(u8) = 16, + DecreaseSpeed(u8) = 17, #[cfg(feature = "storage")] - SaveConfig, - ResetTime, // normally used internally for syncing LEDs for split keyboards + SaveConfig = 18, + ResetTime = 19, // normally used internally for syncing LEDs for split keyboards } #[generate_items_from_enum_variants("const {variant_shouty_snake_case}_ENABLED: bool = true")] @@ -137,20 +275,20 @@ impl UnderglowEffect { } } -pub(super) struct UnderglowAnimator, D: UnderglowDevice> +pub struct UnderglowAnimator> where [(); D::NUM_LEDS]:, { - pub(super) config: UnderglowConfig, - pub(super) buf: [RGB8; D::NUM_LEDS], - pub(super) twinkle_state: [(Hsv, u8); D::NUM_LEDS], // For the twinkle effect specifically, tracks the lifespan of lit LEDs. - pub(super) tick: u32, - pub(super) time_of_last_press: u32, - pub(super) driver: R, - pub(super) rng: SmallRng, + config: UnderglowConfig, + buf: [RGB8; D::NUM_LEDS], + twinkle_state: [(Hsv, u8); D::NUM_LEDS], // For the twinkle effect specifically, tracks the lifespan of lit LEDs. + tick: u32, + time_of_last_press: u32, + driver: R, + rng: SmallRng, } -impl, D: UnderglowDevice> UnderglowAnimator +impl> UnderglowAnimator where [(); D::NUM_LEDS]:, { @@ -185,7 +323,7 @@ where }; } - pub async fn process_command(&mut self, command: UnderglowCommand) { + pub fn process_command(&mut self, command: UnderglowCommand) { match command { UnderglowCommand::Toggle => { self.config.enabled = !self.config.enabled; @@ -250,7 +388,7 @@ where } #[cfg(feature = "storage")] UnderglowCommand::SaveConfig => { - super::storage::UNDERGLOW_SAVE_SIGNAL.signal(()); + D::get_save_signal().signal(()); } UnderglowCommand::ResetTime => { self.tick = 0; @@ -271,13 +409,15 @@ where } pub fn register_event(&mut self, event: Event) { - match event { - Event::Press(_x, _y) => { - self.time_of_last_press = (self.tick << 8) - / (((D::FPS as u32) << 8) - / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); + if self.config.enabled && self.config.effect.is_reactive() { + match event { + Event::Press(_x, _y) => { + self.time_of_last_press = (self.tick << 8) + / (((D::FPS as u32) << 8) + / (self.config.speed as u32 + 128 + (self.config.speed as u32 >> 1))); + } + Event::Release(_x, _y) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. } - Event::Release(_x, _y) => {} // nothing for now. maybe change some effects to behave depending on the state of a key. } } @@ -532,4 +672,152 @@ where self.tick += 1; } + + #[cfg(feature = "storage")] + pub fn create_storage_instance(&self) -> UnderglowStorage { + UnderglowStorage { + _device_phantom: core::marker::PhantomData, + _driver_phantom: core::marker::PhantomData, + } + } +} + +impl> Animator for UnderglowAnimator +where + [(); D::NUM_LEDS]:, +{ + type CommandType = UnderglowCommand; + + type ConfigType = UnderglowConfig; + + type BufferUpdateArgs = (); + + const FPS: usize = D::FPS; + + async fn initialize(&mut self) { + self.config = D::get_state().get().await; + + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + async fn tick(&mut self) { + self.tick().await + } + + fn is_waiting_for_command(&self) -> bool { + !(self.config.enabled && self.config.effect.is_animated()) + } + + fn process_command(&mut self, command: Self::CommandType) { + self.process_command(command) + } + + async fn handle_state_change(&mut self) { + // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled + let toggled = D::get_state() + .update(|config| { + let toggled = config.enabled != self.config.enabled; + **config = self.config; + toggled + }) + .await; + + if toggled { + match self.config.enabled { + true => self.turn_on().await, + false => self.turn_off().await, + } + } + + // Send commands to be consumed by the split peripherals + #[cfg(feature = "split-central")] + { + use crate::split::central::private::MaybeCentralDevice; + if let Some(channel) = D::CentralDevice::get_message_to_peripheral_channel() { + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::ResetTime, + )) + .await; + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::SetEffect(self.config.effect), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::SetHue(self.config.hue), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::SetSaturation(self.config.sat), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::SetValue(self.config.val), + )) + .await; + channel + .send(crate::split::MessageToPeripheral::Underglow( + UnderglowCommand::SetSpeed(self.config.speed), + )) + .await; + } + } + } + + #[inline(always)] + fn get_command_channel() -> &'static Channel { + D::get_command_channel() + } + + #[inline(always)] + fn get_state() -> &'static State<'static, Self::ConfigType> { + D::get_state() + } +} + +#[cfg(feature = "storage")] +use storage::*; + +#[cfg(feature = "storage")] +mod storage { + use embassy_sync::signal::Signal; + + use crate::hw::platform::RawMutex; + + use super::{UnderglowAnimator, UnderglowDevice, UnderglowDriver}; + + pub(super) static UNDERGLOW_CONFIG_STATE_LISTENER: Signal = Signal::new(); + pub(super) static UNDERGLOW_SAVE_SIGNAL: Signal = Signal::new(); + + pub struct UnderglowStorage { + pub(super) _device_phantom: core::marker::PhantomData, + pub(super) _driver_phantom: core::marker::PhantomData, + } + + impl> crate::lighting::AnimatorStorage + for UnderglowStorage + where + [(); D::NUM_LEDS]:, + { + type Animator = UnderglowAnimator; + + const STORAGE_KEY: crate::storage::StorageKey = crate::storage::StorageKey::UnderglowConfig; + + #[inline(always)] + fn get_state_listener() -> &'static Signal { + D::get_state_listener() + } + + #[inline(always)] + fn get_save_signal() -> &'static Signal { + D::get_save_signal() + } + } } diff --git a/rumcake/src/split/central.rs b/rumcake/src/split/central.rs index ff80fa8..07c6c01 100644 --- a/rumcake/src/split/central.rs +++ b/rumcake/src/split/central.rs @@ -7,37 +7,117 @@ //! will also be responsible for sending their related commands to the peripherals (see //! [`MessageToPeripheral`]). +use core::fmt::Debug; + use defmt::{error, Debug2Format}; use embassy_futures::select::{select, Either}; use embassy_sync::channel::Channel; +use embedded_io_async::ReadExactError; +use postcard::Error; + +use super::{MessageToCentral, MessageToPeripheral}; +use crate::hw::platform::RawMutex; +use crate::keyboard::KeyboardLayout; + +pub trait CentralDevice { + /// The layout to send matrix events (which were received by peripherals) to. + type Layout: KeyboardLayout; + + /// Get a reference to a channel that can receive messages from other tasks to be sent to + /// peripherals. + fn get_message_to_peripheral_channel() -> &'static Channel { + static MESSAGE_TO_PERIPHERALS: Channel = Channel::new(); + + &MESSAGE_TO_PERIPHERALS + } +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + + use crate::hw::platform::RawMutex; + use crate::split::MessageToPeripheral; + + use super::CentralDevice; + + pub struct EmptyCentralDevice; + impl MaybeCentralDevice for EmptyCentralDevice {} + + pub trait MaybeCentralDevice { + #[inline(always)] + fn get_message_to_peripheral_channel( + ) -> Option<&'static Channel> { + None + } + } -use crate::hw::mcu::RawMutex; -use crate::keyboard::POLLED_EVENTS_CHANNEL; -use crate::split::MessageToCentral; + impl MaybeCentralDevice for T { + #[inline(always)] + fn get_message_to_peripheral_channel( + ) -> Option<&'static Channel> { + Some(T::get_message_to_peripheral_channel()) + } + } +} -use super::drivers::CentralDeviceDriver; -use super::MessageToPeripheral; +/// A trait that a driver must implement to allow a central device to send and receive messages from peripherals. +pub trait CentralDeviceDriver { + /// The type of error that the driver will return if it fails to receive or send a message. + type DriverError: Debug; -/// Channel for sending messages to peripherals. -/// -/// Channel messages should be consumed by the central task, so user-level code should -/// **not** attempt to receive messages from the channel, otherwise commands may not be processed -/// appropriately. You should only send to this channel. -pub static MESSAGE_TO_PERIPHERALS: Channel = Channel::new(); + /// Receive a message from a peripheral device ([`MessageToCentral`]). + async fn receive_message_from_peripherals( + &mut self, + ) -> Result>; + + /// Send a [`MessageToPeripheral`] to all connected peripherals using the driver. + async fn broadcast_message_to_peripherals( + &mut self, + message: MessageToPeripheral, + ) -> Result<(), CentralDeviceError>; +} + +#[derive(Debug)] +/// Types of errors that can occur when a central device sends and receives messages from peripherals +pub enum CentralDeviceError { + /// Wrapper around an error provided by a driver implementation + /// ([`CentralDeviceDriver::DriverError`]). + DriverError(E), + /// An error that can occur if the driver fails to deserialize the data from a peripheral into + /// a [`MessageToCentral`]. + DeserializationError(Error), + /// An error that can occur if the driver fails to serialize the data when sending a + /// [`MessageToPeripheral`]. + SerializationError(Error), + /// Reached an EOF unexpectedly when trying to receive data from a peripheral. + UnexpectedEof, +} + +impl From> for CentralDeviceError { + fn from(value: ReadExactError) -> Self { + match value { + ReadExactError::UnexpectedEof => CentralDeviceError::UnexpectedEof, + ReadExactError::Other(e) => CentralDeviceError::DriverError(e), + } + } +} #[rumcake_macros::task] -pub async fn central_task(mut driver: impl CentralDeviceDriver) { +pub async fn central_task(_k: K, mut driver: impl CentralDeviceDriver) { + let message_to_peripherals_channel = K::get_message_to_peripheral_channel(); + let matrix_events_channel = K::Layout::get_matrix_events_channel(); + loop { match select( driver.receive_message_from_peripherals(), - MESSAGE_TO_PERIPHERALS.receive(), + message_to_peripherals_channel.receive(), ) .await { Either::First(message) => match message { Ok(event) => match event { MessageToCentral::KeyPress(_, _) | MessageToCentral::KeyRelease(_, _) => { - POLLED_EVENTS_CHANNEL.send(event.try_into().unwrap()).await; + matrix_events_channel.send(event.try_into().unwrap()).await; } }, Err(err) => { diff --git a/rumcake/src/split/drivers.rs b/rumcake/src/split/drivers.rs deleted file mode 100644 index 669c0b4..0000000 --- a/rumcake/src/split/drivers.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! A set of traits that split keyboard drivers must implement, and error types that can be used by -//! driver implementations. - -use core::fmt::Debug; - -use embedded_io_async::ReadExactError; -use postcard::Error; - -use super::MessageToCentral; -use super::MessageToPeripheral; - -/// A trait that a driver must implement to allow a central device to send and receive messages from peripherals. -pub trait CentralDeviceDriver { - /// The type of error that the driver will return if it fails to receive or send a message. - type DriverError: Debug; - - /// Receive a message from a peripheral device ([`MessageToCentral`]). - async fn receive_message_from_peripherals( - &mut self, - ) -> Result>; - - /// Send a [`MessageToPeripheral`] to all connected peripherals using the driver. - async fn broadcast_message_to_peripherals( - &mut self, - message: MessageToPeripheral, - ) -> Result<(), CentralDeviceError>; -} - -#[derive(Debug)] -/// Types of errors that can occur when a central device sends and receives messages from peripherals -pub enum CentralDeviceError { - /// Wrapper around an error provided by a driver implementation - /// ([`CentralDeviceDriver::DriverError`]). - DriverError(E), - /// An error that can occur if the driver fails to deserialize the data from a peripheral into - /// a [`MessageToCentral`]. - DeserializationError(Error), - /// An error that can occur if the driver fails to serialize the data when sending a - /// [`MessageToPeripheral`]. - SerializationError(Error), - /// Reached an EOF unexpectedly when trying to receive data from a peripheral. - UnexpectedEof, -} - -impl From> for CentralDeviceError { - fn from(value: ReadExactError) -> Self { - match value { - ReadExactError::UnexpectedEof => CentralDeviceError::UnexpectedEof, - ReadExactError::Other(e) => CentralDeviceError::DriverError(e), - } - } -} - -/// A trait that a driver must implement to allow a peripheral device to send and receive messages from the central device. -pub trait PeripheralDeviceDriver { - /// The type of error that the driver will return if it fails to receive or send a message. - type DriverError: Debug; - - /// Send a [`MessageToCentral`] using the driver. - async fn send_message_to_central( - &mut self, - event: MessageToCentral, - ) -> Result<(), PeripheralDeviceError>; - - /// Receive a message from the central device ([`MessageToPeripheral`]) using the driver. - async fn receive_message_from_central( - &mut self, - ) -> Result>; -} - -#[derive(Debug)] -/// Types of errors that can occur when a peripheral device sends and receives messages from a central device -pub enum PeripheralDeviceError { - /// Wrapper around an error provided by a driver implementation - /// ([`PeripheralDeviceDriver::DriverError`]). - DriverError(T), - /// An error that can occur if the driver fails to deserialize the data from a central device - /// into a [`MessageToPeripheral`]. - DeserializationError(Error), - /// An error that can occur if the driver fails to serialize the data when sending a - /// [`MessageToCentral`]. - SerializationError(Error), - /// Reached an EOF unexpectedly when trying to receive data from a central device. - UnexpectedEof, -} - -impl From> for PeripheralDeviceError { - fn from(value: ReadExactError) -> Self { - match value { - ReadExactError::UnexpectedEof => PeripheralDeviceError::UnexpectedEof, - ReadExactError::Other(e) => PeripheralDeviceError::DriverError(e), - } - } -} diff --git a/rumcake/src/split/mod.rs b/rumcake/src/split/mod.rs index 058c741..dade424 100644 --- a/rumcake/src/split/mod.rs +++ b/rumcake/src/split/mod.rs @@ -4,16 +4,15 @@ use keyberon::layout::Event; use postcard::experimental::max_size::MaxSize; use serde::{Deserialize, Serialize}; -pub mod drivers; - #[cfg(feature = "split-central")] pub mod central; #[cfg(feature = "split-peripheral")] pub mod peripheral; -#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] /// Possible messages that can be sent to a central device. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] +#[repr(u8)] pub enum MessageToCentral { /// Key press in the form of (row, col). KeyPress(u8, u8), @@ -44,29 +43,32 @@ impl TryFrom for Event { } } -#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] /// Possible messages that can be sent to a peripheral device. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, MaxSize)] +#[non_exhaustive] +#[repr(u8)] pub enum MessageToPeripheral { #[cfg(feature = "simple-backlight")] - /// A [`BacklightCommand`](crate::backlight::simple_backlight::animations::BacklightCommand) to + /// A [`SimpleBacklightCommand`](crate::lighting::simple_backlight::SimpleBacklightCommand) to /// be processed by the peripheral's simple backlight animator. - SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand), + SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand) = 3, #[cfg(feature = "simple-backlight-matrix")] /// A - /// [`BacklightCommand`](crate::backlight::simple_backlight_matrix::animations::BacklightCommand) + /// [`SimpleBacklightMatrixCommand`](crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand) /// to be processed by the peripheral's simple backlight matrix animator. - SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand), + SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand) = + 4, #[cfg(feature = "rgb-backlight-matrix")] /// A - /// [`BacklightCommand`](crate::backlight::rgb_backlight_matrix::animations::BacklightCommand) + /// [`RGBBacklightMatrixCommand`](crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand) /// to be processed by the peripheral's RGB backlight matrix animator. - RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand), + RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand) = 5, #[cfg(feature = "underglow")] - /// An [`UnderglowCommand`](crate::underglow::animations::UnderglowCommand) to be processed by the peripheral's backlight animator. - Underglow(crate::underglow::animations::UnderglowCommand), + /// An [`UnderglowCommand`](crate::lighting::underglow::UnderglowCommand) to be processed by the peripheral's backlight animator. + Underglow(crate::lighting::underglow::UnderglowCommand) = 6, } /// Size of buffer used when sending messages to a peripheral device diff --git a/rumcake/src/split/peripheral.rs b/rumcake/src/split/peripheral.rs index a10c407..21e18ec 100644 --- a/rumcake/src/split/peripheral.rs +++ b/rumcake/src/split/peripheral.rs @@ -6,22 +6,120 @@ //! extra features, then all the peripherals should receive the related commands from the central //! device (see [`MessageToPeripheral`]). +use core::fmt::Debug; + use defmt::{error, Debug2Format}; use embassy_futures::select::{select, Either}; +use embassy_sync::channel::Channel; use embassy_sync::pubsub::PubSubBehavior; +use embedded_io_async::ReadExactError; +use keyberon::layout::Event; +use postcard::Error; + +use super::{MessageToCentral, MessageToPeripheral}; +use crate::hw::platform::RawMutex; +use crate::keyboard::MATRIX_EVENTS; + +// Trait that devices must implement to serve as a peripheral in a split keyboard setup. +pub trait PeripheralDevice { + /// Get a reference to a channel that can receive matrix events from other tasks to be + /// processed into keycodes. + fn get_matrix_events_channel() -> &'static Channel { + static POLLED_EVENTS_CHANNEL: Channel = Channel::new(); + + &POLLED_EVENTS_CHANNEL + } + + #[cfg(feature = "underglow")] + type UnderglowDeviceType: crate::lighting::underglow::private::MaybeUnderglowDevice = + crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "simple-backlight")] + type SimpleBacklightDeviceType: crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice = + crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "simple-backlight-matrix")] + type SimpleBacklightMatrixDeviceType: crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice = crate::lighting::private::EmptyLightingDevice; + + #[cfg(feature = "rgb-backlight-matrix")] + type RGBBacklightMatrixDeviceType: crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice = crate::lighting::private::EmptyLightingDevice; +} + +pub(crate) mod private { + use embassy_sync::channel::Channel; + use keyberon::layout::Event; + + use crate::hw::platform::RawMutex; + + use super::PeripheralDevice; + + pub struct EmptyPeripheralDevice; + impl MaybePeripheralDevice for EmptyPeripheralDevice {} + + pub trait MaybePeripheralDevice { + fn get_matrix_events_channel() -> Option<&'static Channel> { + None + } + } + + impl MaybePeripheralDevice for T { + fn get_matrix_events_channel() -> Option<&'static Channel> { + Some(T::get_matrix_events_channel()) + } + } +} -use crate::keyboard::{MATRIX_EVENTS, POLLED_EVENTS_CHANNEL}; -use crate::split::MessageToPeripheral; +/// A trait that a driver must implement to allow a peripheral device to send and receive messages from the central device. +pub trait PeripheralDeviceDriver { + /// The type of error that the driver will return if it fails to receive or send a message. + type DriverError: Debug; -use super::drivers::PeripheralDeviceDriver; + /// Send a [`MessageToCentral`] using the driver. + async fn send_message_to_central( + &mut self, + event: MessageToCentral, + ) -> Result<(), PeripheralDeviceError>; + + /// Receive a message from the central device ([`MessageToPeripheral`]) using the driver. + async fn receive_message_from_central( + &mut self, + ) -> Result>; +} + +#[derive(Debug)] +/// Types of errors that can occur when a peripheral device sends and receives messages from a central device +pub enum PeripheralDeviceError { + /// Wrapper around an error provided by a driver implementation + /// ([`PeripheralDeviceDriver::DriverError`]). + DriverError(T), + /// An error that can occur if the driver fails to deserialize the data from a central device + /// into a [`MessageToPeripheral`]. + DeserializationError(Error), + /// An error that can occur if the driver fails to serialize the data when sending a + /// [`MessageToCentral`]. + SerializationError(Error), + /// Reached an EOF unexpectedly when trying to receive data from a central device. + UnexpectedEof, +} + +impl From> for PeripheralDeviceError { + fn from(value: ReadExactError) -> Self { + match value { + ReadExactError::UnexpectedEof => PeripheralDeviceError::UnexpectedEof, + ReadExactError::Other(e) => PeripheralDeviceError::DriverError(e), + } + } +} // This task replaces the `layout_collect` task, which is usually used on non-split keyboards for sending events to the keyboard layout #[rumcake_macros::task] -pub async fn peripheral_task(mut driver: impl PeripheralDeviceDriver) { +pub async fn peripheral_task(_k: K, mut driver: impl PeripheralDeviceDriver) { + let channel = K::get_matrix_events_channel(); + loop { match select( driver.receive_message_from_central(), - POLLED_EVENTS_CHANNEL.receive(), + channel.receive(), ) .await { @@ -29,27 +127,27 @@ pub async fn peripheral_task(mut driver: impl PeripheralDeviceDriver) { Ok(message) => match message { #[cfg(feature = "simple-backlight")] MessageToPeripheral::SimpleBacklight(command) => { - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await + if let Some(channel) = ::get_command_channel() { + channel.send(command).await + } } #[cfg(feature = "simple-backlight-matrix")] MessageToPeripheral::SimpleBacklightMatrix(command) => { - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await + if let Some(channel) = ::get_command_channel() { + channel.send(command).await + } } #[cfg(feature = "rgb-backlight-matrix")] MessageToPeripheral::RGBBacklightMatrix(command) => { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await + if let Some(channel) = ::get_command_channel() { + channel.send(command).await + } } #[cfg(feature = "underglow")] MessageToPeripheral::Underglow(command) => { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(command) - .await + if let Some(channel) = ::get_command_channel() { + channel.send(command).await + } } #[allow(unreachable_patterns)] _ => {} diff --git a/rumcake/src/storage.rs b/rumcake/src/storage.rs index fe608a5..c391f23 100644 --- a/rumcake/src/storage.rs +++ b/rumcake/src/storage.rs @@ -11,6 +11,7 @@ use core::cell::{Cell, RefCell}; use core::fmt::Debug; use core::hash::{Hash, Hasher, SipHasher}; +use core::marker::PhantomData; use defmt::{assert, debug}; use defmt::{error, info, warn, Debug2Format}; @@ -26,7 +27,7 @@ use serde::Serialize; use tickv::success_codes::SuccessCode; use tickv::{AsyncTicKV, ErrorCode, FlashController, MAIN_KEY}; -use crate::hw::mcu::RawMutex; +use crate::hw::platform::RawMutex; fn get_hashed_key(key: &[u8]) -> u64 { let mut hasher = SipHasher::new(); @@ -38,13 +39,13 @@ fn get_hashed_key(key: &[u8]) -> u64 { #[derive(Debug, FromPrimitive, Copy, Clone)] #[repr(u8)] pub enum StorageKey { - /// Key to store [`crate::backlight::simple_backlight::animations::BacklightConfig`]. + /// Key to store [`crate::lighting::simple_backlight::SimpleBacklightConfig`]. SimpleBacklightConfig = 0x00, - /// Key to store [`crate::backlight::simple_backlight_matrix::animations::BacklightConfig`]. + /// Key to store [`crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixConfig`]. SimpleBacklightMatrixConfig = 0x01, - /// Key to store [`crate::backlight::rgb_backlight_matrix::animations::BacklightConfig`]. + /// Key to store [`crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixConfig`]. RGBBacklightMatrixConfig = 0x02, - /// Key to store [`crate::underglow::animations::UnderglowConfig`]. + /// Key to store [`crate::lighting::underglow::UnderglowConfig`]. UnderglowConfig = 0x10, /// Key to store bluetooth profiles, used by the `nrf-ble` implementation of bluetooth host communication. BluetoothProfiles = 0x20, @@ -72,14 +73,15 @@ enum StorageKeyType { /// A wrapper around a TicKV instance which allows you to receive requests to read, write or delete /// data from a storage peripheral. -pub struct StorageService<'a, F: FlashStorage> +pub struct StorageService<'a, F: FlashStorage, S> where [(); F::ERASE_SIZE]:, { database: OnceCell, { F::ERASE_SIZE }>>>, + _phantom: PhantomData, } -impl<'a, F: FlashStorage> StorageService<'a, F> +impl<'a, F: FlashStorage, S: StorageDevice> StorageService<'a, F, S> where [(); F::ERASE_SIZE]:, { @@ -88,6 +90,7 @@ where pub const fn new() -> Self { StorageService { database: OnceCell::new(), + _phantom: PhantomData, } } @@ -126,11 +129,11 @@ where /// update the metadata. pub(crate) async fn check_metadata( &self, - buffer: &'static mut [u8], key: StorageKey, current_metadata: &[u8], ) -> Result<(), ()> { let mut database = self.get_database().await; + let buffer = S::get_storage_buffer(); // Verify if the underlying data type has changed since last boot let (will_reset, buf) = match get_key( @@ -195,12 +198,9 @@ where /// Read and deserialize data from the storage peripheral, using the given /// key to look it up. Uses [`postcard`] for deserialization. - pub async fn read( - &self, - buffer: &'static mut [u8], - key: StorageKey, - ) -> Result { + pub async fn read(&self, key: StorageKey) -> Result { let mut database = self.get_database().await; + let buffer = S::get_storage_buffer(); info!( "[STORAGE] Reading {} data.", @@ -238,12 +238,9 @@ where /// Read data from the storage peripheral, using the given key to look it up. This skips the /// deserialization step, returning raw bytes. - pub async fn read_raw( - &self, - buffer: &'static mut [u8], - key: StorageKey, - ) -> Result<(&[u8], usize), ()> { + pub async fn read_raw(&self, key: StorageKey) -> Result<(&[u8], usize), ()> { let mut database = self.get_database().await; + let buffer = S::get_storage_buffer(); info!( "[STORAGE] Reading {} data.", @@ -270,13 +267,9 @@ where /// Write data to the storage peripheral, at the given key. This will serialize the given data /// using [`postcard`] before storing it. - pub async fn write( - &self, - buffer: &'static mut [u8], - key: StorageKey, - data: T, - ) -> Result<(), ()> { + pub async fn write(&self, key: StorageKey, data: T) -> Result<(), ()> { let mut database = self.get_database().await; + let buffer = S::get_storage_buffer(); info!( "[STORAGE] Writing new {} data.", @@ -321,13 +314,9 @@ where /// Write data to the storage peripheral, at the given key. This skips the serialization step, /// allowing you to write raw bytes to storage. - pub async fn write_raw( - &self, - buffer: &'static mut [u8], - key: StorageKey, - data: &[u8], - ) -> Result<(), ()> { + pub async fn write_raw(&self, key: StorageKey, data: &[u8]) -> Result<(), ()> { let mut database = self.get_database().await; + let buffer = S::get_storage_buffer(); info!( "[STORAGE] Writing new {} data.", @@ -539,6 +528,13 @@ pub trait StorageDevice { static mut STORAGE_BUFFER: [u8; 1024] = [0; 1024]; unsafe { &mut STORAGE_BUFFER } } + + type FlashStorageType: FlashStorage; + + fn get_storage_service() -> &'static StorageService<'static, Self::FlashStorageType, Self> + where + [(); Self::FlashStorageType::ERASE_SIZE]:, + Self: Sized; } #[derive(Debug, Clone, Copy)] @@ -799,3 +795,43 @@ impl<'a, F: FlashStorage> FlashController<{ F::ERASE_SIZE }> for FlashDevice<'a, Err(tickv::ErrorCode::EraseNotReady(region_number)) } } + +pub(crate) mod private { + use super::{FlashStorage, StorageDevice, StorageService}; + + pub struct EmptyFlashDevice; + impl FlashStorage for EmptyFlashDevice { + type Error = (); + + const ERASE_SIZE: usize = 0; + + async fn erase(&mut self, _from: u32, _to: u32) -> Result<(), Self::Error> { + unreachable!() + } + + async fn write(&mut self, _offset: u32, _bytes: &[u8]) -> Result<(), Self::Error> { + unreachable!() + } + + async fn read(&mut self, _offset: u32, _bytes: &mut [u8]) -> Result<(), Self::Error> { + unreachable!() + } + + fn blocking_read(&mut self, _offset: u32, _bytes: &mut [u8]) -> Result<(), Self::Error> { + unreachable!() + } + } + + pub struct EmptyStorageDevice; + impl StorageDevice for EmptyStorageDevice { + type FlashStorageType = EmptyFlashDevice; + + fn get_storage_service() -> &'static StorageService<'static, Self::FlashStorageType, Self> + where + [(); Self::FlashStorageType::ERASE_SIZE]:, + Self: Sized, + { + unreachable!() + } + } +} diff --git a/rumcake/src/underglow/drivers.rs b/rumcake/src/underglow/drivers.rs deleted file mode 100644 index fc705cd..0000000 --- a/rumcake/src/underglow/drivers.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! A trait that underglow drivers must implement. - -use core::fmt::Debug; - -use smart_leds::RGB8; - -use super::UnderglowDevice; - -/// A trait that a driver must implement in order to work with the underglow task. -/// -/// This is an async version of the [`smart_leds::SmartLedsWrite`] trait. -pub trait UnderglowDriver { - /// The type of error that the driver will return if [`UnderglowDriver::write`] fails. - type DriverWriteError: Debug; - - /// Render out a frame buffer using the driver. - async fn write( - &mut self, - iterator: impl Iterator, - ) -> Result<(), Self::DriverWriteError>; - - /// The type of error that the driver will return if [`UnderglowDriver::turn_on`] fails. - type DriverEnableError: Debug; - - /// Turn the LEDs on using the driver when the animator gets enabled. - /// - /// The animator's [`tick()`](super::animations::UnderglowAnimator::tick) method gets called - /// directly after this, and subsequently [`UnderglowDriver::write`]. So, if your - /// driver doesn't need do anything special to turn the LEDs on, you may simply return - /// `Ok(())`. - async fn turn_on(&mut self) -> Result<(), Self::DriverEnableError>; - - /// The type of error that the driver will return if [`UnderglowDriver::turn_off`] fails. - type DriverDisableError: Debug; - - /// Turn the LEDs off using the driver when the animator is disabled. - /// - /// The animator's [`tick()`](super::animations::UnderglowAnimator::tick) method gets called - /// directly after this. However, the tick method will not call - /// [`UnderglowDriver::write`] due to the animator being disabled, so you will need to - /// turn off the LEDs somehow. For example, you can write a brightness of 0 to all LEDs. - async fn turn_off(&mut self) -> Result<(), Self::DriverDisableError>; -} diff --git a/rumcake/src/underglow/mod.rs b/rumcake/src/underglow/mod.rs deleted file mode 100644 index cea5ce0..0000000 --- a/rumcake/src/underglow/mod.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Underglow features. -//! -//! To use underglow features, keyboards must implement [`UnderglowDevice`], and the trait -//! corresponding to a driver that implements [`drivers::UnderglowDriver`]. - -use embassy_futures::select::{select, Either}; -use embassy_sync::channel::Channel; -use embassy_time::{Duration, Ticker}; - -use crate::hw::mcu::RawMutex; -use crate::keyboard::MATRIX_EVENTS; -use crate::{LEDEffect, State}; - -use self::animations::{ - underglow_effect_items, UnderglowAnimator, UnderglowCommand, UnderglowConfig, -}; -use self::drivers::UnderglowDriver; - -pub mod animations; -pub mod drivers; - -/// A trait that keyboards must implement to use the underglow feature. -pub trait UnderglowDevice { - /// How fast the LEDs refresh to display a new animation frame. - /// - /// It is recommended to set this value to a value that your driver can handle, - /// otherwise your animations will appear to be slowed down. - /// - /// **This does not have any effect if the selected animation is static.** - const FPS: usize = 30; - - /// The number of LEDs used for underglow. - /// - /// This number will be used to determine the size of the frame buffer for underglow - /// animations. - const NUM_LEDS: usize; - - // Effect settings - underglow_effect_items!(); -} - -/// Channel for sending underglow commands. -/// -/// Channel messages should be consumed by the [`underglow_task`], so user-level -/// level code should **not** attempt to receive messages from the channel, otherwise -/// commands may not be processed appropriately. You should only send to this channel. -pub static UNDERGLOW_COMMAND_CHANNEL: Channel = Channel::new(); - -/// State that contains the current configuration for the underglow animator. -pub static UNDERGLOW_CONFIG_STATE: State = State::new( - UnderglowConfig::default(), - &[ - #[cfg(feature = "storage")] - &storage::UNDERGLOW_CONFIG_STATE_LISTENER, - ], -); - -#[rumcake_macros::task] -pub async fn underglow_task(_k: D, driver: impl UnderglowDriver) -where - [(); D::NUM_LEDS]:, -{ - let mut subscriber = MATRIX_EVENTS.subscriber().unwrap(); - let mut ticker = Ticker::every(Duration::from_millis(1000 / D::FPS as u64)); - - // This animator has a local copy of the underglow config state so that it doesn't have to lock the config every frame - let mut animator = UnderglowAnimator::new(UNDERGLOW_CONFIG_STATE.get().await, driver); - match animator.config.enabled { - true => animator.turn_on().await, - false => animator.turn_off().await, - } - animator.tick().await; // Force a frame to be rendered in the event that the initial effect is static. - - loop { - let command = if !(animator.config.enabled && animator.config.effect.is_animated()) { - // We want to wait for a command if the animator is not rendering any animated effects. This allows the task to sleep when the LEDs are static. - Some(UNDERGLOW_COMMAND_CHANNEL.receive().await) - } else { - match select(ticker.next(), UNDERGLOW_COMMAND_CHANNEL.receive()).await { - Either::First(()) => { - while let Some(event) = subscriber.try_next_message_pure() { - if animator.config.enabled && animator.config.effect.is_reactive() { - animator.register_event(event); - } - } - - None - } - Either::Second(command) => Some(command), - } - }; - - // Process the command if one was received, otherwise continue to render - if let Some(command) = command { - animator.process_command(command).await; - - // Process commands until there are no more to process - while let Ok(command) = UNDERGLOW_COMMAND_CHANNEL.try_receive() { - animator.process_command(command).await; - } - - // Update the config state, after updating the animator's own copy, and check if it was enabled/disabled - let toggled = UNDERGLOW_CONFIG_STATE - .update(|config| { - let toggled = config.enabled != animator.config.enabled; - **config = animator.config; - toggled - }) - .await; - - if toggled { - match animator.config.enabled { - true => animator.turn_on().await, - false => animator.turn_off().await, - } - } - - // Send commands to be consumed by the split peripherals - #[cfg(feature = "split-central")] - { - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::ResetTime, - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::SetEffect(animator.config.effect), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::SetHue(animator.config.hue), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::SetSaturation(animator.config.sat), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::SetValue(animator.config.val), - )) - .await; - crate::split::central::MESSAGE_TO_PERIPHERALS - .send(crate::split::MessageToPeripheral::Underglow( - UnderglowCommand::SetSpeed(animator.config.speed), - )) - .await; - } - - // Ignore any unprocessed matrix events - while subscriber.try_next_message_pure().is_some() {} - - // Reset the ticker so that it doesn't try to catch up on "missed" ticks. - ticker.reset(); - } - - animator.tick().await; - } -} - -#[cfg(feature = "storage")] -pub mod storage { - use core::any::TypeId; - - use defmt::{info, warn, Debug2Format}; - use embassy_futures::select; - use embassy_futures::select::Either; - use embassy_sync::signal::Signal; - use embassy_time::Duration; - use embassy_time::Timer; - - use crate::hw::mcu::RawMutex; - use crate::storage::{FlashStorage, StorageDevice}; - - use super::UnderglowConfig; - use super::UNDERGLOW_CONFIG_STATE; - - pub(super) static UNDERGLOW_CONFIG_STATE_LISTENER: Signal = Signal::new(); - - pub(super) static UNDERGLOW_SAVE_SIGNAL: Signal = Signal::new(); - - #[rumcake_macros::task] - pub async fn underglow_storage_task( - _k: K, - database: &crate::storage::StorageService<'static, F>, - ) where - [(); F::ERASE_SIZE]:, - { - { - // Check stored underglow config metadata (type id) to see if it has changed - let metadata: [u8; core::mem::size_of::()] = - unsafe { core::mem::transmute(TypeId::of::()) }; - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::UnderglowConfig, - &metadata, - ) - .await; - - // Get underglow config from storage - if let Ok(config) = database - .read( - K::get_storage_buffer(), - crate::storage::StorageKey::UnderglowConfig, - ) - .await - { - info!( - "[UNDERGLOW] Obtained underglow config from storage: {}", - Debug2Format(&config) - ); - // Quietly update the config state so that we don't save the config to storage again - UNDERGLOW_CONFIG_STATE.quiet_set(config).await; - } else { - warn!("[UNDERGLOW] Could not get underglow config from storage, using default config.",); - } - } - - let save = || async { - let _ = database - .write( - K::get_storage_buffer(), - crate::storage::StorageKey::UnderglowConfig, - UNDERGLOW_CONFIG_STATE.get().await, - ) - .await; - }; - - // Save the underglow config if it hasn't been changed in 5 seconds, or if a save was signalled - loop { - match select::select( - UNDERGLOW_SAVE_SIGNAL.wait(), - UNDERGLOW_CONFIG_STATE_LISTENER.wait(), - ) - .await - { - Either::First(_) => { - save().await; - } - Either::Second(_) => { - match select::select( - select::select( - Timer::after(Duration::from_secs(5)), - UNDERGLOW_SAVE_SIGNAL.wait(), - ), - UNDERGLOW_CONFIG_STATE_LISTENER.wait(), - ) - .await - { - Either::First(_) => { - save().await; - } - Either::Second(_) => { - // Re-signal, so that we skip the `wait()` call at the beginning of this loop - UNDERGLOW_CONFIG_STATE_LISTENER.signal(()); - } - } - } - }; - } - } -} diff --git a/rumcake/src/usb.rs b/rumcake/src/usb.rs index ab29f97..70843c5 100644 --- a/rumcake/src/usb.rs +++ b/rumcake/src/usb.rs @@ -2,6 +2,8 @@ //! //! To use USB host communication, keyboards must implement [`USBKeyboard`]. +use core::marker::PhantomData; + use defmt::{error, info, Debug2Format}; use embassy_futures::select::{self, select}; use embassy_sync::signal::Signal; @@ -20,18 +22,16 @@ use usbd_human_interface_device::device::keyboard::{ NKROBootKeyboardReport, NKRO_BOOT_KEYBOARD_REPORT_DESCRIPTOR, }; -use crate::hw::mcu::RawMutex; -use crate::hw::{HIDOutput, CURRENT_OUTPUT_STATE}; -use crate::keyboard::{ - Keyboard, KeyboardLayout, CONSUMER_REPORT_HID_SEND_CHANNEL, KEYBOARD_REPORT_HID_SEND_CHANNEL, -}; +use crate::hw::platform::RawMutex; +use crate::hw::{HIDDevice, HIDOutput, CURRENT_OUTPUT_STATE}; +use crate::keyboard::Keyboard; use crate::{State, StaticArray}; pub(crate) static USB_RUNNING_STATE: State = State::new(false, &[&crate::hw::USB_RUNNING_STATE_LISTENER]); /// A trait that keyboards must implement to communicate with host devices over USB. -pub trait USBKeyboard: Keyboard + KeyboardLayout { +pub trait USBKeyboard: Keyboard + HIDDevice { /// Vendor ID for the keyboard. const USB_VID: u16; @@ -129,17 +129,20 @@ macro_rules! usb_task_inner { pub(crate) static KB_CURRENT_OUTPUT_STATE_LISTENER: Signal = Signal::new(); #[rumcake_macros::task] -pub async fn usb_hid_kb_write_task( +pub async fn usb_hid_kb_write_task( + _k: K, mut hid: HidWriter< 'static, impl Driver<'static>, { <::ByteArray as StaticArray>::LEN }, >, ) { + let channel = K::get_via_hid_send_channel(); + usb_task_inner!( hid, KB_CURRENT_OUTPUT_STATE_LISTENER, - KEYBOARD_REPORT_HID_SEND_CHANNEL, + channel, "[USB] Writing NKRO HID keyboard report to USB: {:?}", "[USB] Couldn't write HID keyboard report: {:?}" ) @@ -148,37 +151,52 @@ pub async fn usb_hid_kb_write_task( pub(crate) static CONSUMER_CURRENT_OUTPUT_STATE_LISTENER: Signal = Signal::new(); #[rumcake_macros::task] -pub async fn usb_hid_consumer_write_task( +pub async fn usb_hid_consumer_write_task( + _k: K, mut hid: HidWriter< 'static, impl Driver<'static>, { <::ByteArray as StaticArray>::LEN }, >, ) { + let channel = K::get_via_hid_send_channel(); + usb_task_inner!( hid, CONSUMER_CURRENT_OUTPUT_STATE_LISTENER, - CONSUMER_REPORT_HID_SEND_CHANNEL, + channel, "[USB] Writing consumer HID report to USB: {:?}", "[USB] Couldn't write consumer HID report: {:?}" ); } #[cfg(feature = "via")] -struct ViaCommandHandler; +pub struct ViaCommandHandler { + _phantom: PhantomData, +} + +#[cfg(feature = "via")] +impl ViaCommandHandler { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} #[cfg(feature = "via")] /// Configure the HID report reader and writer for Via/Vial packets. /// /// The reader should be passed to [`usb_hid_via_read_task`], and the writer should be passed to /// [`usb_hid_via_write_task`]. -pub fn setup_usb_via_hid_reader_writer( +pub fn setup_usb_via_hid_reader_writer( + command_handler: &'static ViaCommandHandler, builder: &mut Builder<'static, impl Driver<'static>>, ) -> HidReaderWriter<'static, impl Driver<'static>, 32, 32> { static VIA_STATE: StaticCell = StaticCell::new(); let via_state = VIA_STATE.init(UsbState::new()); let via_hid_config = Config { - request_handler: Some(&VIA_COMMAND_HANDLER), + request_handler: Some(command_handler), report_descriptor: crate::via::VIA_REPORT_DESCRIPTOR, poll_ms: 1, max_packet_size: 32, @@ -187,10 +205,7 @@ pub fn setup_usb_via_hid_reader_writer( } #[cfg(feature = "via")] -static VIA_COMMAND_HANDLER: ViaCommandHandler = ViaCommandHandler; - -#[cfg(feature = "via")] -impl RequestHandler for ViaCommandHandler { +impl RequestHandler for ViaCommandHandler { fn get_report(&self, _id: ReportId, _buf: &mut [u8]) -> Option { None } @@ -199,7 +214,9 @@ impl RequestHandler for ViaCommandHandler { let mut data: [u8; 32] = [0; 32]; data.copy_from_slice(buf); - if let Err(err) = crate::via::VIA_REPORT_HID_RECEIVE_CHANNEL.try_send(data) { + let channel = T::get_via_hid_receive_channel(); + + if let Err(err) = channel.try_send(data) { error!( "[VIA] Could not queue the Via command to be processed: {:?}", err @@ -218,8 +235,11 @@ impl RequestHandler for ViaCommandHandler { #[cfg(feature = "via")] #[rumcake_macros::task] -pub async fn usb_hid_via_read_task(hid: HidReader<'static, impl Driver<'static>, 32>) { - hid.run(false, &VIA_COMMAND_HANDLER).await; +pub async fn usb_hid_via_read_task( + command_handler: &ViaCommandHandler, + hid: HidReader<'static, impl Driver<'static>, 32>, +) { + hid.run(false, command_handler).await; } #[cfg(feature = "via")] @@ -227,11 +247,16 @@ pub(crate) static VIA_CURRENT_OUTPUT_STATE_LISTENER: Signal = Sign #[cfg(feature = "via")] #[rumcake_macros::task] -pub async fn usb_hid_via_write_task(mut hid: HidWriter<'static, impl Driver<'static>, 32>) { +pub async fn usb_hid_via_write_task( + _k: K, + mut hid: HidWriter<'static, impl Driver<'static>, 32>, +) { + let channel = K::get_via_hid_send_channel(); + usb_task_inner!( hid, VIA_CURRENT_OUTPUT_STATE_LISTENER, - crate::via::VIA_REPORT_HID_SEND_CHANNEL, + channel, "[USB] Writing HID via report: {:?}", "[USB] Couldn't write HID via report: {:?}" ) diff --git a/rumcake/src/via/handlers.rs b/rumcake/src/via/handlers.rs index f5d46b9..12fc9ee 100644 --- a/rumcake/src/via/handlers.rs +++ b/rumcake/src/via/handlers.rs @@ -1,10 +1,10 @@ use defmt::warn; use embassy_sync::signal::Signal; use keyberon::action::Action; -use keyberon::key_code::KeyCode; -use crate::hw::mcu::RawMutex; -use crate::keyboard::Keycode; +use crate::hw::platform::RawMutex; +use crate::keyboard::{KeyboardLayout, Keycode}; +use crate::storage::{FlashStorage, StorageDevice, StorageKey}; use super::ViaKeyboard; @@ -17,8 +17,9 @@ pub fn get_uptime(data: &mut [u8]) { } pub async fn get_switch_matrix_state( - matrix_state: &[u8; (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize - * K::LAYOUT_ROWS], + matrix_state: &[u8; (::LAYOUT_COLS + u8::BITS as usize - 1) + / u8::BITS as usize + * ::LAYOUT_ROWS], data: &mut [u8], ) { // see [`crate::via::protocol::background_task`] to see how `matrix_state` is created. @@ -34,47 +35,71 @@ pub async fn get_layout_options(layout_options: &u32, data: &mut .copy_from_slice(&layout_options.to_be_bytes()[(4 - K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE)..=3]) } -pub async fn set_layout_options(layout_options: &mut u32, data: &[u8]) { +pub async fn set_layout_options(layout_options: &mut u32, data: &[u8]) +where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, +{ let mut bytes = [0; 4]; bytes[(4 - K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE)..] .copy_from_slice(&data[2..(2 + K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE)]); *layout_options = u32::from_be_bytes(bytes); K::handle_set_layout_options(*layout_options); - #[cfg(feature = "storage")] - super::storage::update_data( - super::storage::ViaStorageKeys::LayoutOptions, - 0, - &data[0..K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE], - ) - .await; + if let Some(database) = K::get_storage_service() { + // Update data + // For layout options, we just overwrite all of the old data + if let Err(()) = database + .write_raw( + StorageKey::LayoutOptions, + &data[..K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE], + ) + .await + { + warn!("[VIA] Could not write layout options.") + }; + } } -pub async fn device_indication() { +pub async fn device_indication() { #[cfg(feature = "simple-backlight")] - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight::animations::BacklightCommand::Toggle) - .await; + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + channel + .send(crate::lighting::simple_backlight::SimpleBacklightCommand::Toggle) + .await; + } #[cfg(feature = "simple-backlight-matrix")] - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::Toggle) - .await; + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + channel + .send(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::Toggle) + .await; + } #[cfg(feature = "rgb-backlight-matrix")] - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::Toggle) - .await; + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::Toggle) + .await; + } #[cfg(feature = "underglow")] - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::Toggle) - .await; + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + channel + .send(crate::lighting::underglow::UnderglowCommand::Toggle) + .await; + } } -pub async fn eeprom_reset() { - #[cfg(feature = "storage")] - super::storage::reset_data().await; +pub async fn eeprom_reset() +where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, +{ + if let Some(database) = K::get_storage_service() { + let _ = database.delete(StorageKey::LayoutOptions).await; + let _ = database.delete(StorageKey::DynamicKeymap).await; + let _ = database.delete(StorageKey::DynamicKeymapMacro).await; + let _ = database.delete(StorageKey::DynamicKeymapEncoder).await; + } } pub(super) static BOOTLOADER_JUMP_SIGNAL: Signal = Signal::new(); @@ -125,6 +150,7 @@ pub async fn dynamic_keymap_macro_set_buffer( size: u8, data: &[u8], ) where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { @@ -138,13 +164,34 @@ pub async fn dynamic_keymap_macro_set_buffer( macro_data.update_buffer(offset as usize, &data[..len]); } - #[cfg(feature = "storage")] - super::storage::update_data( - super::storage::ViaStorageKeys::DynamicKeymapMacro, - offset as usize, - &data[..len], - ) - .await; + if let Some(database) = K::get_storage_service() { + let offset = offset as usize; + let mut buf = [0; K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]; + + // Read data + let stored_len = match database.read_raw(StorageKey::DynamicKeymapMacro).await { + Ok((stored_data, stored_len)) => { + buf[..stored_len].copy_from_slice(stored_data); + stored_len + } + Err(()) => { + warn!("[VIA] Could not read dynamic keymap macro buffer."); + 0 // Assume that there is no data yet + } + }; + + // Update data + buf[offset..(offset + len)].copy_from_slice(&data[..len]); + + let new_length = stored_len.max(offset + len); + + if let Err(()) = database + .write_raw(StorageKey::DynamicKeymapMacro, &buf[..new_length]) + .await + { + warn!("[VIA] Could not write dynamic keymap macro buffer.") + }; + } } pub fn dynamic_keymap_get_layer_count(data: &mut [u8]) { @@ -158,17 +205,18 @@ pub async fn dynamic_keymap_get_keycode( data: &mut [u8], convert_action_to_keycode: impl Fn(Action) -> u16, ) where - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); ::LAYERS]:, + [(); ::LAYOUT_ROWS]:, + [(); ::LAYOUT_COLS]:, { let keycodes_bytes = &mut data[0..=1]; if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT - || row as usize >= K::LAYOUT_ROWS - || col as usize >= K::LAYOUT_COLS) + || row as usize >= ::LAYOUT_ROWS + || col as usize >= ::LAYOUT_COLS) { - if let Some(action) = K::get_layout() + if let Some(action) = ::get_layout() + .layout .lock() .await .get_action((row, col), layer as usize) @@ -187,18 +235,23 @@ pub async fn dynamic_keymap_set_keycode( data: &[u8], convert_keycode_to_action: impl Fn(u16) -> Option>, ) where - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); ::LAYERS]:, + [(); ::LAYOUT_ROWS]:, + [(); ::LAYOUT_COLS]:, { let keycode = &data[0..=1]; if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT - || row as usize >= K::LAYOUT_ROWS - || col as usize >= K::LAYOUT_COLS) + || row as usize >= ::LAYOUT_ROWS + || col as usize >= ::LAYOUT_COLS) { { - let mut layout = K::get_layout().lock().await; + let mut layout = ::get_layout() + .layout + .lock() + .await; if let Some(action) = convert_keycode_to_action(u16::from_be_bytes(keycode.try_into().unwrap())) { @@ -208,18 +261,35 @@ pub async fn dynamic_keymap_set_keycode( } } - #[cfg(feature = "storage")] - { - let keycode_offset = ((layer * K::LAYOUT_ROWS as u8 * K::LAYOUT_COLS as u8 * 2) - + (row * K::LAYOUT_COLS as u8 * 2) + if let Some(database) = K::get_storage_service() { + let offset = ((layer + * ::LAYOUT_ROWS as u8 + * K::Layout::LAYOUT_COLS as u8 + * 2) + + (row * ::LAYOUT_COLS as u8 * 2) + (col * 2)) as usize; - super::storage::update_data( - super::storage::ViaStorageKeys::DynamicKeymap, - keycode_offset, - keycode, - ) - .await; + let mut buf = [0; K::DYNAMIC_KEYMAP_LAYER_COUNT + * K::Layout::LAYOUT_COLS + * K::Layout::LAYOUT_ROWS + * 2]; + + // Read data + match database.read_raw(StorageKey::DynamicKeymap).await { + Ok((stored_data, stored_len)) => { + buf[..stored_len].copy_from_slice(stored_data); + } + Err(()) => { + warn!("[VIA] Could not read dynamic keymap buffer."); + } + }; + + // Update data + buf[offset..(offset + 2)].copy_from_slice(keycode); + + if let Err(()) = database.write_raw(StorageKey::DynamicKeymap, &buf).await { + warn!("[VIA] Could not write dynamic keymap buffer.",) + }; } } else { warn!("[VIA] Requested a dynamic keymap keycode that is out of bounds.") @@ -234,11 +304,12 @@ pub async fn dynamic_keymap_get_encoder( ) { let keycode = &mut data[0..=1]; - let keycode_offset = ((layer * K::NUM_ENCODERS as u8 * 2 * 2) + let offset = ((layer * ::NUM_ENCODERS as u8 * 2 * 2) + (encoder_id * 2 * 2) + if clockwise { 0 } else { 2 }) as usize; - if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT || encoder_id as usize >= K::NUM_ENCODERS) + if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT + || encoder_id as usize >= ::NUM_ENCODERS) { //TODO: encoder support } else { @@ -246,28 +317,49 @@ pub async fn dynamic_keymap_get_encoder( } } -pub async fn dynamic_keymap_set_encoder( +pub async fn dynamic_keymap_set_encoder( layer: u8, encoder_id: u8, clockwise: bool, data: &[u8], -) { +) where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]:, +{ let keycode = &data[0..=1]; - let keycode_offset = ((layer * K::NUM_ENCODERS as u8 * 2 * 2) + let offset = ((layer * ::NUM_ENCODERS as u8 * 2 * 2) + (encoder_id * 2 * 2) + if clockwise { 0 } else { 2 }) as usize; - if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT || encoder_id as usize >= K::NUM_ENCODERS) + if !(layer as usize >= K::DYNAMIC_KEYMAP_LAYER_COUNT + || encoder_id as usize >= ::NUM_ENCODERS) { //TODO: encoder support - #[cfg(feature = "storage")] - super::storage::update_data( - super::storage::ViaStorageKeys::DynamicKeymapEncoder, - keycode_offset, - keycode, - ) - .await; + + if let Some(database) = K::get_storage_service() { + let mut buf = [0; K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]; + + // Read data + match database.read_raw(StorageKey::DynamicKeymapEncoder).await { + Ok((stored_data, stored_len)) => { + buf[..stored_len].copy_from_slice(stored_data); + } + Err(()) => { + warn!("[VIA] Could not read dynamic keymap encoder."); + } + }; + + // Update data + buf[offset..(offset + 2)].copy_from_slice(keycode); + + if let Err(()) = database + .write_raw(StorageKey::DynamicKeymapEncoder, &buf) + .await + { + warn!("[VIA] Could not write dynamic keymap encoder.") + }; + } } else { warn!("[VIA] Attempted to set a dynamic keymap encoder out of bounds.") } @@ -279,11 +371,14 @@ pub async fn dynamic_keymap_get_buffer( data: &mut [u8], convert_action_to_keycode: impl Fn(Action) -> u16, ) where - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); ::LAYERS]:, + [(); ::LAYOUT_ROWS]:, + [(); ::LAYOUT_COLS]:, { - let buffer_size = K::DYNAMIC_KEYMAP_LAYER_COUNT * K::LAYOUT_ROWS * K::LAYOUT_COLS * 2; + let buffer_size = K::DYNAMIC_KEYMAP_LAYER_COUNT + * ::LAYOUT_ROWS + * K::Layout::LAYOUT_COLS + * 2; let len = if offset as usize + size as usize > buffer_size { buffer_size.saturating_sub(offset as usize) @@ -291,16 +386,21 @@ pub async fn dynamic_keymap_get_buffer( size as usize }; - let mut layout = K::get_layout().lock().await; + let mut layout = ::get_layout() + .layout + .lock() + .await; // We make the assumption that Via will never request for a buffer that requires us to send // part a 2-byte keycode (so a partial keycode). In other words, we assume that `offset` and // `size` will always be even. // https://github.com/the-via/app/blob/ee4443bbdcad79a9568d43488e5097a9c6d96bbe/src/utils/keyboard-api.ts#L249 for byte in ((offset as usize)..(offset as usize + len)).step_by(2) { - let layer = byte / (K::LAYOUT_ROWS * K::LAYOUT_COLS * 2); - let row = (byte / (K::LAYOUT_COLS * 2)) % K::LAYOUT_ROWS; - let col = (byte / 2) % K::LAYOUT_COLS; + let layer = + byte / (::LAYOUT_ROWS * K::Layout::LAYOUT_COLS * 2); + let row = + (byte / (::LAYOUT_COLS * 2)) % K::Layout::LAYOUT_ROWS; + let col = (byte / 2) % ::LAYOUT_COLS; data[(byte - offset as usize)..(byte - offset as usize + 2)].copy_from_slice( &convert_action_to_keycode(layout.get_action((row as u8, col as u8), layer).unwrap()) @@ -315,11 +415,16 @@ pub async fn dynamic_keymap_set_buffer( data: &[u8], convert_keycode_to_action: impl Fn(u16) -> Option>, ) where - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); ::LAYERS]:, + [(); ::LAYOUT_ROWS]:, + [(); ::LAYOUT_COLS]:, { - let buffer_size = K::DYNAMIC_KEYMAP_LAYER_COUNT * K::LAYOUT_ROWS * K::LAYOUT_COLS * 2; + let buffer_size = K::DYNAMIC_KEYMAP_LAYER_COUNT + * ::LAYOUT_ROWS + * K::Layout::LAYOUT_COLS + * 2; let len = if offset as usize + size as usize > buffer_size { buffer_size.saturating_sub(offset as usize) @@ -328,7 +433,10 @@ pub async fn dynamic_keymap_set_buffer( }; { - let mut layout = K::get_layout().lock().await; + let mut layout = ::get_layout() + .layout + .lock() + .await; // We make the assumption that VIA will never write a buffer that contains part a 2-byte // keycode (so a partial keycode). In other words, we assume that `offset` and `size` will @@ -340,9 +448,11 @@ pub async fn dynamic_keymap_set_buffer( .try_into() .unwrap(), )) { - let layer = byte / (K::LAYOUT_ROWS * K::LAYOUT_COLS * 2); - let row = (byte / (K::LAYOUT_COLS * 2)) % K::LAYOUT_ROWS; - let col = (byte / 2) % K::LAYOUT_COLS; + let layer = byte + / (::LAYOUT_ROWS * K::Layout::LAYOUT_COLS * 2); + let row = (byte / (::LAYOUT_COLS * 2)) + % K::Layout::LAYOUT_ROWS; + let col = (byte / 2) % ::LAYOUT_COLS; layout .change_action((row as u8, col as u8), layer, action) @@ -351,25 +461,43 @@ pub async fn dynamic_keymap_set_buffer( } } - #[cfg(feature = "storage")] - { - super::storage::update_data( - super::storage::ViaStorageKeys::DynamicKeymap, - offset as usize, - &data[..len], - ) - .await; + if let Some(database) = K::get_storage_service() { + let offset = offset as usize; + let mut buf = [0; K::DYNAMIC_KEYMAP_LAYER_COUNT + * K::Layout::LAYOUT_COLS + * K::Layout::LAYOUT_ROWS + * 2]; + + // Read data + match database.read_raw(StorageKey::DynamicKeymap).await { + Ok((stored_data, stored_len)) => { + buf[..stored_len].copy_from_slice(stored_data); + } + Err(()) => { + warn!("[VIA] Could not read dynamic keymap buffer."); + } + }; + + // Update data + buf[offset..(offset + 2)].copy_from_slice(&data[..len]); + + if let Err(()) = database.write_raw(StorageKey::DynamicKeymap, &buf).await { + warn!("[VIA] Could not write dynamic keymap buffer.",) + }; } } pub async fn dynamic_keymap_reset() where - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); ::LAYERS]:, + [(); ::LAYOUT_ROWS]:, + [(); ::LAYOUT_COLS]:, { - let mut layout = K::get_layout().lock().await; - let original = K::get_original_layout(); + let mut layout = ::get_layout() + .layout + .lock() + .await; + let original = ::get_original_layout(); for (layer_idx, layer) in original.iter().enumerate() { for (row_idx, row) in layer.iter().enumerate() { @@ -382,448 +510,486 @@ where } } +#[cfg(feature = "underglow")] +pub async fn underglow_get_enabled(data: &mut [u8]) { + if let Some(state) = + <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() + { + data[0] = state.get().await.enabled as u8 + } +} + #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_get_enabled(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight::BACKLIGHT_CONFIG_STATE - .get() - .await - .enabled as u8 +pub async fn simple_backlight_get_enabled(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_state() { + data[0] = state.get().await.enabled as u8 + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_get_enabled(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .enabled as u8 +pub async fn simple_backlight_matrix_get_enabled(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.enabled as u8 + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_get_enabled(data: &mut [u8]) { - data[0] = crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .enabled as u8 +pub async fn rgb_backlight_matrix_get_enabled(data: &mut [u8]) { + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.enabled as u8 + } +} + +#[cfg(feature = "underglow")] +pub async fn underglow_set_enabled(data: &[u8]) { + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + let command = if data[0] == 1 { + crate::lighting::underglow::UnderglowCommand::TurnOn + } else { + crate::lighting::underglow::UnderglowCommand::TurnOff + }; + + channel.send(command).await; + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_set_enabled(data: &[u8]) { - let command = if data[0] == 1 { - crate::backlight::simple_backlight::animations::BacklightCommand::TurnOn - } else { - crate::backlight::simple_backlight::animations::BacklightCommand::TurnOff - }; +pub async fn simple_backlight_set_enabled(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + let command = if data[0] == 1 { + crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOn + } else { + crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOff + }; - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; + channel.send(command).await; + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_set_enabled(data: &[u8]) { - let command = if data[0] == 1 { - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOn - } else { - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOff - }; +pub async fn simple_backlight_matrix_set_enabled(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + let command = if data[0] == 1 { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOn + } else { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOff + }; - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; + channel.send(command).await; + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_set_enabled(data: &[u8]) { - let command = if data[0] == 1 { - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOn - } else { - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOff - }; +pub async fn rgb_backlight_matrix_set_enabled(data: &[u8]) { + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + let command = if data[0] == 1 { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOn + } else { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOff + }; - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(command) - .await; + channel.send(command).await; + } +} + +#[cfg(feature = "underglow")] +pub async fn underglow_get_brightness(data: &mut [u8]) { + if let Some(state) = + <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() + { + data[0] = state.get().await.val; + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_get_brightness(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight::BACKLIGHT_CONFIG_STATE - .get() - .await - .val +pub async fn simple_backlight_get_brightness(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_state() { + data[0] = state.get().await.val + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_get_brightness(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .val +pub async fn simple_backlight_matrix_get_brightness(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.val + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_get_brightness(data: &mut [u8]) { - data[0] = crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .val +pub async fn rgb_backlight_matrix_get_brightness(data: &mut [u8]) { + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.val + } +} + +#[cfg(feature = "underglow")] +pub async fn underglow_set_brightness(data: &[u8]) { + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + channel + .send(crate::lighting::underglow::UnderglowCommand::SetValue( + data[0], + )) + .await; + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_set_brightness(data: &[u8]) { - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight::animations::BacklightCommand::SetValue(data[0])) - .await; +pub async fn simple_backlight_set_brightness(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + channel + .send(crate::lighting::simple_backlight::SimpleBacklightCommand::SetValue(data[0])) + .await; + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_set_brightness(data: &[u8]) { - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::SetValue( - data[0], - ), - ) - .await; +pub async fn simple_backlight_matrix_set_brightness(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + channel + .send( + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::SetValue( + data[0], + ), + ) + .await; + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_set_brightness(data: &[u8]) { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetValue(data[0]), - ) - .await; +pub async fn rgb_backlight_matrix_set_brightness(data: &[u8]) { + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetValue(data[0]), + ) + .await; + } +} + +#[cfg(feature = "underglow")] +pub async fn underglow_get_effect( + data: &mut [u8], + convert_effect_to_qmk_id: impl Fn(crate::lighting::underglow::UnderglowConfig) -> u8, +) { + if let Some(state) = + <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() + { + data[0] = convert_effect_to_qmk_id(state.get().await); + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_get_effect( +pub async fn simple_backlight_get_effect( data: &mut [u8], - convert_effect_to_qmk_id: impl Fn( - crate::backlight::simple_backlight::animations::BacklightEffect, - ) -> u8, + convert_effect_to_qmk_id: impl Fn(crate::lighting::simple_backlight::SimpleBacklightEffect) -> u8, ) { - data[0] = convert_effect_to_qmk_id( - crate::backlight::simple_backlight::BACKLIGHT_CONFIG_STATE - .get() - .await - .effect, - ) + if let Some(state) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_state() { + data[0] = convert_effect_to_qmk_id(state.get().await.effect) + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_get_effect( +pub async fn simple_backlight_matrix_get_effect( data: &mut [u8], convert_effect_to_qmk_id: impl Fn( - crate::backlight::simple_backlight_matrix::animations::BacklightEffect, + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixEffect, ) -> u8, ) { - data[0] = convert_effect_to_qmk_id( - crate::backlight::simple_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .effect, - ) + if let Some(state) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_state() { + data[0] = convert_effect_to_qmk_id(state.get().await.effect) + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_get_effect( +pub async fn rgb_backlight_matrix_get_effect( data: &mut [u8], convert_effect_to_qmk_id: impl Fn( - crate::backlight::rgb_backlight_matrix::animations::BacklightEffect, + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixEffect, ) -> u8, ) { - data[0] = convert_effect_to_qmk_id( - crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .effect, - ) + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + data[0] = convert_effect_to_qmk_id(state.get().await.effect) + } +} + +#[cfg(feature = "underglow")] +/// `convert_qmk_id_to_effect` must return Option<(UnderglowEffect, Option)> because some QMK +/// underglow effect IDs represent the same overall effect, but differ only by speed or direction. +/// e.g. RainbowSwirl to RainbowSwirl6. Option is used to set the speed for the effect to +/// handle these cases. This only really applies to Vial, since it uses an older protocol. In the +/// new Via protocol, we never set the speed, and instead use a custom UI to control speed. +pub async fn underglow_set_effect( + data: &[u8], + convert_qmk_id_to_effect: impl Fn( + u8, + ) -> Option<( + crate::lighting::underglow::UnderglowEffect, + Option, + )>, +) { + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + if let Some((effect, speed)) = convert_qmk_id_to_effect(data[0]) { + channel + .send(crate::lighting::underglow::UnderglowCommand::SetEffect( + effect, + )) + .await; + + if let Some(speed) = speed { + channel + .send(crate::lighting::underglow::UnderglowCommand::SetSpeed( + speed, + )) + .await; + } + } else { + warn!( + "[VIA] Tried to set an unknown underglow effect: {:?}", + data[0] + ) + } + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_set_effect( +pub async fn simple_backlight_set_effect( data: &[u8], convert_qmk_id_to_effect: impl Fn( u8, ) -> Option< - crate::backlight::simple_backlight::animations::BacklightEffect, + crate::lighting::simple_backlight::SimpleBacklightEffect, >, ) { - if let Some(effect) = convert_qmk_id_to_effect(data[0]) { - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::simple_backlight::animations::BacklightCommand::SetEffect(effect), + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + if let Some(effect) = convert_qmk_id_to_effect(data[0]) { + channel + .send(crate::lighting::simple_backlight::SimpleBacklightCommand::SetEffect(effect)) + .await; + } else { + warn!( + "[VIA] Tried to set an unknown backlight effect: {:?}", + data[0] ) - .await; - } else { - warn!( - "[VIA] Tried to set an unknown backlight effect: {:?}", - data[0] - ) + } } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_set_effect( +pub async fn simple_backlight_matrix_set_effect( data: &[u8], convert_qmk_id_to_effect: impl Fn( u8, ) -> Option< - crate::backlight::simple_backlight_matrix::animations::BacklightEffect, + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixEffect, >, ) { - if let Some(effect) = convert_qmk_id_to_effect(data[0]) { - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + if let Some(effect) = convert_qmk_id_to_effect(data[0]) { + channel .send( - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::SetEffect( + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::SetEffect( effect, ), ) .await; - } else { - warn!( - "[VIA] Tried to set an unknown backlight effect: {:?}", - data[0] - ) + } else { + warn!( + "[VIA] Tried to set an unknown backlight effect: {:?}", + data[0] + ) + } } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_set_effect( +pub async fn rgb_backlight_matrix_set_effect( data: &[u8], convert_qmk_id_to_effect: impl Fn( u8, ) -> Option< - crate::backlight::rgb_backlight_matrix::animations::BacklightEffect, + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixEffect, >, ) { - if let Some(effect) = convert_qmk_id_to_effect(data[0]) { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetEffect( - effect, - ), + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + if let Some(effect) = convert_qmk_id_to_effect(data[0]) { + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetEffect( + effect, + ), + ) + .await; + } else { + warn!( + "[VIA] Tried to set an unknown backlight effect: {:?}", + data[0] ) - .await; - } else { - warn!( - "[VIA] Tried to set an unknown backlight effect: {:?}", - data[0] - ) + } } } -#[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_get_speed(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight::BACKLIGHT_CONFIG_STATE - .get() - .await - .speed -} - -#[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_get_speed(data: &mut [u8]) { - data[0] = crate::backlight::simple_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .speed -} - -#[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_get_speed(data: &mut [u8]) { - data[0] = crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await - .speed +#[cfg(feature = "underglow")] +pub async fn underglow_get_speed(data: &mut [u8]) { + if let Some(state) = + <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() + { + data[0] = state.get().await.speed; + } } #[cfg(feature = "simple-backlight")] -pub async fn simple_backlight_set_speed(data: &[u8]) { - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight::animations::BacklightCommand::SetSpeed(data[0])) - .await; +pub async fn simple_backlight_get_speed(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_state() { + data[0] = state.get().await.speed + } } #[cfg(feature = "simple-backlight-matrix")] -pub async fn simple_backlight_matrix_set_speed(data: &[u8]) { - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::SetSpeed( - data[0], - ), - ) - .await; -} - -#[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_set_speed(data: &[u8]) { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetSpeed(data[0]), - ) - .await; -} - -#[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_get_color(data: &mut [u8]) { - // Color only available on RGB matrices - let config = crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await; - data[0] = config.hue; - data[1] = config.sat; +pub async fn simple_backlight_matrix_get_speed(data: &mut [u8]) { + if let Some(state) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.speed + } } #[cfg(feature = "rgb-backlight-matrix")] -pub async fn rgb_backlight_matrix_set_color(data: &[u8]) { - // Color only available on RGB matrices - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetHue(data[0])) - .await; - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetSaturation( - data[1], - ), - ) - .await; -} - -pub async fn simple_backlight_save() { - #[cfg(all(feature = "storage", feature = "simple-backlight"))] - crate::backlight::simple_backlight::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight::animations::BacklightCommand::SaveConfig) - .await; -} - -pub async fn simple_backlight_matrix_save() { - #[cfg(all(feature = "storage", feature = "simple-backlight-matrix"))] - crate::backlight::simple_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::SaveConfig) - .await; -} - -pub async fn rgb_backlight_matrix_save() { - #[cfg(all(feature = "storage", feature = "rgb-backlight-matrix"))] - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SaveConfig) - .await; +pub async fn rgb_backlight_matrix_get_speed(data: &mut [u8]) { + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + data[0] = state.get().await.speed + } } #[cfg(feature = "underglow")] -pub async fn underglow_get_enabled(data: &mut [u8]) { - data[0] = crate::underglow::UNDERGLOW_CONFIG_STATE.get().await.enabled as u8 +pub async fn underglow_set_speed(data: &[u8]) { + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + channel + .send(crate::lighting::underglow::UnderglowCommand::SetSpeed( + data[0], + )) + .await; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_set_enabled(data: &[u8]) { - let command = if data[0] == 1 { - crate::underglow::animations::UnderglowCommand::TurnOn - } else { - crate::underglow::animations::UnderglowCommand::TurnOff - }; +#[cfg(feature = "simple-backlight")] +pub async fn simple_backlight_set_speed(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + channel + .send(crate::lighting::simple_backlight::SimpleBacklightCommand::SetSpeed(data[0])) + .await; + } +} - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(command) - .await; +#[cfg(feature = "simple-backlight-matrix")] +pub async fn simple_backlight_matrix_set_speed(data: &[u8]) { + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + channel + .send( + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::SetSpeed( + data[0], + ), + ) + .await; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_get_brightness(data: &mut [u8]) { - data[0] = crate::underglow::UNDERGLOW_CONFIG_STATE.get().await.val; +#[cfg(feature = "rgb-backlight-matrix")] +pub async fn rgb_backlight_matrix_set_speed(data: &[u8]) { + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetSpeed(data[0]), + ) + .await; + } } #[cfg(feature = "underglow")] -pub async fn underglow_set_brightness(data: &[u8]) { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetValue( - data[0], - )) - .await; +pub async fn underglow_get_color(data: &mut [u8]) { + if let Some(state) = + <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() + { + let config = state.get().await; + data[0] = config.hue; + data[1] = config.sat; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_get_effect( - data: &mut [u8], - convert_effect_to_qmk_id: impl Fn(crate::underglow::animations::UnderglowConfig) -> u8, -) { - data[0] = convert_effect_to_qmk_id(crate::underglow::UNDERGLOW_CONFIG_STATE.get().await); +#[cfg(feature = "rgb-backlight-matrix")] +pub async fn rgb_backlight_matrix_get_color(data: &mut [u8]) { + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + // Color only available on RGB matrices + let config = state.get().await; + data[0] = config.hue; + data[1] = config.sat; + } } #[cfg(feature = "underglow")] -/// `convert_qmk_id_to_effect` must return Option<(UnderglowEffect, Option)> because some QMK -/// underglow effect IDs represent the same overall effect, but differ only by speed or direction. -/// e.g. RainbowSwirl to RainbowSwirl6. Option is used to set the speed for the effect to -/// handle these cases. This only really applies to Vial, since it uses an older protocol. In the -/// new Via protocol, we never set the speed, and instead use a custom UI to control speed. -pub async fn underglow_set_effect( - data: &[u8], - convert_qmk_id_to_effect: impl Fn( - u8, - ) -> Option<( - crate::underglow::animations::UnderglowEffect, - Option, - )>, -) { - if let Some((effect, speed)) = convert_qmk_id_to_effect(data[0]) { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetEffect( - effect, +pub async fn underglow_set_color(data: &[u8]) { + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + channel + .send(crate::lighting::underglow::UnderglowCommand::SetHue( + data[0], + )) + .await; + channel + .send(crate::lighting::underglow::UnderglowCommand::SetSaturation( + data[1], )) .await; - - if let Some(speed) = speed { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetSpeed( - speed, - )) - .await; - } - } else { - warn!( - "[VIA] Tried to set an unknown underglow effect: {:?}", - data[0] - ) } } -#[cfg(feature = "underglow")] -pub async fn underglow_get_speed(data: &mut [u8]) { - data[0] = crate::underglow::UNDERGLOW_CONFIG_STATE.get().await.speed; +#[cfg(feature = "rgb-backlight-matrix")] +pub async fn rgb_backlight_matrix_set_color(data: &[u8]) { + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + // Color only available on RGB matrices + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetHue(data[0])) + .await; + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetSaturation( + data[1], + ), + ) + .await; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_set_speed(data: &[u8]) { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetSpeed( - data[0], - )) - .await; +pub async fn underglow_save() { + #[cfg(feature = "underglow")] + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + channel + .send(crate::lighting::underglow::UnderglowCommand::SaveConfig) + .await; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_get_color(data: &mut [u8]) { - let config = crate::underglow::UNDERGLOW_CONFIG_STATE.get().await; - data[0] = config.hue; - data[1] = config.sat; +pub async fn simple_backlight_save() { + #[cfg(feature = "simple-backlight")] + if let Some(channel) = <::SimpleBacklightDeviceType as crate::lighting::simple_backlight::private::MaybeSimpleBacklightDevice>::get_command_channel() { + channel + .send(crate::lighting::simple_backlight::SimpleBacklightCommand::SaveConfig) + .await; + } } -#[cfg(feature = "underglow")] -pub async fn underglow_set_color(data: &[u8]) { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetHue( - data[0], - )) - .await; - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SetSaturation(data[1])) - .await; +pub async fn simple_backlight_matrix_save() { + #[cfg(feature = "simple-backlight-matrix")] + if let Some(channel) = <::SimpleBacklightMatrixDeviceType as crate::lighting::simple_backlight_matrix::private::MaybeSimpleBacklightMatrixDevice>::get_command_channel() { + channel + .send( + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::SaveConfig, + ) + .await; + } } -pub async fn underglow_save() { - #[cfg(all(feature = "storage", feature = "underglow"))] - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::SaveConfig) - .await; +pub async fn rgb_backlight_matrix_save() { + #[cfg(feature = "rgb-backlight-matrix")] + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SaveConfig) + .await; + } } diff --git a/rumcake/src/via/mod.rs b/rumcake/src/via/mod.rs index de46e4b..fd484da 100644 --- a/rumcake/src/via/mod.rs +++ b/rumcake/src/via/mod.rs @@ -4,20 +4,23 @@ //! changes, you will also need to enable the `storage` feature flag, and setup the appropriate //! storage buffers using [`crate::setup_via_storage_buffers`]. -use crate::keyboard::{Keyboard, KeyboardLayout}; use defmt::assert; use embassy_futures::join; -use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; +use embassy_sync::signal::Signal; -use crate::hw::mcu::RawMutex; +use crate::hw::platform::RawMutex; +use crate::hw::HIDDevice; +use crate::keyboard::KeyboardLayout; +use crate::storage::private::EmptyStorageDevice; +use crate::storage::{FlashStorage, StorageDevice, StorageKey, StorageService}; pub(crate) mod handlers; pub(crate) mod protocol_12; pub(crate) use protocol_12 as protocol; -pub use rumcake_macros::setup_macro_buffer; +pub use rumcake_macros::{connect_storage_service, setup_macro_buffer}; /// Data structure that contains data for macros created by Via. Requires the size of the buffer, /// and the number of sequences that can be created to be specified. @@ -57,7 +60,25 @@ pub enum BacklightType { } /// A trait that keyboards must implement to use the Via protocol. -pub trait ViaKeyboard: Keyboard + KeyboardLayout { +pub trait ViaKeyboard { + /// The layout that this Via instance will control. + type Layout: KeyboardLayout; + + /// The storage device used to store Via data. + type StorageType: StorageDevice = EmptyStorageDevice; + fn get_storage_service() -> Option< + &'static StorageService< + 'static, + ::FlashStorageType, + Self::StorageType, + >, + > + where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + { + None + } + const VIA_ENABLED: bool = true; /// Version of your firmware. @@ -72,7 +93,7 @@ pub trait ViaKeyboard: Keyboard + KeyboardLayout { /// The number of layers that you can modify in the Via app. This number must be equal to or /// less than the number of layers in your keyberon layout, [`KeyboardLayout::LAYERS`]. - const DYNAMIC_KEYMAP_LAYER_COUNT: usize = Self::LAYERS; + const DYNAMIC_KEYMAP_LAYER_COUNT: usize = Self::Layout::LAYERS; // const DYNAMIC_KEYMAP_LAYER_COUNT: usize = 4; // This is the default if this variable isn't defined in QMK /// The number of macros that your keyboard can store. You should use [`setup_macro_buffer`] to @@ -150,25 +171,21 @@ pub(crate) const VIA_REPORT_DESCRIPTOR: &[u8] = &[ 0xC0, // End Collection ]; -/// Channel used to receive reports from the Via app to be processed. Reports that are sent to this -/// channel will be processed by the Via task, and call the appropriate command handler, depending -/// on the report contents. -pub static VIA_REPORT_HID_RECEIVE_CHANNEL: Channel = Channel::new(); - -/// Channel used to send Via reports back to the Via host. -pub static VIA_REPORT_HID_SEND_CHANNEL: Channel = Channel::new(); - #[rumcake_macros::task] -pub async fn via_process_task(_k: K) +pub async fn via_process_task(_k: K) where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYERS]:, + [(); K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYOUT_COLS]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { - assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= K::LAYERS); + assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= K::Layout::LAYERS); assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= 16); if K::get_macro_buffer().is_some() { assert!( @@ -187,10 +204,12 @@ where } let via_state: Mutex> = Mutex::new(Default::default()); + let receive_channel = K::get_via_hid_receive_channel(); + let send_channel = K::get_via_hid_send_channel(); let report_fut = async { loop { - let mut report = VIA_REPORT_HID_RECEIVE_CHANNEL.receive().await; + let mut report = receive_channel.receive().await; if K::VIA_ENABLED { { @@ -198,7 +217,7 @@ where protocol::process_via_command::(&mut report, &mut via_state).await; } - VIA_REPORT_HID_SEND_CHANNEL.send(report).await; + send_channel.send(report).await; } } }; @@ -206,299 +225,111 @@ where join::join(report_fut, protocol::background_task::(&via_state)).await; } -#[cfg(feature = "storage")] -pub mod storage { - use defmt::warn; - use embassy_sync::channel::Channel; - use embassy_sync::signal::Signal; - - use crate::hw::mcu::RawMutex; - use crate::storage::{FlashStorage, StorageDevice, StorageKey}; - - use super::ViaKeyboard; +static VIA_LAYOUT_OPTIONS: Signal = Signal::new(); - pub(super) enum ViaStorageKeys { - LayoutOptions, - DynamicKeymap, - DynamicKeymapMacro, - DynamicKeymapEncoder, - } - - impl From for StorageKey { - fn from(value: ViaStorageKeys) -> Self { - match value { - ViaStorageKeys::LayoutOptions => StorageKey::LayoutOptions, - ViaStorageKeys::DynamicKeymap => StorageKey::DynamicKeymap, - ViaStorageKeys::DynamicKeymapMacro => StorageKey::DynamicKeymapMacro, - ViaStorageKeys::DynamicKeymapEncoder => StorageKey::DynamicKeymapEncoder, - } - } - } - - #[repr(u8)] - enum Operation { - Write([u8; 32], ViaStorageKeys, usize, usize), - Delete, - } - - /// A function that dispatches a flash operation to the Via storage task. This will obtain a - /// lock, and hold onto it until the storage task signals a completion. `offset` corresponds to - /// the first byte of the stored data for the given `key` that we want to update. For example, - /// if [0x23, 0x65, 0xEB] is stored in flash for the key `LayoutOptions`, and we want to update - /// the last 2 bytes, we would pass in an offset of 1, and a `data` slice with a length of 2. - pub(super) async fn update_data(key: ViaStorageKeys, offset: usize, data: &[u8]) { - // TODO: this function will wait eternally if via_storage_task is not there - // Buffer size of 32 is based off of the VIA packet size. This can actually be less, because - // some bytes are used for the command IDs, but 32 should be fine. - let mut buf = [0; 32]; - let len = data.len(); - buf[..len].copy_from_slice(data); - OPERATION_CHANNEL - .send(Operation::Write(buf, key, offset, len)) +pub async fn initialize_via_data(_v: V) +where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); V::VIA_EEPROM_LAYOUT_OPTIONS_SIZE]:, + [(); V::DYNAMIC_KEYMAP_LAYER_COUNT * V::Layout::LAYOUT_COLS * V::Layout::LAYOUT_ROWS * 2]:, + [(); V::DYNAMIC_KEYMAP_LAYER_COUNT * V::Layout::NUM_ENCODERS * 2 * 2]:, + [(); V::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, + [(); V::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, + [(); V::Layout::LAYERS]:, + [(); V::Layout::LAYOUT_ROWS]:, + [(); V::Layout::LAYOUT_COLS]:, +{ + if let Some(database) = V::get_storage_service() { + // Initialize layout options + let options_metadata = [V::VIA_EEPROM_LAYOUT_OPTIONS_SIZE as u8]; + let _ = database + .check_metadata(crate::storage::StorageKey::LayoutOptions, &options_metadata) .await; - OPERATION_COMPLETE.wait().await; - } - - pub(super) async fn reset_data() { - OPERATION_CHANNEL.send(Operation::Delete).await; - OPERATION_COMPLETE.wait().await; - } - - static OPERATION_CHANNEL: Channel = Channel::new(); - static OPERATION_COMPLETE: Signal = Signal::new(); - - pub(super) static VIA_LAYOUT_OPTIONS: Signal = Signal::new(); - - #[rumcake_macros::task] - pub async fn via_storage_task( - _k: K, - database: &crate::storage::StorageService<'_, F>, - ) where - [(); K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE]:, - [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::LAYOUT_COLS * K::LAYOUT_ROWS * 2]:, - [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::NUM_ENCODERS * 2 * 2]:, - [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, - [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, - [(); F::ERASE_SIZE]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, - { - // Initialize VIA data + if let Ok((stored_data, stored_len)) = database + .read_raw(crate::storage::StorageKey::LayoutOptions) + .await { - // Initialize layout options - let options_metadata = [K::VIA_EEPROM_LAYOUT_OPTIONS_SIZE as u8]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::LayoutOptions, - &options_metadata, - ) - .await; - if let Ok((stored_data, stored_len)) = database - .read_raw( - K::get_storage_buffer(), - crate::storage::StorageKey::LayoutOptions, - ) - .await - { - let mut bytes = [0; 4]; - bytes[(4 - stored_len)..].copy_from_slice(&stored_data[..stored_len]); - VIA_LAYOUT_OPTIONS.signal(u32::from_be_bytes(bytes)) - }; - - // Initialize layout - let layout_metadata = [ - K::DYNAMIC_KEYMAP_LAYER_COUNT as u8, - K::LAYOUT_COLS as u8, - K::LAYOUT_ROWS as u8, - ]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::DynamicKeymap, - &layout_metadata, - ) - .await; - if let Ok((stored_data, stored_len)) = database - .read_raw( - K::get_storage_buffer(), - crate::storage::StorageKey::DynamicKeymap, - ) - .await - { - // Load layout from flash - let mut layout = K::get_layout().lock().await; - for byte in (0..stored_len).step_by(2) { - if let Some(action) = super::protocol::keycodes::convert_keycode_to_action::( - u16::from_be_bytes(stored_data[byte..byte + 2].try_into().unwrap()), - ) { - let layer = byte / (K::LAYOUT_ROWS * K::LAYOUT_COLS * 2); - let row = (byte / (K::LAYOUT_COLS * 2)) % K::LAYOUT_ROWS; - let col = (byte / 2) % K::LAYOUT_COLS; - - layout - .change_action((row as u8, col as u8), layer, action) - .unwrap(); - } - } - } else { - // Save default layout to flash - let mut layout = K::get_layout().lock().await; - let mut buf = - [0; K::DYNAMIC_KEYMAP_LAYER_COUNT * K::LAYOUT_COLS * K::LAYOUT_ROWS * 2]; - for byte in (0..buf.len()).step_by(2) { - let layer = byte / (K::LAYOUT_ROWS * K::LAYOUT_COLS * 2); - let row = (byte / (K::LAYOUT_COLS * 2)) % K::LAYOUT_ROWS; - let col = (byte / 2) % K::LAYOUT_COLS; - - buf[(byte)..(byte + 2)].copy_from_slice( - &super::protocol::keycodes::convert_action_to_keycode::( - layout.get_action((row as u8, col as u8), layer).unwrap(), - ) - .to_be_bytes(), - ); - } - let _ = database - .write_raw(K::get_storage_buffer(), StorageKey::DynamicKeymap, &buf) - .await; - }; - - // Initialize encoder layout - let encoder_metadata = [K::DYNAMIC_KEYMAP_LAYER_COUNT as u8, K::NUM_ENCODERS as u8]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::DynamicKeymapEncoder, - &encoder_metadata, - ) - .await; - - // Initialize macros - let _ = database - .check_metadata( - K::get_storage_buffer(), - crate::storage::StorageKey::DynamicKeymapMacro, - &layout_metadata, - ) - .await; - if let Ok((stored_data, stored_len)) = database - .read_raw( - K::get_storage_buffer(), - crate::storage::StorageKey::DynamicKeymapMacro, - ) - .await - { - if let Some(macro_data) = K::get_macro_buffer() { - macro_data.update_buffer(0, &stored_data[..stored_len]) - } - }; - } - - loop { - match OPERATION_CHANNEL.receive().await { - Operation::Write(data, key, offset, len) => { - match key { - ViaStorageKeys::LayoutOptions => { - // Update data - // For layout options, we just overwrite all of the old data - if let Err(()) = database - .write_raw(K::get_storage_buffer(), key.into(), &data[..len]) - .await - { - warn!("[VIA] Could not write layout options.") - }; - } - ViaStorageKeys::DynamicKeymap => { - let key = key.into(); - let mut buf = [0; K::DYNAMIC_KEYMAP_LAYER_COUNT - * K::LAYOUT_COLS - * K::LAYOUT_ROWS - * 2]; - - // Read data - match database.read_raw(K::get_storage_buffer(), key).await { - Ok((stored_data, stored_len)) => { - buf[..stored_len].copy_from_slice(stored_data); - } - Err(()) => { - warn!("[VIA] Could not read dynamic keymap buffer."); - } - }; - - // Update data - buf[offset..(offset + len)].copy_from_slice(&data[..len]); - - if let Err(()) = - database.write_raw(K::get_storage_buffer(), key, &buf).await - { - warn!("[VIA] Could not write dynamic keymap buffer.",) - }; - } - ViaStorageKeys::DynamicKeymapMacro => { - let key = key.into(); - let mut buf = [0; K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]; - - // Read data - let stored_len = - match database.read_raw(K::get_storage_buffer(), key).await { - Ok((stored_data, stored_len)) => { - buf[..stored_len].copy_from_slice(stored_data); - stored_len - } - Err(()) => { - warn!("[VIA] Could not read dynamic keymap macro buffer."); - 0 // Assume that there is no data yet - } - }; - - // Update data - buf[offset..(offset + len)].copy_from_slice(&data[..len]); - - let new_length = stored_len.max(offset + len); - - if let Err(()) = database - .write_raw(K::get_storage_buffer(), key, &buf[..new_length]) - .await - { - warn!("[VIA] Could not write dynamic keymap macro buffer.") - }; - } - ViaStorageKeys::DynamicKeymapEncoder => { - let key = key.into(); - let mut buf = - [0; K::DYNAMIC_KEYMAP_LAYER_COUNT * K::NUM_ENCODERS * 2 * 2]; - - // Read data - match database.read_raw(K::get_storage_buffer(), key).await { - Ok((stored_data, stored_len)) => { - buf[..stored_len].copy_from_slice(stored_data); - } - Err(()) => { - warn!("[VIA] Could not read dynamic keymap encoder."); - } - }; - - // Update data - buf[offset..(offset + len)].copy_from_slice(&data[..len]); - - if let Err(()) = - database.write_raw(K::get_storage_buffer(), key, &buf).await - { - warn!("[VIA] Could not write dynamic keymap encoder.") - }; - } - } - } - Operation::Delete => { - let _ = database.delete(StorageKey::LayoutOptions).await; - let _ = database.delete(StorageKey::DynamicKeymap).await; - let _ = database.delete(StorageKey::DynamicKeymapMacro).await; - let _ = database.delete(StorageKey::DynamicKeymapEncoder).await; + let mut bytes = [0; 4]; + bytes[(4 - stored_len)..].copy_from_slice(&stored_data[..stored_len]); + VIA_LAYOUT_OPTIONS.signal(u32::from_be_bytes(bytes)) + }; + + // Initialize layout + let layout_metadata = [ + V::DYNAMIC_KEYMAP_LAYER_COUNT as u8, + V::Layout::LAYOUT_COLS as u8, + V::Layout::LAYOUT_ROWS as u8, + ]; + let _ = database + .check_metadata(crate::storage::StorageKey::DynamicKeymap, &layout_metadata) + .await; + if let Ok((stored_data, stored_len)) = database + .read_raw(crate::storage::StorageKey::DynamicKeymap) + .await + { + // Load layout from flash + let mut layout = V::Layout::get_layout().layout.lock().await; + for byte in (0..stored_len).step_by(2) { + if let Some(action) = protocol::keycodes::convert_keycode_to_action::( + u16::from_be_bytes(stored_data[byte..byte + 2].try_into().unwrap()), + ) { + let layer = byte / (V::Layout::LAYOUT_ROWS * V::Layout::LAYOUT_COLS * 2); + let row = (byte / (V::Layout::LAYOUT_COLS * 2)) % V::Layout::LAYOUT_ROWS; + let col = (byte / 2) % V::Layout::LAYOUT_COLS; + + layout + .change_action((row as u8, col as u8), layer, action) + .unwrap(); } } + } else { + // Save default layout to flash + let mut layout = V::Layout::get_layout().layout.lock().await; + let mut buf = [0; V::DYNAMIC_KEYMAP_LAYER_COUNT + * V::Layout::LAYOUT_COLS + * V::Layout::LAYOUT_ROWS + * 2]; + for byte in (0..buf.len()).step_by(2) { + let layer = byte / (V::Layout::LAYOUT_ROWS * V::Layout::LAYOUT_COLS * 2); + let row = (byte / (V::Layout::LAYOUT_COLS * 2)) % V::Layout::LAYOUT_ROWS; + let col = (byte / 2) % V::Layout::LAYOUT_COLS; + + buf[(byte)..(byte + 2)].copy_from_slice( + &protocol::keycodes::convert_action_to_keycode::( + layout.get_action((row as u8, col as u8), layer).unwrap(), + ) + .to_be_bytes(), + ); + } + let _ = database.write_raw(StorageKey::DynamicKeymap, &buf).await; + }; + + // Initialize encoder layout + let encoder_metadata = [ + V::DYNAMIC_KEYMAP_LAYER_COUNT as u8, + V::Layout::NUM_ENCODERS as u8, + ]; + let _ = database + .check_metadata( + crate::storage::StorageKey::DynamicKeymapEncoder, + &encoder_metadata, + ) + .await; - OPERATION_COMPLETE.signal(()) - } + // Initialize macros + let _ = database + .check_metadata( + crate::storage::StorageKey::DynamicKeymapMacro, + &layout_metadata, + ) + .await; + if let Ok((stored_data, stored_len)) = database + .read_raw(crate::storage::StorageKey::DynamicKeymapMacro) + .await + { + if let Some(macro_data) = V::get_macro_buffer() { + macro_data.update_buffer(0, &stored_data[..stored_len]) + } + }; } } diff --git a/rumcake/src/via/protocol_12/keycodes.rs b/rumcake/src/via/protocol_12/keycodes.rs index 99bc1b5..29c2a73 100644 --- a/rumcake/src/via/protocol_12/keycodes.rs +++ b/rumcake/src/via/protocol_12/keycodes.rs @@ -970,6 +970,13 @@ where UNKNOWN_KEYCODE } } + Keycode::Hardware(command) => match command { + crate::hw::HardwareCommand::OutputUSB => QMKKeycodes::QK_OUTPUT_USB as u16, + crate::hw::HardwareCommand::OutputBluetooth => { + QMKKeycodes::QK_OUTPUT_BLUETOOTH as u16 + } + _ => UNKNOWN_KEYCODE, + }, #[cfg(feature = "media-keycodes")] Keycode::Media(keycode) => match keycode { usbd_human_interface_device::page::Consumer::Power => { @@ -1043,70 +1050,68 @@ where }, #[cfg(feature = "underglow")] Keycode::Underglow(command) => match command { - crate::underglow::animations::UnderglowCommand::Toggle => { - QMKKeycodes::RGB_TOG as u16 - } - crate::underglow::animations::UnderglowCommand::NextEffect => { + crate::lighting::underglow::UnderglowCommand::Toggle => QMKKeycodes::RGB_TOG as u16, + crate::lighting::underglow::UnderglowCommand::NextEffect => { QMKKeycodes::RGB_MODE_FORWARD as u16 } - crate::underglow::animations::UnderglowCommand::PrevEffect => { + crate::lighting::underglow::UnderglowCommand::PrevEffect => { QMKKeycodes::RGB_MODE_REVERSE as u16 } - crate::underglow::animations::UnderglowCommand::SetEffect(effect) => match effect { - crate::underglow::animations::UnderglowEffect::Solid => { + crate::lighting::underglow::UnderglowCommand::SetEffect(effect) => match effect { + crate::lighting::underglow::UnderglowEffect::Solid => { QMKKeycodes::RGB_MODE_PLAIN as u16 } - crate::underglow::animations::UnderglowEffect::Breathing => { + crate::lighting::underglow::UnderglowEffect::Breathing => { QMKKeycodes::RGB_MODE_BREATHE as u16 } - crate::underglow::animations::UnderglowEffect::RainbowMood => { + crate::lighting::underglow::UnderglowEffect::RainbowMood => { QMKKeycodes::RGB_MODE_RAINBOW as u16 } - crate::underglow::animations::UnderglowEffect::RainbowSwirl => { + crate::lighting::underglow::UnderglowEffect::RainbowSwirl => { QMKKeycodes::RGB_MODE_SWIRL as u16 } - crate::underglow::animations::UnderglowEffect::Snake => { + crate::lighting::underglow::UnderglowEffect::Snake => { QMKKeycodes::RGB_MODE_SNAKE as u16 } - crate::underglow::animations::UnderglowEffect::Knight => { + crate::lighting::underglow::UnderglowEffect::Knight => { QMKKeycodes::RGB_MODE_KNIGHT as u16 } - crate::underglow::animations::UnderglowEffect::Christmas => { + crate::lighting::underglow::UnderglowEffect::Christmas => { QMKKeycodes::RGB_MODE_XMAS as u16 } - crate::underglow::animations::UnderglowEffect::StaticGradient => { + crate::lighting::underglow::UnderglowEffect::StaticGradient => { QMKKeycodes::RGB_MODE_GRADIENT as u16 } - crate::underglow::animations::UnderglowEffect::RGBTest => { + crate::lighting::underglow::UnderglowEffect::RGBTest => { QMKKeycodes::RGB_MODE_RGBTEST as u16 } - crate::underglow::animations::UnderglowEffect::Twinkle => { + crate::lighting::underglow::UnderglowEffect::Twinkle => { QMKKeycodes::RGB_MODE_TWINKLE as u16 } _ => UNKNOWN_KEYCODE, }, - crate::underglow::animations::UnderglowCommand::IncreaseHue(_) => { + crate::lighting::underglow::UnderglowCommand::IncreaseHue(_) => { QMKKeycodes::RGB_HUI as u16 } - crate::underglow::animations::UnderglowCommand::DecreaseHue(_) => { + crate::lighting::underglow::UnderglowCommand::DecreaseHue(_) => { QMKKeycodes::RGB_HUD as u16 } - crate::underglow::animations::UnderglowCommand::IncreaseSaturation(_) => { + crate::lighting::underglow::UnderglowCommand::IncreaseSaturation(_) => { QMKKeycodes::RGB_SAI as u16 } - crate::underglow::animations::UnderglowCommand::DecreaseSaturation(_) => { + crate::lighting::underglow::UnderglowCommand::DecreaseSaturation(_) => { QMKKeycodes::RGB_SAD as u16 } - crate::underglow::animations::UnderglowCommand::IncreaseValue(_) => { + crate::lighting::underglow::UnderglowCommand::IncreaseValue(_) => { QMKKeycodes::RGB_VAI as u16 } - crate::underglow::animations::UnderglowCommand::DecreaseValue(_) => { + crate::lighting::underglow::UnderglowCommand::DecreaseValue(_) => { QMKKeycodes::RGB_VAD as u16 } - crate::underglow::animations::UnderglowCommand::IncreaseSpeed(_) => { + crate::lighting::underglow::UnderglowCommand::IncreaseSpeed(_) => { QMKKeycodes::RGB_SPI as u16 } - crate::underglow::animations::UnderglowCommand::DecreaseSpeed(_) => { + crate::lighting::underglow::UnderglowCommand::DecreaseSpeed(_) => { QMKKeycodes::RGB_SPD as u16 } _ => UNKNOWN_KEYCODE, @@ -1115,22 +1120,22 @@ where Keycode::SimpleBacklight(command) => { if let Some(BacklightType::SimpleBacklight) = K::BACKLIGHT_TYPE { return match command { - crate::backlight::simple_backlight::animations::BacklightCommand::TurnOn => { + crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOn => { QMKKeycodes::QK_BACKLIGHT_ON as u16 } - crate::backlight::simple_backlight::animations::BacklightCommand::TurnOff => { + crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOff => { QMKKeycodes::QK_BACKLIGHT_OFF as u16 } - crate::backlight::simple_backlight::animations::BacklightCommand::Toggle => { + crate::lighting::simple_backlight::SimpleBacklightCommand::Toggle => { QMKKeycodes::QK_BACKLIGHT_TOGGLE as u16 } - crate::backlight::simple_backlight::animations::BacklightCommand::NextEffect => { + crate::lighting::simple_backlight::SimpleBacklightCommand::NextEffect => { QMKKeycodes::QK_BACKLIGHT_STEP as u16 } - crate::backlight::simple_backlight::animations::BacklightCommand::IncreaseValue(_) => { + crate::lighting::simple_backlight::SimpleBacklightCommand::IncreaseValue(_) => { QMKKeycodes::QK_BACKLIGHT_UP as u16 } - crate::backlight::simple_backlight::animations::BacklightCommand::DecreaseSpeed(_) => { + crate::lighting::simple_backlight::SimpleBacklightCommand::DecreaseSpeed(_) => { QMKKeycodes::QK_BACKLIGHT_DOWN as u16 } // Note: Increase/DecreaseHue and Increase/DecreaseSaturation is not handled for RGB matrices. See the note on line 679 @@ -1144,22 +1149,22 @@ where Keycode::SimpleBacklightMatrix(command) => { if let Some(BacklightType::SimpleBacklightMatrix) = K::BACKLIGHT_TYPE { return match command { - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOn => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOn => { QMKKeycodes::QK_BACKLIGHT_ON as u16 } - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOff => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOff => { QMKKeycodes::QK_BACKLIGHT_OFF as u16 } - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::Toggle => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::Toggle => { QMKKeycodes::QK_BACKLIGHT_TOGGLE as u16 } - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::NextEffect => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::NextEffect => { QMKKeycodes::QK_BACKLIGHT_STEP as u16 } - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::IncreaseValue(_) => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::IncreaseValue(_) => { QMKKeycodes::QK_BACKLIGHT_UP as u16 } - crate::backlight::simple_backlight_matrix::animations::BacklightCommand::DecreaseSpeed(_) => { + crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::DecreaseSpeed(_) => { QMKKeycodes::QK_BACKLIGHT_DOWN as u16 } // Note: Increase/DecreaseHue and Increase/DecreaseSaturation is not handled for RGB matrices. See the note on line 679 @@ -1173,22 +1178,22 @@ where Keycode::RGBBacklightMatrix(command) => { if let Some(BacklightType::RGBBacklightMatrix) = K::BACKLIGHT_TYPE { return match command { - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOn => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOn => { QMKKeycodes::QK_BACKLIGHT_ON as u16 } - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOff => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOff => { QMKKeycodes::QK_BACKLIGHT_OFF as u16 } - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::Toggle => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::Toggle => { QMKKeycodes::QK_BACKLIGHT_TOGGLE as u16 } - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::NextEffect => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::NextEffect => { QMKKeycodes::QK_BACKLIGHT_STEP as u16 } - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::IncreaseValue(_) => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::IncreaseValue(_) => { QMKKeycodes::QK_BACKLIGHT_UP as u16 } - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::DecreaseSpeed(_) => { + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::DecreaseSpeed(_) => { QMKKeycodes::QK_BACKLIGHT_DOWN as u16 } // Note: Increase/DecreaseHue and Increase/DecreaseSaturation is not handled for RGB matrices. See the note on line 679 @@ -1198,16 +1203,6 @@ where UNKNOWN_KEYCODE } - #[cfg(feature = "bluetooth")] - Keycode::Bluetooth(command) => match command { - #[cfg(feature = "usb")] - crate::bluetooth::BluetoothCommand::OutputUSB => QMKKeycodes::QK_OUTPUT_USB as u16, - #[cfg(feature = "usb")] - crate::bluetooth::BluetoothCommand::OutputBluetooth => { - QMKKeycodes::QK_OUTPUT_BLUETOOTH as u16 - } - _ => UNKNOWN_KEYCODE, - }, #[allow(unreachable_patterns)] _ => UNKNOWN_KEYCODE, }, @@ -1446,15 +1441,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::TurnOff), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOff), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOff), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOff), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOff), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOff), )), _ => None, }; @@ -1464,15 +1459,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::TurnOn), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::TurnOn), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::TurnOn), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::TurnOn), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOn), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOn), )), _ => None, }; @@ -1482,15 +1477,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::Toggle), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::Toggle), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::Toggle), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::Toggle), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::Toggle), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::Toggle), )), _ => None, }; @@ -1500,15 +1495,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::DecreaseValue(17)), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::DecreaseValue(17)), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::DecreaseValue(17)), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::DecreaseValue(17)), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::DecreaseValue(17)), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::DecreaseValue(17)), )), _ => None, }; @@ -1518,15 +1513,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::IncreaseValue(17)), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::IncreaseValue(17)), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::IncreaseValue(17)), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::IncreaseValue(17)), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::IncreaseValue(17)), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::IncreaseValue(17)), )), _ => None, }; @@ -1536,15 +1531,15 @@ where return match K::BACKLIGHT_TYPE { #[cfg(feature = "simple-backlight")] Some(BacklightType::SimpleBacklight) => Some(Action::Custom( - Keycode::SimpleBacklight(crate::backlight::simple_backlight::animations::BacklightCommand::NextEffect), + Keycode::SimpleBacklight(crate::lighting::simple_backlight::SimpleBacklightCommand::NextEffect), )), #[cfg(feature = "simple-backlight-matrix")] Some(BacklightType::SimpleBacklightMatrix) => Some(Action::Custom( - Keycode::SimpleBacklightMatrix(crate::backlight::simple_backlight_matrix::animations::BacklightCommand::NextEffect), + Keycode::SimpleBacklightMatrix(crate::lighting::simple_backlight_matrix::SimpleBacklightMatrixCommand::NextEffect), )), #[cfg(feature = "rgb-backlight-matrix")] Some(BacklightType::RGBBacklightMatrix) => Some(Action::Custom( - Keycode::RGBBacklightMatrix(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::NextEffect), + Keycode::RGBBacklightMatrix(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::NextEffect), )), _ => None, }; @@ -1555,146 +1550,146 @@ where { if keycode == QMKKeycodes::RGB_TOG as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::Toggle, + crate::lighting::underglow::UnderglowCommand::Toggle, ))); } if keycode == QMKKeycodes::RGB_MODE_FORWARD as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::NextEffect, + crate::lighting::underglow::UnderglowCommand::NextEffect, ))); } if keycode == QMKKeycodes::RGB_MODE_REVERSE as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::PrevEffect, + crate::lighting::underglow::UnderglowCommand::PrevEffect, ))); } if keycode == QMKKeycodes::RGB_HUI as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::IncreaseHue(17), + crate::lighting::underglow::UnderglowCommand::IncreaseHue(17), ))); } if keycode == QMKKeycodes::RGB_HUD as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::DecreaseHue(17), + crate::lighting::underglow::UnderglowCommand::DecreaseHue(17), ))); } if keycode == QMKKeycodes::RGB_SAI as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::IncreaseHue(17), + crate::lighting::underglow::UnderglowCommand::IncreaseHue(17), ))); } if keycode == QMKKeycodes::RGB_SAD as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::DecreaseSaturation(17), + crate::lighting::underglow::UnderglowCommand::DecreaseSaturation(17), ))); } if keycode == QMKKeycodes::RGB_VAI as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::IncreaseValue(17), + crate::lighting::underglow::UnderglowCommand::IncreaseValue(17), ))); } if keycode == QMKKeycodes::RGB_VAD as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::DecreaseValue(17), + crate::lighting::underglow::UnderglowCommand::DecreaseValue(17), ))); } if keycode == QMKKeycodes::RGB_SPI as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::IncreaseSpeed(17), + crate::lighting::underglow::UnderglowCommand::IncreaseSpeed(17), ))); } if keycode == QMKKeycodes::RGB_SPD as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::DecreaseSpeed(17), + crate::lighting::underglow::UnderglowCommand::DecreaseSpeed(17), ))); } if keycode == QMKKeycodes::RGB_MODE_PLAIN as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Solid, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Solid, ), ))); } if keycode == QMKKeycodes::RGB_MODE_BREATHE as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Breathing, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Breathing, ), ))); } if keycode == QMKKeycodes::RGB_MODE_RAINBOW as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::RainbowMood, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::RainbowMood, ), ))); } if keycode == QMKKeycodes::RGB_MODE_SWIRL as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::RainbowSwirl, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::RainbowSwirl, ), ))); } if keycode == QMKKeycodes::RGB_MODE_SNAKE as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Snake, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Snake, ), ))); } if keycode == QMKKeycodes::RGB_MODE_KNIGHT as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Knight, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Knight, ), ))); } if keycode == QMKKeycodes::RGB_MODE_XMAS as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Christmas, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Christmas, ), ))); } if keycode == QMKKeycodes::RGB_MODE_GRADIENT as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::StaticGradient, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::StaticGradient, ), ))); } if keycode == QMKKeycodes::RGB_MODE_RGBTEST as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::RGBTest, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::RGBTest, ), ))); } if keycode == QMKKeycodes::RGB_MODE_TWINKLE as u16 { return Some(Action::Custom(Keycode::Underglow( - crate::underglow::animations::UnderglowCommand::SetEffect( - crate::underglow::animations::UnderglowEffect::Twinkle, + crate::lighting::underglow::UnderglowCommand::SetEffect( + crate::lighting::underglow::UnderglowEffect::Twinkle, ), ))); } @@ -1704,17 +1699,15 @@ where if QMKKeycodeRanges::QK_QUANTUM as u16 <= keycode && keycode <= QMKKeycodeRanges::QK_QUANTUM as u16 { - #[cfg(all(feature = "usb", feature = "bluetooth"))] if keycode == QMKKeycodes::QK_OUTPUT_USB as u16 { - return Some(Action::Custom(Keycode::Bluetooth( - crate::bluetooth::BluetoothCommand::OutputUSB, + return Some(Action::Custom(Keycode::Hardware( + crate::hw::HardwareCommand::OutputUSB, ))); } - #[cfg(all(feature = "usb", feature = "bluetooth"))] if keycode == QMKKeycodes::QK_OUTPUT_BLUETOOTH as u16 { - return Some(Action::Custom(Keycode::Bluetooth( - crate::bluetooth::BluetoothCommand::OutputBluetooth, + return Some(Action::Custom(Keycode::Hardware( + crate::hw::HardwareCommand::OutputBluetooth, ))); } } diff --git a/rumcake/src/via/protocol_12/mod.rs b/rumcake/src/via/protocol_12/mod.rs index c0d2e6b..7c7e718 100644 --- a/rumcake/src/via/protocol_12/mod.rs +++ b/rumcake/src/via/protocol_12/mod.rs @@ -1,12 +1,14 @@ use super::ViaKeyboard; use crate::keyboard::MATRIX_EVENTS; +use crate::storage::{FlashStorage, StorageDevice}; use crate::via::handlers::*; use defmt::{info, warn, Debug2Format}; use embassy_futures::select; use embassy_sync::mutex::Mutex; use num_derive::FromPrimitive; -use crate::hw::mcu::RawMutex; +use crate::hw::platform::RawMutex; +use crate::keyboard::KeyboardLayout; pub(crate) mod keycodes; @@ -97,21 +99,24 @@ enum ViaLEDMatrixValue { pub(crate) struct ViaState where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { - pub(crate) layout_state: - [u8; (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS], + pub(crate) layout_state: [u8; (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) + / u8::BITS as usize + * K::Layout::LAYOUT_ROWS], pub(crate) layout_options: u32, } impl Default for ViaState where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { fn default() -> Self { Self { - layout_state: [0; (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize - * K::LAYOUT_ROWS], + layout_state: [0; (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS], layout_options: K::VIA_EEPROM_LAYOUT_OPTIONS_DEFAULT, } } @@ -121,10 +126,14 @@ pub(crate) async fn process_via_command( data: &mut [u8], via_state: &mut ViaState, ) where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_COLS]:, - [(); K::LAYOUT_ROWS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYERS]:, + [(); K::Layout::LAYOUT_COLS]:, + [(); K::Layout::LAYOUT_ROWS]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { @@ -171,7 +180,7 @@ pub(crate) async fn process_via_command( Some(ViaKeyboardValueId::LayoutOptions) => { set_layout_options::(&mut via_state.layout_options, &data[2..=5]).await } - Some(ViaKeyboardValueId::DeviceIndication) => device_indication().await, + Some(ViaKeyboardValueId::DeviceIndication) => device_indication::().await, Some(value) => { data[0] = ViaCommandId::Unhandled as u8; warn!( @@ -284,20 +293,23 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomGetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaBacklightValue::Brightness) => { - simple_backlight_get_brightness(&mut data[3..=3]).await + simple_backlight_get_brightness::(&mut data[3..=3]).await } Some(ViaBacklightValue::Effect) => { - simple_backlight_get_effect(&mut data[3..=3], |effect| { - // Just directly convert to an ID. We assume that custom UI is being used. - effect as u8 - }) + simple_backlight_get_effect::( + &mut data[3..=3], + |effect| { + // Just directly convert to an ID. We assume that custom UI is being used. + effect as u8 + }, + ) .await } Some(ViaBacklightValue::EffectSpeed) => { - simple_backlight_get_speed(&mut data[3..=3]).await + simple_backlight_get_speed::(&mut data[3..=3]).await } Some(ViaBacklightValue::Enabled) => { - simple_backlight_get_enabled(&mut data[3..=3]).await + simple_backlight_get_enabled::(&mut data[3..=3]).await } None => { warn!( @@ -310,20 +322,20 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomSetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaBacklightValue::Brightness) => { - simple_backlight_set_brightness(&data[3..=3]).await + simple_backlight_set_brightness::(&data[3..=3]).await } Some(ViaBacklightValue::Effect) => { - simple_backlight_set_effect(&data[3..=3], |id| { + simple_backlight_set_effect::(&data[3..=3], |id| { // Just directly convert to an effect from the ID. We assume that custom UI is being used. num::FromPrimitive::from_u8(id) }) .await } Some(ViaBacklightValue::EffectSpeed) => { - simple_backlight_set_speed(&data[3..=3]).await + simple_backlight_set_speed::(&data[3..=3]).await } Some(ViaBacklightValue::Enabled) => { - simple_backlight_set_enabled(&data[3..=3]).await + simple_backlight_set_enabled::(&data[3..=3]).await } None => { warn!( @@ -333,7 +345,7 @@ pub(crate) async fn process_via_command( } }; } - ViaCommandId::CustomSave => simple_backlight_save().await, + ViaCommandId::CustomSave => simple_backlight_save::().await, _ => unreachable!("Should not happen"), }; } @@ -343,11 +355,13 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomGetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaLEDMatrixValue::Brightness) => { - simple_backlight_matrix_get_brightness(&mut data[3..=3]) - .await + simple_backlight_matrix_get_brightness::( + &mut data[3..=3], + ) + .await } Some(ViaLEDMatrixValue::Effect) => { - simple_backlight_matrix_get_effect( + simple_backlight_matrix_get_effect::( &mut data[3..=3], |effect| { // Just directly convert to an ID. We assume that custom UI is being used. @@ -357,10 +371,12 @@ pub(crate) async fn process_via_command( .await } Some(ViaLEDMatrixValue::EffectSpeed) => { - simple_backlight_matrix_get_speed(&mut data[3..=3]).await + simple_backlight_matrix_get_speed::(&mut data[3..=3]) + .await } Some(ViaLEDMatrixValue::Enabled) => { - simple_backlight_matrix_get_enabled(&mut data[3..=3]).await + simple_backlight_matrix_get_enabled::(&mut data[3..=3]) + .await } None => { warn!( @@ -373,20 +389,24 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomSetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaLEDMatrixValue::Brightness) => { - simple_backlight_matrix_set_brightness(&data[3..=3]).await + simple_backlight_matrix_set_brightness::(&data[3..=3]) + .await } Some(ViaLEDMatrixValue::Effect) => { - simple_backlight_matrix_set_effect(&data[3..=3], |id| { - // Just directly convert to an effect from the ID. We assume that custom UI is being used. - num::FromPrimitive::from_u8(id) - }) + simple_backlight_matrix_set_effect::( + &data[3..=3], + |id| { + // Just directly convert to an effect from the ID. We assume that custom UI is being used. + num::FromPrimitive::from_u8(id) + }, + ) .await } Some(ViaLEDMatrixValue::EffectSpeed) => { - simple_backlight_matrix_set_speed(&data[3..=3]).await + simple_backlight_matrix_set_speed::(&data[3..=3]).await } Some(ViaLEDMatrixValue::Enabled) => { - simple_backlight_matrix_set_enabled(&data[3..=3]).await + simple_backlight_matrix_set_enabled::(&data[3..=3]).await } None => { warn!( @@ -396,7 +416,7 @@ pub(crate) async fn process_via_command( } }; } - ViaCommandId::CustomSave => simple_backlight_matrix_save().await, + ViaCommandId::CustomSave => simple_backlight_matrix_save::().await, _ => unreachable!("Should not happen"), }; } @@ -406,10 +426,11 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomGetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaRGBMatrixValue::Brightness) => { - rgb_backlight_matrix_get_brightness(&mut data[3..=3]).await + rgb_backlight_matrix_get_brightness::(&mut data[3..=3]) + .await } Some(ViaRGBMatrixValue::Effect) => { - rgb_backlight_matrix_get_effect( + rgb_backlight_matrix_get_effect::( &mut data[3..=3], |effect| { // Just directly convert to an ID. We assume that custom UI is being used. @@ -419,13 +440,14 @@ pub(crate) async fn process_via_command( .await } Some(ViaRGBMatrixValue::EffectSpeed) => { - rgb_backlight_matrix_get_speed(&mut data[3..=3]).await + rgb_backlight_matrix_get_speed::(&mut data[3..=3]).await } Some(ViaRGBMatrixValue::Color) => { - rgb_backlight_matrix_get_color(&mut data[3..=4]).await + rgb_backlight_matrix_get_color::(&mut data[3..=4]).await } Some(ViaRGBMatrixValue::Enabled) => { - rgb_backlight_matrix_get_enabled(&mut data[3..=3]).await + rgb_backlight_matrix_get_enabled::(&mut data[3..=3]) + .await } None => { warn!( @@ -438,23 +460,23 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomSetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaRGBMatrixValue::Brightness) => { - rgb_backlight_matrix_set_brightness(&data[3..=3]).await + rgb_backlight_matrix_set_brightness::(&data[3..=3]).await } Some(ViaRGBMatrixValue::Effect) => { - rgb_backlight_matrix_set_effect(&data[3..=3], |id| { + rgb_backlight_matrix_set_effect::(&data[3..=3], |id| { // Just directly convert to an effect from the ID. We assume that custom UI is being used. num::FromPrimitive::from_u8(id) }) .await } Some(ViaRGBMatrixValue::EffectSpeed) => { - rgb_backlight_matrix_set_speed(&data[3..=3]).await + rgb_backlight_matrix_set_speed::(&data[3..=3]).await } Some(ViaRGBMatrixValue::Color) => { - rgb_backlight_matrix_set_color(&data[3..=4]).await + rgb_backlight_matrix_set_color::(&data[3..=4]).await } Some(ViaRGBMatrixValue::Enabled) => { - rgb_backlight_matrix_set_enabled(&data[3..=3]).await + rgb_backlight_matrix_set_enabled::(&data[3..=3]).await } None => { warn!( @@ -464,7 +486,7 @@ pub(crate) async fn process_via_command( } }; } - ViaCommandId::CustomSave => rgb_backlight_matrix_save().await, + ViaCommandId::CustomSave => rgb_backlight_matrix_save::().await, _ => unreachable!("Should not happen"), }; } @@ -474,23 +496,23 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomGetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaRGBLightValue::Brightness) => { - underglow_get_brightness(&mut data[3..=3]).await + underglow_get_brightness::(&mut data[3..=3]).await } Some(ViaRGBLightValue::Effect) => { - underglow_get_effect(&mut data[3..=3], |config| { + underglow_get_effect::(&mut data[3..=3], |config| { // Just directly convert to an ID. We assume that custom UI is being used. config.effect as u8 }) .await } Some(ViaRGBLightValue::EffectSpeed) => { - underglow_get_speed(&mut data[3..=3]).await; + underglow_get_speed::(&mut data[3..=3]).await; } Some(ViaRGBLightValue::Color) => { - underglow_get_color(&mut data[3..=4]).await; + underglow_get_color::(&mut data[3..=4]).await; } Some(ViaRGBLightValue::Enabled) => { - underglow_get_enabled(&mut data[3..=3]).await; + underglow_get_enabled::(&mut data[3..=3]).await; } None => { warn!( @@ -503,27 +525,27 @@ pub(crate) async fn process_via_command( ViaCommandId::CustomSetValue => { match num::FromPrimitive::from_u8(data[2]) { Some(ViaRGBLightValue::Brightness) => { - underglow_set_brightness(&data[3..=3]).await + underglow_set_brightness::(&data[3..=3]).await } Some(ViaRGBLightValue::Effect) => { - underglow_set_effect(&data[3..=3], |id| { + underglow_set_effect::(&data[3..=3], |id| { // Just directly convert to an effect from the ID. We assume that custom UI is being used. (num::FromPrimitive::from_u8(id) as Option< - crate::underglow::animations::UnderglowEffect, + crate::lighting::underglow::UnderglowEffect, >) .map(|effect| (effect, None)) }) .await } Some(ViaRGBLightValue::EffectSpeed) => { - underglow_set_speed(&data[3..=3]).await + underglow_set_speed::(&data[3..=3]).await } Some(ViaRGBLightValue::Color) => { - underglow_set_color(&data[3..=4]).await + underglow_set_color::(&data[3..=4]).await } Some(ViaRGBLightValue::Enabled) => { - underglow_set_enabled(&data[3..=3]).await; + underglow_set_enabled::(&data[3..=3]).await; } None => { warn!( @@ -533,7 +555,7 @@ pub(crate) async fn process_via_command( } }; } - ViaCommandId::CustomSave => underglow_save().await, + ViaCommandId::CustomSave => underglow_save::().await, _ => unreachable!("Should not happen"), }; } @@ -570,16 +592,18 @@ pub(crate) async fn process_via_command( } } -pub(crate) async fn background_task(via_state: &Mutex>) -where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, +pub(crate) async fn background_task( + via_state: &Mutex>, +) where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { // Update the layout_state. Used for SwitchMatrixState let mut subscriber = MATRIX_EVENTS.subscriber().unwrap(); - #[cfg(feature = "storage")] - { - via_state.lock().await.layout_options = super::storage::VIA_LAYOUT_OPTIONS.wait().await; + if K::get_storage_service().is_some() { + via_state.lock().await.layout_options = super::VIA_LAYOUT_OPTIONS.wait().await; } loop { @@ -594,7 +618,7 @@ where // (cols + 8 bits - 1) / 8 bits: we get the number of bytes needed to store the state of a // row (based on number of cols). multiply this by (row + 1), subtract by 1 and subtract by // (col / 8 bits) to get the byte that contains the bit we need to update. - let byte = (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + let byte = (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * (row as usize + 1) - 1 - col as usize / u8::BITS as usize; @@ -610,7 +634,7 @@ where select::Either::Second(()) => { // Wait for 500 ms. This should give enough time to send an HID report and let the host read it embassy_time::Timer::after(embassy_time::Duration::from_millis(500)).await; - crate::hw::mcu::jump_to_bootloader(); + crate::hw::platform::jump_to_bootloader(); } } } diff --git a/rumcake/src/vial/handlers.rs b/rumcake/src/vial/handlers.rs index fc5efdd..7543a3b 100644 --- a/rumcake/src/vial/handlers.rs +++ b/rumcake/src/vial/handlers.rs @@ -4,7 +4,9 @@ use smart_leds::hsv::hsv2rgb; use super::protocol::via::ViaState; use super::protocol::{VialState, VIAL_RAW_EPSIZE}; use super::{VialKeyboard, VIAL_DIRECT_SET_CHANNEL}; -use crate::backlight::BacklightMatrixDevice; +use crate::keyboard::KeyboardLayout; +use crate::lighting::BacklightMatrixDevice; +use crate::storage::{FlashStorage, StorageDevice, StorageKey}; // Unlike the other normal Via comands, Vial overwrites the command data received from the host @@ -60,13 +62,14 @@ pub fn unlock_poll( vial_state: &mut VialState, via_state: &ViaState, ) where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { if !K::VIAL_INSECURE && vial_state.unlock_in_progress { let holding = K::VIAL_UNLOCK_COMBO.iter().all(|(row, col)| { // get the byte that stores the bit for the corresponding matrix coordinate // see [`crate::via::protocol::background_task`] - let byte = (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + let byte = (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * (*row as usize + 1) - 1 - *col as usize / u8::BITS as usize; @@ -107,39 +110,39 @@ pub fn vialrgb_get_info(version: u16, data: &mut [u8]) { #[cfg(feature = "rgb-backlight-matrix")] pub async fn vialrgb_get_mode(data: &mut [u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE && K::get_backlight_matrix().is_some() { - let config = crate::backlight::rgb_backlight_matrix::BACKLIGHT_CONFIG_STATE - .get() - .await; - if !config.enabled { - data[0..=1].copy_from_slice(&[0; 2]) - } else { - data[0..=1].copy_from_slice( - &super::protocol::vialrgb::convert_effect_to_vialrgb_id(config.effect) - .to_le_bytes(), - ); + if let Some(state) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_state() { + let config = state.get().await; + if !config.enabled { + data[0..=1].copy_from_slice(&[0; 2]) + } else { + data[0..=1].copy_from_slice( + &super::protocol::vialrgb::convert_effect_to_vialrgb_id(config.effect) + .to_le_bytes(), + ); + } + data[2] = config.speed; + data[3] = config.hue; + data[4] = config.sat; + data[5] = config.val; } - data[2] = config.speed; - data[3] = config.hue; - data[4] = config.sat; - data[5] = config.val; } } #[cfg(feature = "rgb-backlight-matrix")] pub fn vialrgb_get_supported(data: &mut [u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE && K::get_backlight_matrix().is_some() { let gt = u16::from_le_bytes(data[0..=1].try_into().unwrap()); data.fill(0xFF); for id in gt..=super::protocol::vialrgb::MAX_VIALRGB_ID { - if super::protocol::vialrgb::is_supported::(id) { + if super::protocol::vialrgb::is_supported::(id) { data[(id as usize - gt as usize)..=(id as usize - gt as usize + 1)] .copy_from_slice(&id.to_le_bytes()) } @@ -150,12 +153,12 @@ where #[cfg(feature = "rgb-backlight-matrix")] pub fn vialrgb_get_num_leds(data: &mut [u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE && K::get_backlight_matrix().is_some() { - let num_leds = (K::BacklightMatrixDevice::LIGHTING_COLS - * K::BacklightMatrixDevice::LIGHTING_ROWS) as u16; + let num_leds = (K::RGBBacklightMatrixDevice::LIGHTING_COLS + * K::RGBBacklightMatrixDevice::LIGHTING_ROWS) as u16; data[0..=1].copy_from_slice(&num_leds.to_le_bytes()); } } @@ -163,15 +166,15 @@ where #[cfg(feature = "rgb-backlight-matrix")] pub fn vialrgb_get_led_info(data: &mut [u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE { if let Some(backlight_matrix) = K::get_backlight_matrix() { let led = u16::from_le_bytes(data[0..=1].try_into().unwrap()); - let col = led as usize % K::BacklightMatrixDevice::LIGHTING_COLS; - let row = (led as usize / K::BacklightMatrixDevice::LIGHTING_COLS) - % K::BacklightMatrixDevice::LIGHTING_ROWS; + let col = led as usize % K::RGBBacklightMatrixDevice::LIGHTING_COLS; + let row = (led as usize / K::RGBBacklightMatrixDevice::LIGHTING_COLS) + % K::RGBBacklightMatrixDevice::LIGHTING_ROWS; if let Some((x, y)) = backlight_matrix.layout[row][col] { data[0] = x; data[1] = y; @@ -186,78 +189,81 @@ where #[cfg(feature = "rgb-backlight-matrix")] pub async fn vialrgb_set_mode(data: &[u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE && K::get_backlight_matrix().is_some() { - // set mode - let vialrgb_id = u16::from_le_bytes(data[0..=1].try_into().unwrap()); - if vialrgb_id == 0 { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOff) - .await; - } else { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::TurnOn) - .await; - if let Some(effect) = super::protocol::vialrgb::convert_vialrgb_id_to_effect(vialrgb_id) - { - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send(crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetEffect( + if let Some(channel) = <::RGBBacklightMatrixDeviceType as crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice>::get_command_channel() { + // set mode + let vialrgb_id = u16::from_le_bytes(data[0..=1].try_into().unwrap()); + if vialrgb_id == 0 { + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOff) + .await; + } else { + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::TurnOn) + .await; + if let Some(effect) = + super::protocol::vialrgb::convert_vialrgb_id_to_effect(vialrgb_id) + { + channel + .send(crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetEffect( effect, )) .await; - } else { - warn!( - "[VIA] Tried to set an unknown VialRGB effect: {:?}", - vialrgb_id - ) + } else { + warn!( + "[VIA] Tried to set an unknown VialRGB effect: {:?}", + vialrgb_id + ) + } } - } - // set speed - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetSpeed( - data[2], - ), - ) - .await; - - // set hsv - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetHue( - data[3], - ), - ) - .await; - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetSaturation( - data[4], - ), - ) - .await; - crate::backlight::rgb_backlight_matrix::BACKLIGHT_COMMAND_CHANNEL - .send( - crate::backlight::rgb_backlight_matrix::animations::BacklightCommand::SetValue( - data[5], - ), - ) - .await; + // set speed + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetSpeed( + data[2], + ), + ) + .await; + + // set hsv + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetHue( + data[3], + ), + ) + .await; + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetSaturation( + data[4], + ), + ) + .await; + channel + .send( + crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixCommand::SetValue( + data[5], + ), + ) + .await; + } } } #[cfg(feature = "rgb-backlight-matrix")] pub async fn vialrgb_direct_fast_set(data: &[u8]) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, { if K::VIALRGB_ENABLE && K::get_backlight_matrix().is_some() { - let total_num_leds = (K::BacklightMatrixDevice::LIGHTING_COLS - * K::BacklightMatrixDevice::LIGHTING_ROWS) as u8; + let total_num_leds = (K::RGBBacklightMatrixDevice::LIGHTING_COLS + * K::RGBBacklightMatrixDevice::LIGHTING_ROWS) as u8; let first_led = u16::from_le_bytes(data[0..=1].try_into().unwrap()) as u8; // We assume that a backlight matrix will not have more than 255 leds let num_leds = data[2]; @@ -321,7 +327,13 @@ pub fn dynamic_keymap_set_key_override(data: &mut [u8]) { // TODO } -pub async fn eeprom_reset() { - #[cfg(feature = "storage")] - super::storage::reset_data().await; +pub async fn eeprom_reset() +where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, +{ + if let Some(database) = K::get_storage_service() { + let _ = database.delete(StorageKey::DynamicKeymapTapDance).await; + let _ = database.delete(StorageKey::DynamicKeymapCombo).await; + let _ = database.delete(StorageKey::DynamicKeymapKeyOverride).await; + } } diff --git a/rumcake/src/vial/mod.rs b/rumcake/src/vial/mod.rs index e33e2a0..f4694c2 100644 --- a/rumcake/src/vial/mod.rs +++ b/rumcake/src/vial/mod.rs @@ -2,15 +2,20 @@ //! //! To use Vial, you will need to implement [`ViaKeyboard`] and [`VialKeyboard`]. -use crate::backlight::{BacklightMatrixDevice, EmptyBacklightMatrix}; use defmt::assert; use embassy_futures::join; use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use smart_leds::RGB8; -use crate::hw::mcu::RawMutex; -use crate::via::{ViaKeyboard, VIA_REPORT_HID_RECEIVE_CHANNEL, VIA_REPORT_HID_SEND_CHANNEL}; +use crate::hw::platform::RawMutex; +use crate::hw::HIDDevice; +use crate::keyboard::KeyboardLayout; +use crate::lighting::private::EmptyLightingDevice; +use crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice; +use crate::lighting::BacklightMatrixDevice; +use crate::storage::{FlashStorage, StorageDevice, StorageKey}; +use crate::via::ViaKeyboard; mod handlers; @@ -47,11 +52,11 @@ pub trait VialKeyboard: ViaKeyboard { const VIAL_KEY_OVERRIDE_ENTRIES: u8 = 0; // TODO: Change when key override is implemented // TODO: replace with specialization if it doesn't cause an ICE - type BacklightMatrixDevice: BacklightMatrixDevice = EmptyBacklightMatrix; + type RGBBacklightMatrixDevice: MaybeRGBBacklightMatrixDevice = EmptyLightingDevice; fn get_backlight_matrix() -> Option< - crate::backlight::BacklightMatrix< - { ::LIGHTING_COLS }, - { ::LIGHTING_ROWS }, + crate::lighting::BacklightMatrix< + { ::LIGHTING_COLS }, + { ::LIGHTING_ROWS }, >, > { None @@ -59,25 +64,27 @@ pub trait VialKeyboard: ViaKeyboard { } /// Channel used to update the frame buffer for the -/// [`crate::backlight::rgb_backlight_matrix::animations::BacklightEffect::DirectSet`] effect. +/// [`crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixEffect::DirectSet`] effect. pub(crate) static VIAL_DIRECT_SET_CHANNEL: Channel = Channel::new(); #[rumcake_macros::task] -pub async fn vial_process_task(_k: K) +pub async fn vial_process_task(_k: K) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYERS]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { - assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= K::LAYERS); + assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= K::Layout::LAYERS); assert!(K::DYNAMIC_KEYMAP_LAYER_COUNT <= 16); assert!(K::VIAL_UNLOCK_COMBO.len() < 15); - if K::get_macro_buffer().is_some() { + if ::get_macro_buffer().is_some() { assert!( K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE > 0, "Macro buffer size must be greater than 0 if you are using Via macros." @@ -95,6 +102,8 @@ where let vial_state: Mutex = Mutex::new(Default::default()); let via_state: Mutex> = Mutex::new(Default::default()); + let receive_channel = K::get_via_hid_receive_channel(); + let send_channel = K::get_via_hid_send_channel(); if K::VIAL_INSECURE { vial_state.lock().await.unlocked = true; @@ -102,7 +111,7 @@ where let report_fut = async { loop { - let mut report = VIA_REPORT_HID_RECEIVE_CHANNEL.receive().await; + let mut report = receive_channel.receive().await; if K::VIAL_ENABLED && K::VIA_ENABLED { { @@ -116,7 +125,7 @@ where .await; } - VIA_REPORT_HID_SEND_CHANNEL.send(report).await; + send_channel.send(report).await; } } }; @@ -124,114 +133,27 @@ where join::join(report_fut, protocol::via::background_task::(&via_state)).await; } -#[cfg(feature = "storage")] -pub mod storage { - use embassy_sync::channel::Channel; - use embassy_sync::signal::Signal; - - use crate::hw::mcu::RawMutex; - use crate::storage::{FlashStorage, StorageDevice, StorageKey}; - - use super::VialKeyboard; - - pub(super) enum VialStorageKeys { - DynamicKeymapTapDance, - DynamicKeymapCombo, - DynamicKeymapKeyOverride, - } - - impl From for StorageKey { - fn from(value: VialStorageKeys) -> Self { - match value { - VialStorageKeys::DynamicKeymapTapDance => StorageKey::DynamicKeymapTapDance, - VialStorageKeys::DynamicKeymapCombo => StorageKey::DynamicKeymapCombo, - VialStorageKeys::DynamicKeymapKeyOverride => StorageKey::DynamicKeymapKeyOverride, - } - } - } - - enum Operation { - Write([u8; 32], VialStorageKeys, usize, usize), - Delete, - } - - /// A function that dispatches a flash operation to the Vial storage task. This will obtain a - /// lock, and hold onto it until the storage task signals a completion. `offset` corresponds to - /// the first byte of the stored data for the given `key` that we want to update. For example, - /// if [0x23, 0x65, 0xEB] is stored in flash for the key `LayoutOptions`, and we want to update - /// the last 2 bytes, we would pass in an offset of 1, and a `data` slice with a length of 2. - pub(super) async fn update_data(key: VialStorageKeys, offset: usize, data: &[u8]) { - // TODO: this function will wait eternally if vial_storage_task is not there - let mut buf = [0; 32]; - let len = data.len(); - buf[..len].copy_from_slice(data); - OPERATION_CHANNEL - .send(Operation::Write(buf, key, offset, len)) +pub async fn initialize_vial_data(_v: V) +where + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, +{ + if let Some(database) = V::get_storage_service() { + // let tap_dance_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; + let tap_dance_metadata = [1]; + let _ = database + .check_metadata(StorageKey::DynamicKeymapTapDance, &tap_dance_metadata) .await; - OPERATION_COMPLETE.wait().await; - } - - pub(super) async fn reset_data() { - OPERATION_CHANNEL.send(Operation::Delete).await; - OPERATION_COMPLETE.wait().await - } - static OPERATION_COMPLETE: Signal = Signal::new(); - static OPERATION_CHANNEL: Channel = Channel::new(); - - #[rumcake_macros::task] - pub async fn vial_storage_task( - _k: K, - database: &crate::storage::StorageService<'static, F>, - ) where - [(); F::ERASE_SIZE]:, - { - // Initialize Vial data - { - // let tap_dance_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; - let tap_dance_metadata = [1]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - StorageKey::DynamicKeymapTapDance, - &tap_dance_metadata, - ) - .await; - - // let combo_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; - let combo_metadata = [1]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - StorageKey::DynamicKeymapCombo, - &combo_metadata, - ) - .await; - - // let key_override_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; - let key_override_metadata = [1]; - let _ = database - .check_metadata( - K::get_storage_buffer(), - StorageKey::DynamicKeymapKeyOverride, - &key_override_metadata, - ) - .await; - } + // let combo_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; + let combo_metadata = [1]; + let _ = database + .check_metadata(StorageKey::DynamicKeymapCombo, &combo_metadata) + .await; - loop { - match OPERATION_CHANNEL.receive().await { - Operation::Write(data, key, offset, len) => match key { - VialStorageKeys::DynamicKeymapTapDance => {} - VialStorageKeys::DynamicKeymapCombo => {} - VialStorageKeys::DynamicKeymapKeyOverride => {} - }, - Operation::Delete => { - let _ = database.delete(StorageKey::DynamicKeymapTapDance).await; - let _ = database.delete(StorageKey::DynamicKeymapCombo).await; - let _ = database.delete(StorageKey::DynamicKeymapKeyOverride).await; - } - } - } + // let key_override_metadata: [u8; core::mem::size_of::()] = unsafe {core::mem::transmute(TypeId::of::<>())}; + let key_override_metadata = [1]; + let _ = database + .check_metadata(StorageKey::DynamicKeymapKeyOverride, &key_override_metadata) + .await; } } diff --git a/rumcake/src/vial/protocol/lighting.rs b/rumcake/src/vial/protocol/lighting.rs index 7fc0f6e..8bea969 100644 --- a/rumcake/src/vial/protocol/lighting.rs +++ b/rumcake/src/vial/protocol/lighting.rs @@ -54,9 +54,9 @@ const UNKNOWN_EFFECT: u8 = 0; #[cfg(feature = "underglow")] pub(crate) fn convert_underglow_effect_to_qmk_id( - config: crate::underglow::animations::UnderglowConfig, + config: crate::lighting::underglow::UnderglowConfig, ) -> u8 { - use crate::underglow::animations::UnderglowEffect; + use crate::lighting::underglow::UnderglowEffect; match config.effect { UnderglowEffect::Solid => QMKRGBLightEffects::SolidColor as u8, UnderglowEffect::Breathing => match config.speed { @@ -103,8 +103,8 @@ pub(crate) fn convert_underglow_effect_to_qmk_id( #[cfg(feature = "underglow")] pub(crate) fn convert_qmk_id_to_underglow_effect( id: u8, -) -> Option<(crate::underglow::animations::UnderglowEffect, Option)> { - use crate::underglow::animations::UnderglowEffect; +) -> Option<(crate::lighting::underglow::UnderglowEffect, Option)> { + use crate::lighting::underglow::UnderglowEffect; match num::FromPrimitive::from_u8(id) as Option { Some(effect) => match effect { QMKRGBLightEffects::AllOff => None, // ID 0 is handled in the protocol by disabling the underglow system @@ -158,25 +158,25 @@ enum QMKBacklightEffects { #[cfg(feature = "simple-backlight")] pub(crate) fn convert_backlight_effect_to_qmk_id( - effect: crate::backlight::simple_backlight::animations::BacklightEffect, + effect: crate::lighting::simple_backlight::SimpleBacklightEffect, ) -> u8 { - use crate::backlight::simple_backlight::animations::BacklightEffect; + use crate::lighting::simple_backlight::SimpleBacklightEffect; match effect { - BacklightEffect::Solid => QMKBacklightEffects::Solid as u8, - BacklightEffect::Breathing => QMKBacklightEffects::Breathing as u8, - BacklightEffect::Reactive => UNKNOWN_EFFECT, + SimpleBacklightEffect::Solid => QMKBacklightEffects::Solid as u8, + SimpleBacklightEffect::Breathing => QMKBacklightEffects::Breathing as u8, + SimpleBacklightEffect::Reactive => UNKNOWN_EFFECT, } } #[cfg(feature = "simple-backlight")] pub(crate) fn convert_qmk_id_to_backlight_effect( id: u8, -) -> Option { - use crate::backlight::simple_backlight::animations::BacklightEffect; +) -> Option { + use crate::lighting::simple_backlight::SimpleBacklightEffect; match num::FromPrimitive::from_u8(id) as Option { Some(effect) => match effect { - QMKBacklightEffects::Solid => Some(BacklightEffect::Solid), - QMKBacklightEffects::Breathing => Some(BacklightEffect::Breathing), + QMKBacklightEffects::Solid => Some(SimpleBacklightEffect::Solid), + QMKBacklightEffects::Breathing => Some(SimpleBacklightEffect::Breathing), }, None => None, // Instead of defaulting to the last valid effect like QMK, we will just do nothing } diff --git a/rumcake/src/vial/protocol/mod.rs b/rumcake/src/vial/protocol/mod.rs index b9a1a4b..aebe3bd 100644 --- a/rumcake/src/vial/protocol/mod.rs +++ b/rumcake/src/vial/protocol/mod.rs @@ -1,5 +1,7 @@ use super::VialKeyboard; -use crate::backlight::BacklightMatrixDevice; +use crate::keyboard::KeyboardLayout; +use crate::lighting::BacklightMatrixDevice; +use crate::storage::{FlashStorage, StorageDevice}; use crate::via::handlers::{dynamic_keymap_get_encoder, dynamic_keymap_set_encoder}; use crate::vial::handlers::*; use defmt::{info, warn, Debug2Format}; @@ -60,12 +62,16 @@ pub(crate) async fn process_vial_command( vial_state: &mut VialState, via_state: &mut via::ViaState, ) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::NUM_ENCODERS * 2 * 2]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYERS]:, + [(); K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYOUT_COLS]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { diff --git a/rumcake/src/vial/protocol/via.rs b/rumcake/src/vial/protocol/via.rs index 4b575f6..5c52d54 100644 --- a/rumcake/src/vial/protocol/via.rs +++ b/rumcake/src/vial/protocol/via.rs @@ -1,5 +1,6 @@ -use crate::backlight::BacklightMatrixDevice; use crate::keyboard::MATRIX_EVENTS; +use crate::lighting::BacklightMatrixDevice; +use crate::storage::{FlashStorage, StorageDevice}; use crate::via::handlers::eeprom_reset as via_eeprom_reset; use crate::via::handlers::*; use crate::vial::handlers::eeprom_reset as vial_eeprom_reset; @@ -11,7 +12,8 @@ use embassy_sync::mutex::Mutex; use num_derive::FromPrimitive; use super::VialState; -use crate::hw::mcu::RawMutex; +use crate::hw::platform::RawMutex; +use crate::keyboard::KeyboardLayout; use crate::via::protocol::keycodes; // We just use the keycode conversions from the new via protocol pub(crate) const VIA_PROTOCOL_VERSION: u16 = 0x0009; @@ -142,21 +144,24 @@ enum ViaAudioValue { pub(crate) struct ViaState where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { - pub(crate) layout_state: - [u8; (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS], + pub(crate) layout_state: [u8; (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) + / u8::BITS as usize + * K::Layout::LAYOUT_ROWS], pub(crate) layout_options: u32, } impl Default for ViaState where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { fn default() -> Self { Self { - layout_state: [0; (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize - * K::LAYOUT_ROWS], + layout_state: [0; (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS], layout_options: K::VIA_EEPROM_LAYOUT_OPTIONS_DEFAULT, } } @@ -167,12 +172,15 @@ pub(crate) async fn process_via_command( via_state: &mut ViaState, vial_state: &mut VialState, ) where - [(); K::BacklightMatrixDevice::LIGHTING_COLS]:, - [(); K::BacklightMatrixDevice::LIGHTING_ROWS]:, - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, - [(); K::LAYERS]:, - [(); K::LAYOUT_ROWS]:, - [(); K::LAYOUT_COLS]:, + [(); <::FlashStorageType as FlashStorage>::ERASE_SIZE]:, + [(); K::DYNAMIC_KEYMAP_LAYER_COUNT * K::Layout::LAYOUT_COLS * K::Layout::LAYOUT_ROWS * 2]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_COLS]:, + [(); K::RGBBacklightMatrixDevice::LIGHTING_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYERS]:, + [(); K::Layout::LAYOUT_ROWS]:, + [(); K::Layout::LAYOUT_COLS]:, [(); K::DYNAMIC_KEYMAP_MACRO_BUFFER_SIZE as usize]:, [(); K::DYNAMIC_KEYMAP_MACRO_COUNT as usize]:, { @@ -316,42 +324,44 @@ pub(crate) async fn process_via_command( match num::FromPrimitive::from_u8(data[1]) as Option { #[cfg(feature = "simple-backlight")] Some(ViaLightingValue::BacklightBrightness) => { - simple_backlight_set_brightness(&mut data[2..=2]).await + simple_backlight_set_brightness::(&mut data[2..=2]).await } #[cfg(feature = "simple-backlight")] Some(ViaLightingValue::BacklightEffect) => { - simple_backlight_set_effect(&mut data[2..=2], |id| { + simple_backlight_set_effect::(&mut data[2..=2], |id| { lighting::convert_qmk_id_to_backlight_effect(id) }) .await; } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightBrightness) => { - underglow_set_brightness(&mut data[2..=2]).await + underglow_set_brightness::(&mut data[2..=2]).await } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightEffect) => { - if data[2] == 0 { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::TurnOff) - .await; - } else { - crate::underglow::UNDERGLOW_COMMAND_CHANNEL - .send(crate::underglow::animations::UnderglowCommand::TurnOn) - .await; - underglow_set_effect(&mut data[2..=2], |id| { - lighting::convert_qmk_id_to_underglow_effect(id) - }) - .await + if let Some(channel) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_command_channel() { + if data[2] == 0 { + channel + .send(crate::lighting::underglow::UnderglowCommand::TurnOff) + .await; + } else { + channel + .send(crate::lighting::underglow::UnderglowCommand::TurnOn) + .await; + underglow_set_effect::(&mut data[2..=2], |id| { + lighting::convert_qmk_id_to_underglow_effect(id) + }) + .await + } } } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightEffectSpeed) => { - underglow_set_speed(&mut data[2..=2]).await + underglow_set_speed::(&mut data[2..=2]).await } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightColor) => { - underglow_set_color(&mut data[2..=3]).await + underglow_set_color::(&mut data[2..=3]).await } #[cfg(feature = "rgb-backlight-matrix")] Some(ViaLightingValue::Mode) => vialrgb_set_mode::(&mut data[2..=7]).await, @@ -374,37 +384,39 @@ pub(crate) async fn process_via_command( match num::FromPrimitive::from_u8(data[1]) as Option { #[cfg(feature = "simple-backlight")] Some(ViaLightingValue::BacklightBrightness) => { - simple_backlight_get_brightness(&mut data[2..=2]).await + simple_backlight_get_brightness::(&mut data[2..=2]).await } #[cfg(feature = "simple-backlight")] Some(ViaLightingValue::BacklightEffect) => { - simple_backlight_get_effect(&mut data[2..=2], |effect| { + simple_backlight_get_effect::(&mut data[2..=2], |effect| { lighting::convert_backlight_effect_to_qmk_id(effect) }) .await } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightBrightness) => { - underglow_get_brightness(&mut data[2..=2]).await + underglow_get_brightness::(&mut data[2..=2]).await } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightEffect) => { - if !crate::underglow::UNDERGLOW_CONFIG_STATE.get().await.enabled { - data[2] = 0 - } else { - underglow_get_effect(&mut data[2..=2], |config| { - lighting::convert_underglow_effect_to_qmk_id(config) - }) - .await + if let Some(state) = <::UnderglowDeviceType as crate::lighting::underglow::private::MaybeUnderglowDevice>::get_state() { + if !state.get().await.enabled { + data[2] = 0 + } else { + underglow_get_effect::(&mut data[2..=2], |config| { + lighting::convert_underglow_effect_to_qmk_id(config) + }) + .await + } } } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightEffectSpeed) => { - underglow_get_speed(&mut data[2..=2]).await + underglow_get_speed::(&mut data[2..=2]).await } #[cfg(feature = "underglow")] Some(ViaLightingValue::RGBLightColor) => { - underglow_get_color(&mut data[2..=3]).await + underglow_get_color::(&mut data[2..=3]).await } #[cfg(feature = "rgb-backlight-matrix")] Some(ViaLightingValue::Info) => { @@ -434,9 +446,9 @@ pub(crate) async fn process_via_command( }; } ViaCommandId::CustomSave => { - simple_backlight_save().await; - rgb_backlight_matrix_save().await; - underglow_save().await; + simple_backlight_save::().await; + rgb_backlight_matrix_save::().await; + underglow_save::().await; } _ => { data[0] = ViaCommandId::Unhandled as u8; @@ -453,7 +465,8 @@ pub(crate) async fn process_via_command( pub(crate) async fn background_task(via_state: &Mutex>) where - [(); (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * K::LAYOUT_ROWS]:, + [(); (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + * K::Layout::LAYOUT_ROWS]:, { // Update the layout_state. Used for SwitchMatrixState let mut subscriber = MATRIX_EVENTS.subscriber().unwrap(); @@ -464,7 +477,7 @@ where // (cols + 8 bits - 1) / 8 bits: we get the number of bytes needed to store the state of a // row (based on number of cols). multiply this by (row + 1), subtract by 1 and subtract by // (col / 8 bits) to get the byte that contains the bit we need to update. - let byte = (K::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize + let byte = (K::Layout::LAYOUT_COLS + u8::BITS as usize - 1) / u8::BITS as usize * (row as usize + 1) - 1 - col as usize / u8::BITS as usize; diff --git a/rumcake/src/vial/protocol/vialrgb.rs b/rumcake/src/vial/protocol/vialrgb.rs index 80a8e25..87afd83 100644 --- a/rumcake/src/vial/protocol/vialrgb.rs +++ b/rumcake/src/vial/protocol/vialrgb.rs @@ -1,7 +1,7 @@ //! Functions for handling VialRGB effect ID conversions. -use crate::backlight::rgb_backlight_matrix::animations::BacklightEffect; -use crate::backlight::BacklightDevice; +use crate::lighting::rgb_backlight_matrix::private::MaybeRGBBacklightMatrixDevice; +use crate::lighting::rgb_backlight_matrix::RGBBacklightMatrixEffect; use num_derive::FromPrimitive; #[repr(u16)] @@ -59,190 +59,254 @@ enum VialRGBEffectIDs { pub(crate) const MAX_VIALRGB_ID: u16 = VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_FRACTAL as u16; const UNKNOWN_EFFECT: u16 = 0; -pub(crate) fn convert_effect_to_vialrgb_id(effect: BacklightEffect) -> u16 { +pub(crate) fn convert_effect_to_vialrgb_id(effect: RGBBacklightMatrixEffect) -> u16 { match effect { - BacklightEffect::Solid => VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_COLOR as u16, - BacklightEffect::AlphasMods => VialRGBEffectIDs::VIALRGB_EFFECT_ALPHAS_MODS as u16, - BacklightEffect::GradientUpDown => VialRGBEffectIDs::VIALRGB_EFFECT_GRADIENT_UP_DOWN as u16, - BacklightEffect::GradientLeftRight => { + RGBBacklightMatrixEffect::Solid => VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_COLOR as u16, + RGBBacklightMatrixEffect::AlphasMods => VialRGBEffectIDs::VIALRGB_EFFECT_ALPHAS_MODS as u16, + RGBBacklightMatrixEffect::GradientUpDown => { + VialRGBEffectIDs::VIALRGB_EFFECT_GRADIENT_UP_DOWN as u16 + } + RGBBacklightMatrixEffect::GradientLeftRight => { VialRGBEffectIDs::VIALRGB_EFFECT_GRADIENT_LEFT_RIGHT as u16 } - BacklightEffect::Breathing => VialRGBEffectIDs::VIALRGB_EFFECT_BREATHING as u16, - BacklightEffect::ColorbandSat => VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SAT as u16, - BacklightEffect::ColorbandVal => VialRGBEffectIDs::VIALRGB_EFFECT_BAND_VAL as u16, - BacklightEffect::ColorbandPinWheelSat => { + RGBBacklightMatrixEffect::Breathing => VialRGBEffectIDs::VIALRGB_EFFECT_BREATHING as u16, + RGBBacklightMatrixEffect::ColorbandSat => VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SAT as u16, + RGBBacklightMatrixEffect::ColorbandVal => VialRGBEffectIDs::VIALRGB_EFFECT_BAND_VAL as u16, + RGBBacklightMatrixEffect::ColorbandPinWheelSat => { VialRGBEffectIDs::VIALRGB_EFFECT_BAND_PINWHEEL_SAT as u16 } - BacklightEffect::ColorbandPinWheelVal => { + RGBBacklightMatrixEffect::ColorbandPinWheelVal => { VialRGBEffectIDs::VIALRGB_EFFECT_BAND_PINWHEEL_VAL as u16 } - BacklightEffect::ColorbandSpiralSat => { + RGBBacklightMatrixEffect::ColorbandSpiralSat => { VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SPIRAL_SAT as u16 } - BacklightEffect::ColorbandSpiralVal => { + RGBBacklightMatrixEffect::ColorbandSpiralVal => { VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SPIRAL_VAL as u16 } - BacklightEffect::CycleAll => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_ALL as u16, - BacklightEffect::CycleLeftRight => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_LEFT_RIGHT as u16, - BacklightEffect::CycleUpDown => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_UP_DOWN as u16, - BacklightEffect::RainbowMovingChevron => { + RGBBacklightMatrixEffect::CycleAll => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_ALL as u16, + RGBBacklightMatrixEffect::CycleLeftRight => { + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_LEFT_RIGHT as u16 + } + RGBBacklightMatrixEffect::CycleUpDown => { + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_UP_DOWN as u16 + } + RGBBacklightMatrixEffect::RainbowMovingChevron => { VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_MOVING_CHEVRON as u16 } - BacklightEffect::CycleOutIn => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN as u16, - BacklightEffect::CycleOutInDual => { + RGBBacklightMatrixEffect::CycleOutIn => { + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN as u16 + } + RGBBacklightMatrixEffect::CycleOutInDual => { VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN_DUAL as u16 } - BacklightEffect::CyclePinWheel => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_PINWHEEL as u16, - BacklightEffect::CycleSpiral => VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_SPIRAL as u16, - BacklightEffect::DualBeacon => VialRGBEffectIDs::VIALRGB_EFFECT_DUAL_BEACON as u16, - BacklightEffect::RainbowBeacon => VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_BEACON as u16, - BacklightEffect::RainbowPinWheels => { + RGBBacklightMatrixEffect::CyclePinWheel => { + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_PINWHEEL as u16 + } + RGBBacklightMatrixEffect::CycleSpiral => { + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_SPIRAL as u16 + } + RGBBacklightMatrixEffect::DualBeacon => VialRGBEffectIDs::VIALRGB_EFFECT_DUAL_BEACON as u16, + RGBBacklightMatrixEffect::RainbowBeacon => { + VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_BEACON as u16 + } + RGBBacklightMatrixEffect::RainbowPinWheels => { VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_PINWHEELS as u16 } - BacklightEffect::Raindrops => VialRGBEffectIDs::VIALRGB_EFFECT_RAINDROPS as u16, - BacklightEffect::JellybeanRaindrops => { + RGBBacklightMatrixEffect::Raindrops => VialRGBEffectIDs::VIALRGB_EFFECT_RAINDROPS as u16, + RGBBacklightMatrixEffect::JellybeanRaindrops => { VialRGBEffectIDs::VIALRGB_EFFECT_JELLYBEAN_RAINDROPS as u16 } - BacklightEffect::HueBreathing => VialRGBEffectIDs::VIALRGB_EFFECT_HUE_BREATHING as u16, - BacklightEffect::HuePendulum => VialRGBEffectIDs::VIALRGB_EFFECT_HUE_PENDULUM as u16, - BacklightEffect::HueWave => VialRGBEffectIDs::VIALRGB_EFFECT_HUE_WAVE as u16, - BacklightEffect::PixelRain => VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_RAIN as u16, - BacklightEffect::PixelFlow => UNKNOWN_EFFECT, - BacklightEffect::PixelFractal => VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_FRACTAL as u16, - BacklightEffect::TypingHeatmap => VialRGBEffectIDs::VIALRGB_EFFECT_TYPING_HEATMAP as u16, - BacklightEffect::DigitalRain => VialRGBEffectIDs::VIALRGB_EFFECT_DIGITAL_RAIN as u16, - BacklightEffect::SolidReactiveSimple => { + RGBBacklightMatrixEffect::HueBreathing => { + VialRGBEffectIDs::VIALRGB_EFFECT_HUE_BREATHING as u16 + } + RGBBacklightMatrixEffect::HuePendulum => { + VialRGBEffectIDs::VIALRGB_EFFECT_HUE_PENDULUM as u16 + } + RGBBacklightMatrixEffect::HueWave => VialRGBEffectIDs::VIALRGB_EFFECT_HUE_WAVE as u16, + RGBBacklightMatrixEffect::PixelRain => VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_RAIN as u16, + RGBBacklightMatrixEffect::PixelFlow => UNKNOWN_EFFECT, + RGBBacklightMatrixEffect::PixelFractal => { + VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_FRACTAL as u16 + } + RGBBacklightMatrixEffect::TypingHeatmap => { + VialRGBEffectIDs::VIALRGB_EFFECT_TYPING_HEATMAP as u16 + } + RGBBacklightMatrixEffect::DigitalRain => { + VialRGBEffectIDs::VIALRGB_EFFECT_DIGITAL_RAIN as u16 + } + RGBBacklightMatrixEffect::SolidReactiveSimple => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_SIMPLE as u16 } - BacklightEffect::SolidReactive => VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE as u16, - BacklightEffect::SolidReactiveWide => { + RGBBacklightMatrixEffect::SolidReactive => { + VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE as u16 + } + RGBBacklightMatrixEffect::SolidReactiveWide => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_WIDE as u16 } - BacklightEffect::SolidReactiveMultiWide => { + RGBBacklightMatrixEffect::SolidReactiveMultiWide => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTIWIDE as u16 } - BacklightEffect::SolidReactiveCross => { + RGBBacklightMatrixEffect::SolidReactiveCross => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_CROSS as u16 } - BacklightEffect::SolidReactiveMultiCross => { + RGBBacklightMatrixEffect::SolidReactiveMultiCross => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTICROSS as u16 } - BacklightEffect::SolidReactiveNexus => { + RGBBacklightMatrixEffect::SolidReactiveNexus => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_NEXUS as u16 } - BacklightEffect::SolidReactiveMultiNexus => { + RGBBacklightMatrixEffect::SolidReactiveMultiNexus => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTINEXUS as u16 } - BacklightEffect::Splash => VialRGBEffectIDs::VIALRGB_EFFECT_SPLASH as u16, - BacklightEffect::MultiSplash => VialRGBEffectIDs::VIALRGB_EFFECT_MULTISPLASH as u16, - BacklightEffect::SolidSplash => VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_SPLASH as u16, - BacklightEffect::SolidMultiSplash => { + RGBBacklightMatrixEffect::Splash => VialRGBEffectIDs::VIALRGB_EFFECT_SPLASH as u16, + RGBBacklightMatrixEffect::MultiSplash => { + VialRGBEffectIDs::VIALRGB_EFFECT_MULTISPLASH as u16 + } + RGBBacklightMatrixEffect::SolidSplash => { + VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_SPLASH as u16 + } + RGBBacklightMatrixEffect::SolidMultiSplash => { VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_MULTISPLASH as u16 } - BacklightEffect::DirectSet => VialRGBEffectIDs::VIALRGB_EFFECT_DIRECT as u16, + RGBBacklightMatrixEffect::DirectSet => VialRGBEffectIDs::VIALRGB_EFFECT_DIRECT as u16, } } -pub(crate) fn convert_vialrgb_id_to_effect(id: u16) -> Option { +pub(crate) fn convert_vialrgb_id_to_effect(id: u16) -> Option { match num::FromPrimitive::from_u16(id) as Option { Some(vialrgb_id) => { match vialrgb_id { VialRGBEffectIDs::VIALRGB_EFFECT_OFF => None, // ID 0 is handled in the protocol by disabling the rgb matrix system - VialRGBEffectIDs::VIALRGB_EFFECT_DIRECT => Some(BacklightEffect::DirectSet), - VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_COLOR => Some(BacklightEffect::Solid), - VialRGBEffectIDs::VIALRGB_EFFECT_ALPHAS_MODS => Some(BacklightEffect::AlphasMods), + VialRGBEffectIDs::VIALRGB_EFFECT_DIRECT => { + Some(RGBBacklightMatrixEffect::DirectSet) + } + VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_COLOR => { + Some(RGBBacklightMatrixEffect::Solid) + } + VialRGBEffectIDs::VIALRGB_EFFECT_ALPHAS_MODS => { + Some(RGBBacklightMatrixEffect::AlphasMods) + } VialRGBEffectIDs::VIALRGB_EFFECT_GRADIENT_UP_DOWN => { - Some(BacklightEffect::GradientUpDown) + Some(RGBBacklightMatrixEffect::GradientUpDown) } VialRGBEffectIDs::VIALRGB_EFFECT_GRADIENT_LEFT_RIGHT => { - Some(BacklightEffect::GradientLeftRight) + Some(RGBBacklightMatrixEffect::GradientLeftRight) + } + VialRGBEffectIDs::VIALRGB_EFFECT_BREATHING => { + Some(RGBBacklightMatrixEffect::Breathing) + } + VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SAT => { + Some(RGBBacklightMatrixEffect::ColorbandSat) + } + VialRGBEffectIDs::VIALRGB_EFFECT_BAND_VAL => { + Some(RGBBacklightMatrixEffect::ColorbandVal) } - VialRGBEffectIDs::VIALRGB_EFFECT_BREATHING => Some(BacklightEffect::Breathing), - VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SAT => Some(BacklightEffect::ColorbandSat), - VialRGBEffectIDs::VIALRGB_EFFECT_BAND_VAL => Some(BacklightEffect::ColorbandVal), VialRGBEffectIDs::VIALRGB_EFFECT_BAND_PINWHEEL_SAT => { - Some(BacklightEffect::ColorbandPinWheelSat) + Some(RGBBacklightMatrixEffect::ColorbandPinWheelSat) } VialRGBEffectIDs::VIALRGB_EFFECT_BAND_PINWHEEL_VAL => { - Some(BacklightEffect::ColorbandPinWheelVal) + Some(RGBBacklightMatrixEffect::ColorbandPinWheelVal) } VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SPIRAL_SAT => { - Some(BacklightEffect::ColorbandSpiralSat) + Some(RGBBacklightMatrixEffect::ColorbandSpiralSat) } VialRGBEffectIDs::VIALRGB_EFFECT_BAND_SPIRAL_VAL => { - Some(BacklightEffect::ColorbandSpiralVal) + Some(RGBBacklightMatrixEffect::ColorbandSpiralVal) + } + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_ALL => { + Some(RGBBacklightMatrixEffect::CycleAll) } - VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_ALL => Some(BacklightEffect::CycleAll), VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_LEFT_RIGHT => { - Some(BacklightEffect::CycleLeftRight) + Some(RGBBacklightMatrixEffect::CycleLeftRight) } VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_UP_DOWN => { - Some(BacklightEffect::CycleUpDown) + Some(RGBBacklightMatrixEffect::CycleUpDown) } VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_MOVING_CHEVRON => { - Some(BacklightEffect::RainbowMovingChevron) + Some(RGBBacklightMatrixEffect::RainbowMovingChevron) + } + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN => { + Some(RGBBacklightMatrixEffect::CycleOutIn) } - VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN => Some(BacklightEffect::CycleOutIn), VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_OUT_IN_DUAL => { - Some(BacklightEffect::CycleOutInDual) + Some(RGBBacklightMatrixEffect::CycleOutInDual) } VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_PINWHEEL => { - Some(BacklightEffect::CyclePinWheel) + Some(RGBBacklightMatrixEffect::CyclePinWheel) + } + VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_SPIRAL => { + Some(RGBBacklightMatrixEffect::CycleSpiral) + } + VialRGBEffectIDs::VIALRGB_EFFECT_DUAL_BEACON => { + Some(RGBBacklightMatrixEffect::DualBeacon) } - VialRGBEffectIDs::VIALRGB_EFFECT_CYCLE_SPIRAL => Some(BacklightEffect::CycleSpiral), - VialRGBEffectIDs::VIALRGB_EFFECT_DUAL_BEACON => Some(BacklightEffect::DualBeacon), VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_BEACON => { - Some(BacklightEffect::RainbowBeacon) + Some(RGBBacklightMatrixEffect::RainbowBeacon) } VialRGBEffectIDs::VIALRGB_EFFECT_RAINBOW_PINWHEELS => { - Some(BacklightEffect::RainbowPinWheels) + Some(RGBBacklightMatrixEffect::RainbowPinWheels) + } + VialRGBEffectIDs::VIALRGB_EFFECT_RAINDROPS => { + Some(RGBBacklightMatrixEffect::Raindrops) } - VialRGBEffectIDs::VIALRGB_EFFECT_RAINDROPS => Some(BacklightEffect::Raindrops), VialRGBEffectIDs::VIALRGB_EFFECT_JELLYBEAN_RAINDROPS => { - Some(BacklightEffect::JellybeanRaindrops) + Some(RGBBacklightMatrixEffect::JellybeanRaindrops) } VialRGBEffectIDs::VIALRGB_EFFECT_HUE_BREATHING => { - Some(BacklightEffect::HueBreathing) + Some(RGBBacklightMatrixEffect::HueBreathing) + } + VialRGBEffectIDs::VIALRGB_EFFECT_HUE_PENDULUM => { + Some(RGBBacklightMatrixEffect::HuePendulum) + } + VialRGBEffectIDs::VIALRGB_EFFECT_HUE_WAVE => { + Some(RGBBacklightMatrixEffect::HueWave) } - VialRGBEffectIDs::VIALRGB_EFFECT_HUE_PENDULUM => Some(BacklightEffect::HuePendulum), - VialRGBEffectIDs::VIALRGB_EFFECT_HUE_WAVE => Some(BacklightEffect::HueWave), VialRGBEffectIDs::VIALRGB_EFFECT_TYPING_HEATMAP => { - Some(BacklightEffect::TypingHeatmap) + Some(RGBBacklightMatrixEffect::TypingHeatmap) + } + VialRGBEffectIDs::VIALRGB_EFFECT_DIGITAL_RAIN => { + Some(RGBBacklightMatrixEffect::DigitalRain) } - VialRGBEffectIDs::VIALRGB_EFFECT_DIGITAL_RAIN => Some(BacklightEffect::DigitalRain), VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_SIMPLE => { - Some(BacklightEffect::SolidReactiveSimple) + Some(RGBBacklightMatrixEffect::SolidReactiveSimple) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE => { - Some(BacklightEffect::SolidReactive) + Some(RGBBacklightMatrixEffect::SolidReactive) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_WIDE => { - Some(BacklightEffect::SolidReactiveWide) + Some(RGBBacklightMatrixEffect::SolidReactiveWide) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTIWIDE => { - Some(BacklightEffect::SolidReactiveMultiWide) + Some(RGBBacklightMatrixEffect::SolidReactiveMultiWide) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_CROSS => { - Some(BacklightEffect::SolidReactiveCross) + Some(RGBBacklightMatrixEffect::SolidReactiveCross) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTICROSS => { - Some(BacklightEffect::SolidReactiveMultiCross) + Some(RGBBacklightMatrixEffect::SolidReactiveMultiCross) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_NEXUS => { - Some(BacklightEffect::SolidReactiveNexus) + Some(RGBBacklightMatrixEffect::SolidReactiveNexus) } VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_REACTIVE_MULTINEXUS => { - Some(BacklightEffect::SolidReactiveMultiNexus) + Some(RGBBacklightMatrixEffect::SolidReactiveMultiNexus) + } + VialRGBEffectIDs::VIALRGB_EFFECT_SPLASH => Some(RGBBacklightMatrixEffect::Splash), + VialRGBEffectIDs::VIALRGB_EFFECT_MULTISPLASH => { + Some(RGBBacklightMatrixEffect::MultiSplash) + } + VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_SPLASH => { + Some(RGBBacklightMatrixEffect::SolidSplash) } - VialRGBEffectIDs::VIALRGB_EFFECT_SPLASH => Some(BacklightEffect::Splash), - VialRGBEffectIDs::VIALRGB_EFFECT_MULTISPLASH => Some(BacklightEffect::MultiSplash), - VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_SPLASH => Some(BacklightEffect::SolidSplash), VialRGBEffectIDs::VIALRGB_EFFECT_SOLID_MULTISPLASH => { - Some(BacklightEffect::SolidMultiSplash) + Some(RGBBacklightMatrixEffect::SolidMultiSplash) + } + VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_RAIN => { + Some(RGBBacklightMatrixEffect::PixelRain) } - VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_RAIN => Some(BacklightEffect::PixelRain), VialRGBEffectIDs::VIALRGB_EFFECT_PIXEL_FRACTAL => { - Some(BacklightEffect::PixelFractal) + Some(RGBBacklightMatrixEffect::PixelFractal) } } } @@ -250,9 +314,9 @@ pub(crate) fn convert_vialrgb_id_to_effect(id: u16) -> Option { } } -pub(crate) fn is_supported(id: u16) -> bool { +pub(crate) fn is_supported(id: u16) -> bool { match convert_vialrgb_id_to_effect(id) { - Some(effect) => effect.is_enabled::(), + Some(effect) => K::is_effect_enabled(effect), None => false, } }