diff --git a/.gitignore b/.gitignore index 73a638b..8b053e4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.embuild /target /Cargo.lock +/.env diff --git a/Cargo.toml b/Cargo.toml index 015b23d..6752389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,10 @@ authors = ["Jack Hogan "] edition = "2021" license = "MIT OR Apache-2.0" +[[bin]] +name = "sign-firmware" +harness = false + [dependencies] esp-backtrace = { version = "0.14.0", features = [ "esp32s3", @@ -12,15 +16,19 @@ esp-backtrace = { version = "0.14.0", features = [ "panic-handler", "println", ] } -esp-hal = { version = "0.20.1", features = ["esp32s3"] } +esp-hal = { git = "https://github.com/esp-rs/esp-hal.git", features = [ + "esp32s3", +] } esp-println = { version = "0.11.0", features = ["esp32s3", "log"] } log = { version = "0.4.21" } -esp-alloc = { version = "0.4.0" } -esp-wifi = { version = "0.8.0", features = [ +esp-alloc = { git = "https://github.com/esp-rs/esp-hal.git" } +esp-wifi = { git = "https://github.com/esp-rs/esp-hal.git", features = [ "esp32s3", "phy-enable-usb", "utils", "wifi", + "embassy-net", + "async", ] } heapless = { version = "0.8.0", default-features = false } smoltcp = { version = "0.11.0", default-features = false, features = [ @@ -36,16 +44,41 @@ smoltcp = { version = "0.11.0", default-features = false, features = [ ] } embassy-executor = { version = "0.6.0", features = ["task-arena-size-12288"] } embassy-sync = "0.6.0" -embassy-time = { version = "0.3.1", features = ["generic-queue"] } +embassy-time = { version = "*", features = ["generic-queue"] } embedded-hal = "1.0.0" -static_cell = "2.1.0" -esp-hal-embassy = { version = "0.3.0", features = ["esp32s3"] } +static_cell = { version = "2.1.0", features = ["nightly"] } +esp-hal-embassy = { git = "https://github.com/esp-rs/esp-hal.git", features = [ + "esp32s3", +] } palette = { version = "0.7.6", default-features = false, features = [ "alloc", "libm", ] } lightning-time = { version = "0.2.0", default-features = false } chrono = { version = "0.4.38", default-features = false, features = ["alloc"] } +dotenvy_macro = "0.15.7" +embassy-net = { version = "*", features = [ + "dhcpv4", + "dns", + "log", + "medium-ip", + "proto-ipv4", + "tcp", + "udp", +] } +reqwless = { version = "*", default-features = false, features = [ + "log", + "embedded-tls", + "alloc", +] } +const-random = "0.1.18" +serde = { version = "1.0.210", default-features = false, features = [ + "derive", + "alloc", +] } +serde_json = { version = "1.0.128", default-features = false, features = [ + "alloc", +] } [profile.dev] # Rust debug is too slow. @@ -60,3 +93,6 @@ incremental = false lto = 'fat' opt-level = 's' overflow-checks = false + +[build-dependencies] +embuild = "0.32.0" diff --git a/src/lib.rs b/src/lib.rs index 354f967..dbafa08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,9 @@ // pub mod eeprom; // pub mod schema; +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel}; use embassy_time::{Duration, Ticker}; -use esp_hal::gpio::AnyOutput; +use esp_hal::gpio::{AnyPin, Output}; pub struct Leds { pub buffer: [u8; 15], @@ -47,21 +48,11 @@ const GAMMA_LUT: [u8; 256] = [ 249, 251, 253, 255, ]; -static mut TARGET_DATA_BUFFER: usize = 0; - -static mut LED_DATA_BUFFER_0: [u8; 15] = [ - 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, -]; - -static mut LED_DATA_BUFFER_1: [u8; 15] = [ - 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, -]; +static PWM_CONTROL: Channel = Channel::new(); impl Leds { pub fn create() -> Leds { - Leds { - buffer: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - } + Leds { buffer: [0; 15] } } pub async fn set_color(&mut self, color: palette::Srgb, block: Block) { @@ -75,56 +66,36 @@ impl Leds { self.buffer[b] = GAMMA_LUT[color.blue as usize]; } - pub async fn swap(&mut self) { - unsafe { - let mut copied_data_buffer_target = TARGET_DATA_BUFFER; - - match copied_data_buffer_target { - 0 => { - LED_DATA_BUFFER_1 = self.buffer; - } - 1 => { - LED_DATA_BUFFER_0 = self.buffer; - } - _ => { - copied_data_buffer_target = 0; - } - } - - TARGET_DATA_BUFFER = 1 - copied_data_buffer_target; - } + pub async fn swap(&self) { + PWM_CONTROL.send(self.buffer).await; } } #[embassy_executor::task] -pub async fn leds_software_pwm(mut led_pins: [AnyOutput<'static>; 15]) { +pub async fn leds_software_pwm(mut led_pins: [Output<'static, AnyPin>; 15]) { let mut timer_value: u8 = 0; + let mut last_buffer = [0_u8; 15]; // Update at 120hz - let mut ticker = Ticker::every(Duration::from_hz(256 * 120)); + let mut ticker = Ticker::every(Duration::from_hz(256 * 200)); - loop { - timer_value += 1; + let pwm_receiver = PWM_CONTROL.receiver(); - let copied_data_buffer_target = unsafe { TARGET_DATA_BUFFER }; + loop { + last_buffer = pwm_receiver.try_receive().unwrap_or(last_buffer); for n in 0..15 { if timer_value == 0 { led_pins[n].set_high(); - } else if unsafe { - match copied_data_buffer_target { - 0 => LED_DATA_BUFFER_0[n], - 1 => LED_DATA_BUFFER_1[n], - _ => { - unreachable!("Woah, that's an invalid buffer pointer you got there."); - } - } - } >= timer_value - { + } + + if last_buffer[n] == timer_value { led_pins[n].set_low(); } } + timer_value += 1; + ticker.next().await; } } diff --git a/src/main.rs b/src/main.rs index 4c28aa0..cfe6f1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,48 +1,110 @@ #![no_std] #![no_main] - -use chrono::{Duration, NaiveTime}; +#![feature(type_alias_impl_trait)] + +use chrono::{DateTime, Duration, FixedOffset, NaiveDateTime, NaiveTime}; +use dotenvy_macro::dotenv; +use embassy_net::{ + dns::DnsSocket, + tcp::client::{TcpClient, TcpClientState}, + tcp::TcpSocket, + Ipv4Address, Stack as NetStack, StackResources, +}; use embassy_time::Timer; use esp_backtrace as _; use esp_hal::{ - clock::ClockControl, cpu_control::{CpuControl, Stack}, - gpio::{AnyOutput, Io, Level}, + gpio::{AnyPin, Io, Level, Output}, + interrupt::software::SoftwareInterruptControl, interrupt::Priority, peripherals::Peripherals, prelude::*, rtc_cntl::Rtc, - system::SystemControl, timer::timg::TimerGroup, }; -use esp_hal_embassy::InterruptExecutor; +use esp_hal_embassy::{Executor, InterruptExecutor}; +use esp_wifi::wifi::{ + Configuration, EapClientConfiguration, TtlsPhase2Method, WifiController, WifiDevice, WifiEvent, + WifiStaDevice, WifiState, +}; use lightning_time::LightningTime; +use log::{debug, error, info}; +use reqwless::{ + client::{HttpClient, TlsConfig, TlsVerify}, + request::Method, +}; use sign_firmware::Block; use sign_firmware::{leds_software_pwm, Leds}; use static_cell::StaticCell; extern crate alloc; -use core::{mem::MaybeUninit, ptr::addr_of_mut}; - -#[global_allocator] -static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); +use core::{ptr::addr_of_mut, str::FromStr}; -fn init_heap() { - const HEAP_SIZE: usize = 32 * 1024; - static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); +#[embassy_executor::task] +async fn amain( + mut leds: Leds, + rtc: Rtc<'static>, + stack: &'static NetStack>, +) { + loop { + if stack.is_link_up() { + break; + } + Timer::after_millis(500).await; + } - unsafe { - ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE); + info!("Waiting to get IP address..."); + loop { + if let Some(config) = stack.config_v4() { + info!("Got IP: {}", config.address); + break; + } + Timer::after_millis(500).await; } -} -#[embassy_executor::task] -async fn amain(mut leds: Leds, rtc: Rtc<'static>) { - let time = NaiveTime::from_hms_opt(12, 29, 0).unwrap(); + let state: TcpClientState<1, 2048, 2048> = TcpClientState::new(); + let tcp = TcpClient::new(stack, &state); + let dns = DnsSocket::new(stack); + let mut read_buffer = [0; 20_000]; + let mut write_buffer = [0; 20_000]; + let tls = TlsConfig::new( + const_random::const_random!(u64), + &mut read_buffer, + &mut write_buffer, + TlsVerify::None, + ); + let mut client = HttpClient::new_with_tls(&tcp, &dns, tls); + + let time = { + let mut req = client + .request( + Method::GET, + "https://worldtimeapi.org/api/timezone/America/New_York", + ) + .await + .expect("request to be created"); + + let mut headers = [0; 2048]; + let response = req.send(&mut headers).await.expect("request to succeed"); + + let body = response.body().read_to_end().await.expect("body to read"); + + #[derive(Debug, serde::Deserialize)] + struct TimeResponse { + unixtime: i64, + } + + let time: TimeResponse = serde_json::from_slice(body).expect("parse success"); + DateTime::from_timestamp(time.unixtime, 0) + .expect("valid time") + .with_timezone(&FixedOffset::west_opt(4 * 3600).unwrap()) + .naive_local() + }; + + rtc.set_current_time(time); loop { - let colors = - LightningTime::from(time + Duration::milliseconds(rtc.get_time_ms() as i64)).colors(); + let colors = LightningTime::from(rtc.current_time().time()).colors(); leds.set_color(colors.bolt, Block::BottomLeft).await; @@ -54,54 +116,65 @@ async fn amain(mut leds: Leds, rtc: Rtc<'static>) { leds.set_color(colors.spark, block).await; } + leds.swap().await; + Timer::after_millis(100).await; } } static mut APP_CORE_STACK: Stack<8192> = Stack::new(); +static STACK: StaticCell>> = StaticCell::new(); +static RESOURCES: StaticCell> = StaticCell::new(); #[entry] fn main() -> ! { - init_heap(); - - let peripherals = Peripherals::take(); + let peripherals = unsafe { Peripherals::steal() }; let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); - let system = SystemControl::new(peripherals.SYSTEM); - let sw_ints = system.software_interrupt_control; + let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); - let clocks = ClockControl::max(system.clock_control).freeze(); - let timg0 = TimerGroup::new(peripherals.TIMG1, &clocks); - esp_hal_embassy::init(&clocks, timg0.timer0); + let timg0 = TimerGroup::new(peripherals.TIMG1); + esp_hal_embassy::init(timg0.timer0); - // let _init = esp_wifi::initialize( - // esp_wifi::EspWifiInitFor::Wifi, - // timg0.timer1, - // esp_hal::rng::Rng::new(peripherals.RNG), - // peripherals.RADIO_CLK, - // &clocks, - // ) - // .unwrap(); + let init = esp_wifi::initialize( + esp_wifi::EspWifiInitFor::Wifi, + timg0.timer1, + esp_hal::rng::Rng::new(peripherals.RNG), + peripherals.RADIO_CLK, + ) + .unwrap(); + + let (wifi_interface, controller) = + esp_wifi::wifi::new_with_mode(&init, peripherals.WIFI, WifiStaDevice).unwrap(); + + let config = embassy_net::Config::dhcpv4(Default::default()); + + let stack = &*STACK.init(embassy_net::Stack::new( + wifi_interface, + config, + RESOURCES.init(embassy_net::StackResources::new()), + const_random::const_random!(u64), + )); let mut cpu_control = CpuControl::new(peripherals.CPU_CTRL); let leds = [ - AnyOutput::new(io.pins.gpio1, Level::Low), - AnyOutput::new(io.pins.gpio2, Level::Low), - AnyOutput::new(io.pins.gpio4, Level::Low), - AnyOutput::new(io.pins.gpio5, Level::Low), - AnyOutput::new(io.pins.gpio6, Level::Low), - AnyOutput::new(io.pins.gpio7, Level::Low), - AnyOutput::new(io.pins.gpio8, Level::Low), - AnyOutput::new(io.pins.gpio9, Level::Low), - AnyOutput::new(io.pins.gpio10, Level::Low), - AnyOutput::new(io.pins.gpio11, Level::Low), - AnyOutput::new(io.pins.gpio12, Level::Low), - AnyOutput::new(io.pins.gpio13, Level::Low), - AnyOutput::new(io.pins.gpio14, Level::Low), - AnyOutput::new(io.pins.gpio15, Level::Low), - AnyOutput::new(io.pins.gpio16, Level::Low), + Output::new(io.pins.gpio1.degrade(), Level::Low), + Output::new(io.pins.gpio2.degrade(), Level::Low), + Output::new(io.pins.gpio4.degrade(), Level::Low), + Output::new(io.pins.gpio5.degrade(), Level::Low), + Output::new(io.pins.gpio6.degrade(), Level::Low), + Output::new(io.pins.gpio7.degrade(), Level::Low), + Output::new(io.pins.gpio8.degrade(), Level::Low), + Output::new(io.pins.gpio9.degrade(), Level::Low), + Output::new(io.pins.gpio10.degrade(), Level::Low), + Output::new(io.pins.gpio11.degrade(), Level::Low), + Output::new(io.pins.gpio12.degrade(), Level::Low), + Output::new(io.pins.gpio13.degrade(), Level::Low), + Output::new(io.pins.gpio14.degrade(), Level::Low), + Output::new(io.pins.gpio17.degrade(), Level::Low), + Output::new(io.pins.gpio18.degrade(), Level::Low), ]; static EXECUTOR_CORE_1: StaticCell> = StaticCell::new(); @@ -110,7 +183,7 @@ fn main() -> ! { let _guard = cpu_control .start_app_core(unsafe { &mut *addr_of_mut!(APP_CORE_STACK) }, move || { - let spawner = executor_core1.start(Priority::Priority1); + let spawner = executor_core1.start(Priority::max()); spawner.spawn(leds_software_pwm(leds)).ok(); @@ -119,17 +192,58 @@ fn main() -> ! { }) .unwrap(); - static EXECUTOR_CORE_0: StaticCell> = StaticCell::new(); - let executor_core0 = InterruptExecutor::new(sw_ints.software_interrupt0); - let executor_core0 = EXECUTOR_CORE_0.init(executor_core0); - - let spawner = executor_core0.start(Priority::Priority1); - let leds = Leds::create(); let rtc = Rtc::new(peripherals.LPWR); - spawner.spawn(amain(leds, rtc)).ok(); + static EXECUTOR_CORE_0: StaticCell = StaticCell::new(); + let executor_core0 = Executor::new(); + let executor_core0 = EXECUTOR_CORE_0.init(executor_core0); + executor_core0.run(|spawner| { + spawner.spawn(connection(controller)).ok(); + spawner.spawn(net_task(stack)).ok(); + spawner.spawn(amain(leds, rtc, stack)).ok(); + }); +} - // Just loop to show that the main thread does not need to poll the executor. - loop {} +#[embassy_executor::task] +async fn net_task(stack: &'static NetStack>) { + stack.run().await +} + +#[embassy_executor::task] +async fn connection(mut controller: WifiController<'static>) { + info!("start connection task"); + debug!("Device capabilities: {:?}", controller.get_capabilities()); + loop { + if matches!(esp_wifi::wifi::get_wifi_state(), WifiState::StaConnected) { + // wait until we're no longer connected + controller.wait_for_event(WifiEvent::StaDisconnected).await; + Timer::after_millis(5000).await + } + if !matches!(controller.is_started(), Ok(true)) { + // Assume we don't need any certs + let client_config = Configuration::EapClient(EapClientConfiguration { + ssid: heapless::String::from_str(dotenv!("WIFI_SSID")).unwrap(), + auth_method: esp_wifi::wifi::AuthMethod::WPA2Enterprise, + identity: Some(heapless::String::from_str(dotenv!("WIFI_USERNAME")).unwrap()), + username: Some(heapless::String::from_str(dotenv!("WIFI_USERNAME")).unwrap()), + password: Some(heapless::String::from_str(dotenv!("WIFI_PASSWORD")).unwrap()), + ttls_phase2_method: Some(TtlsPhase2Method::Mschapv2), + ..Default::default() + }); + controller.set_configuration(&client_config).unwrap(); + info!("Starting wifi"); + controller.start().await.unwrap(); + info!("Wifi started!"); + } + info!("About to connect..."); + + match controller.connect().await { + Ok(_) => info!("Wifi connected!"), + Err(e) => { + error!("Failed to connect to wifi: {e:?}"); + Timer::after_millis(5000).await + } + } + } }