diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a4370cc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "Keyboard" +version = "0.1.0" +edition = "2024" + +[dependencies] +embassy-embedded-hal = { version = "0.3.0", path = "embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.2", path = "embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.4.0", path = "embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = { version = "0.4.0", path = "embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "embassy-net", features = ["defmt", "icmp", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } +embassy-net-wiznet = { version = "0.2.0", path = "embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "embassy-futures" } +embassy-usb-logger = { version = "0.4.0", path = "embassy-usb-logger" } +cyw43 = { version = "0.3.0", path = "cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { version = "0.4.0", path = "cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" +reqwless = { version = "0.13.0", features = ["defmt"] } +serde = { version = "1.0.203", default-features = false, features = ["derive"] } + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.4.0" +heapless = "0.8" +usbd-hid = "0.8.1" +rand_core = "0.6.4" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" +elf2uf2-rs = "2.1.1" +mqttrust_core = "=0.6.0" +bbqueue = { version = "0.5.1", features = ["thumbv6"]} + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..6f5a665 --- /dev/null +++ b/build.rs @@ -0,0 +1,36 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} \ No newline at end of file diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..771b8fa --- /dev/null +++ b/memory.x @@ -0,0 +1,17 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + + /* Pick one of the two options for RAM layout */ + + /* OPTION A: Use all RAM banks as one big block */ + /* Reasonable, unless you are doing something */ + /* really particular with DMA or other concurrent */ + /* access that would benefit from striping */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K + + /* OPTION B: Keep the unstriped sections separate */ + /* RAM: ORIGIN = 0x20000000, LENGTH = 256K */ + /* SCRATCH_A: ORIGIN = 0x20040000, LENGTH = 4K */ + /* SCRATCH_B: ORIGIN = 0x20041000, LENGTH = 4K */ +} \ No newline at end of file diff --git a/src/config_parser.rs b/src/config_parser.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/connections.rs b/src/connections.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..34b0955 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,323 @@ +#![no_std] +#![no_main] + + +use core::sync::atomic::{AtomicBool, Ordering}; +use bbqueue::BBBuffer; +use cyw43::{JoinOptions}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::{join}; +use embassy_rp::{bind_interrupts}; +use embassy_rp::gpio::{Input, Output, Pull}; +use embassy_rp::gpio::Level::{High, Low}; +use embassy_rp::peripherals::{DMA_CH0, PIO0, USB}; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; +use static_cell::StaticCell; +use embassy_rp::pio::{Pio,InterruptHandler as IrqHandler}; +use embassy_net::{Config as Conf, StackResources}; +use embassy_rp::clocks::RoscRng; +use rand::RngCore; +use embassy_time::{Timer}; + + +const WIFI_NETWORK: &str = "iot2.4"; +const WIFI_PASSWORD: &str = "Holthuizenbrink"; + +static mut Q: BBBuffer<{ 1024 * 6 }> = BBBuffer::new(); + +struct Reports { + key_press: [[u8; 5]; 5], +} + +enum KeyActions { + KeyWrite, + KeySequence, + KeyCommand, +} + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await +} +#[embassy_executor::task] +async fn mqtt_task() { + +} + + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => IrqHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let fw = include_bytes!("../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin"); + + let pwr = Output::new(p.PIN_23, Low); + let cs = Output::new(p.PIN_25, High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(_spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + + let config = Conf::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(_net_device, config, RESOURCES.init(StackResources::new()), seed); + + unwrap!(_spawner.spawn(net_task(runner))); + + loop { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // Wait for DHCP, not necessary when using static IP + info!("waiting for DHCP..."); + while !stack.is_config_up() { + Timer::after_millis(100).await; + } + info!("DHCP is now up!"); + + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Martijn"); + config.product = Some("keyboard"); + config.serial_number = Some("000001"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + /* + let (fp, c) = unsafe { Q.try_split_framed().unwrap() }; + + let client_id = "mqtt_test_client_id"; + + let mut mqtt_eventloop = EventLoop::new( + c, + embassy_time::, + MqttOptions::new(client_id, "broker.hivemq.com".into(), 1883), + ); + + let mut mqttoptions = MqttOptions::new("rumqtt-sync", Broker::from("test.mosquitto.org"), 1883); + mqttoptions.set_keep_alive(5); + */ + + + + let report = [[4,4,4,5,6],[7,8,9,10,11],[12,13,14,15,16],[17,18,19,20,21],[22,23,24,25,26],]; + + let reports = Reports{key_press: report}; + + let mut rows = [Output::new(p.PIN_6, High), Output::new(p.PIN_15, High), Output::new(p.PIN_16, High), Output::new(p.PIN_17, High), Output::new(p.PIN_18, High)]; + let mut cols = [Input::new(p.PIN_1, Pull::Up), Input::new(p.PIN_5, Pull::Up),Input::new(p.PIN_8, Pull::Up),Input::new(p.PIN_10, Pull::Up), Input::new(p.PIN_21, Pull::Up)]; + + control.gpio_set(0, true).await; + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop{ + let mut keys = [0;6]; + let count = 0; + for (i, row) in rows.iter_mut().enumerate() { + row.set_low(); + for (j, col) in cols.iter_mut().enumerate() { + if col.is_low() { + keys[count] = reports.key_press[i][j]; + } + } + row.set_high(); + } + //TODO + //keep track of order of keypresses + + + + let report = KeyboardReport { + keycodes: keys, + leds: 0, + modifier: 0, + reserved: 0, + }; + // send report + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + + async fn wifi_init(){ + + } + + + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} \ No newline at end of file diff --git a/src/mqtt_ha.rs b/src/mqtt_ha.rs new file mode 100644 index 0000000..34ff87f --- /dev/null +++ b/src/mqtt_ha.rs @@ -0,0 +1,11 @@ +use mqttrust_core; +use crate::mqtt_ha; +// setup all mqtt settings +// manage connection +// send button presses to home assistant + +//init + + +//send_button +