init project

This commit is contained in:
martijn 2025-04-24 12:08:06 +02:00
parent 788d9bd6ea
commit 4e333f29da
7 changed files with 445 additions and 0 deletions

58
Cargo.toml Normal file
View File

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

36
build.rs Normal file
View File

@ -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");
}

17
memory.x Normal file
View File

@ -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 */
}

0
src/config_parser.rs Normal file
View File

0
src/connections.rs Normal file
View File

323
src/main.rs Normal file
View File

@ -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<USB>;
PIO0_IRQ_0 => IrqHandler<PIO0>;
});
#[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<cyw43::State> = 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<StackResources<3>> = 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<usize> {
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<ReportId>, dur: u32) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> {
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.");
}
}
}

11
src/mqtt_ha.rs Normal file
View File

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