initial commit

This commit is contained in:
Sebastian Hugentobler 2024-11-17 10:27:30 +01:00
commit 17a8e573c0
Signed by: shu
GPG key ID: BB32CF3CA052C2F0
40 changed files with 4009 additions and 0 deletions

View file

@ -0,0 +1,78 @@
use defmt::{error, info};
use embassy_rp::usb::{Driver, Instance};
use embassy_usb::{class::cdc_acm::CdcAcmClass, driver::EndpointError};
use serial_comm::{
cobs::SENTINEL,
protocol::{cmd::Cobs, error::ErrorCommand, ok::OkCommand},
};
use crate::{
cmd_handlers::{handle_cmd, PACKET_SIZE},
flash::Config,
usb_serial::DeviceClass,
};
pub struct Disconnected;
impl From<EndpointError> for Disconnected {
fn from(val: EndpointError) -> Self {
match val {
EndpointError::BufferOverflow => panic!("Buffer overflow"),
EndpointError::Disabled => Disconnected {},
}
}
}
#[embassy_executor::task]
pub async fn cmd_task(class: &'static mut DeviceClass, config: &'static mut Config<'static>) -> ! {
loop {
class.wait_connection().await;
info!("Connected");
let _ = handle_bytes(class, config).await;
info!("Disconnected");
}
}
/// Handle incoming bytes over the usb-serial connection.
///
/// Always buffer data until you encounter the `SENTINEL` value. After that try to decode and
/// execute the received command.
pub async fn handle_bytes<'d, T: Instance + 'd>(
class: &mut CdcAcmClass<'d, Driver<'d, T>>,
config: &mut Config<'d>,
) -> Result<(), Disconnected> {
let mut cmd_buffer: [u8; PACKET_SIZE] = [0; PACKET_SIZE];
let mut cursor: usize = 0;
loop {
let mut read_buf: [u8; 64] = [0; 64];
let n = class.read_packet(&mut read_buf).await?;
let data = &read_buf[..n];
if n > 0 {
cmd_buffer[cursor..cursor + n].copy_from_slice(data);
cursor += n;
if data[n - 1] == SENTINEL {
match handle_cmd(cmd_buffer, cursor, config) {
Err(e) => {
error!(
"Failed to handle command: {:?} - {:?}",
e,
cmd_buffer[0..cursor]
);
let err_cmd = ErrorCommand::new("Failed to handle command");
let (length, err_cmd): (usize, [u8; 128]) = err_cmd.as_cobs();
class.write_packet(&err_cmd[0..length]).await?;
}
_ => {
let ok_cmd = OkCommand::new();
let (length, ok_cmd): (usize, [u8; 128]) = ok_cmd.as_cobs();
class.write_packet(&ok_cmd[0..length]).await?;
}
};
cursor = 0;
}
}
}
}

View file

@ -0,0 +1,72 @@
//! Handle specific commands, comming in from usb-serial.
pub mod set_config;
use defmt::Format;
use serial_comm::{cobs, protocol::set_config::SetConfig};
use crate::flash::{Config, FlashError};
#[derive(Debug)]
pub struct Utf8Error(pub core::str::Utf8Error);
impl Format for Utf8Error {
fn format(&self, f: defmt::Formatter) {
defmt::write!(f, "Utf8Error");
}
}
#[derive(Debug, Format)]
pub enum CmdError {
Utf8Error(Utf8Error),
FlashError(FlashError),
NoSuchCmd,
}
impl From<core::str::Utf8Error> for CmdError {
fn from(err: core::str::Utf8Error) -> Self {
CmdError::Utf8Error(Utf8Error(err))
}
}
impl From<FlashError> for CmdError {
fn from(err: FlashError) -> Self {
CmdError::FlashError(err)
}
}
// arbitrarily constrained, we know we don't need more for now
pub const PACKET_SIZE: usize = 256;
type CmdHandler = fn([u8; PACKET_SIZE], &mut Config) -> Result<(), CmdError>;
static CMD_HANDLERS: &[(&str, CmdHandler)] = &[(SetConfig::PREFIX, set_config::handle)];
/// Try decoding a command buffer and run it.
///
/// On failure return an ErrorCommand across the serial connection, otherwise an OkCommand.
pub fn handle_cmd(
cmd_buffer: [u8; PACKET_SIZE],
length: usize,
config: &mut Config<'_>,
) -> Result<(), CmdError> {
let (length, decoded_cmd): (usize, [u8; PACKET_SIZE]) = cobs::decode(cmd_buffer, length);
if length > 1 {
let header_length = (decoded_cmd[0] + 1) as usize;
let cmd_prefix = core::str::from_utf8(&decoded_cmd[header_length..header_length + 2])?;
let mut idx = 0;
while idx < CMD_HANDLERS.len() && CMD_HANDLERS[idx].0 != cmd_prefix {
idx += 1;
}
if idx == CMD_HANDLERS.len() {
return Err(CmdError::NoSuchCmd);
}
let handler = CMD_HANDLERS[idx].1;
handler(decoded_cmd, config)?;
}
Ok(())
}

View file

@ -0,0 +1,17 @@
use defmt::info;
use serial_comm::protocol::{cmd::Cmd, set_config::SetConfig};
use crate::{cmd_handlers::CmdError, flash::Config};
use super::PACKET_SIZE;
/// Handle a SetConfig command by writing the received key-value pair to flash storage.
pub fn handle(data: [u8; PACKET_SIZE], config: &mut Config<'_>) -> Result<(), CmdError> {
let mut cmd_buffer: [u8; PACKET_SIZE] = [0; PACKET_SIZE];
let set_config = SetConfig::from_bytes::<PACKET_SIZE>(data, &mut cmd_buffer)?;
config.write_config(set_config.key, set_config.value)?;
info!("set {} to {}", set_config.key, set_config.value);
Ok(())
}

124
controller/src/flash.rs Normal file
View file

@ -0,0 +1,124 @@
use defmt::{error, Format};
use embassy_rp::{
flash::{self, Async, Flash, ERASE_SIZE},
peripherals::{DMA_CH0, FLASH},
};
use crate::cmd_handlers::Utf8Error;
const ADDR_OFFSET: u32 = 0x100000;
const FLASH_SIZE: usize = 2 * 1024 * 1024;
pub const ENTRY_SIZE: u32 = ERASE_SIZE as u32;
const SSID_CONFIG: u32 = ADDR_OFFSET;
const SSID_PW_CONFIG: u32 = ADDR_OFFSET + ENTRY_SIZE;
const MQTT_CONFIG: u32 = ADDR_OFFSET + 2 * ENTRY_SIZE;
const CLIENT_ID_CONFIG: u32 = ADDR_OFFSET + 3 * ENTRY_SIZE;
pub const SSID_KEY: &str = "ssid";
pub const SSID_PW_KEY: &str = "ssid_pw";
pub const MQTT_KEY: &str = "mqtt";
pub const CLIENT_ID_KEY: &str = "client_id";
#[derive(Debug, Format)]
pub enum FlashError {
Internal(flash::Error),
Utf8Error(Utf8Error),
NoSuchKey,
ValueTooLong,
NoValue,
}
impl From<flash::Error> for FlashError {
fn from(err: flash::Error) -> Self {
FlashError::Internal(err)
}
}
impl From<core::str::Utf8Error> for FlashError {
fn from(err: core::str::Utf8Error) -> Self {
FlashError::Utf8Error(Utf8Error(err))
}
}
/// Write key-value data to flash storage for persistence and read it from there again.
pub struct Config<'a> {
flash: Flash<'a, FLASH, Async, FLASH_SIZE>,
}
impl<'a> Config<'a> {
/// Initialize the flash module on the RP2040.
pub fn init(flash: FLASH, dma_ch0: DMA_CH0) -> Self {
let flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(flash, dma_ch0);
Self { flash }
}
/// Get the position on flash for a configuration key.
fn get_config_cursor(&self, key: &str) -> Option<u32> {
match key {
SSID_KEY => Some(SSID_CONFIG),
SSID_PW_KEY => Some(SSID_PW_CONFIG),
MQTT_KEY => Some(MQTT_CONFIG),
CLIENT_ID_KEY => Some(CLIENT_ID_CONFIG),
_ => None,
}
}
/// Write a key-value pair to flash storage.
///
/// The key must be one of the predefined ones and the value length can not be longer than
/// `ENTRY_SIZE`.
///
/// Before writing, the whole sector must be erased, otherwise garbage writes can occur. For
/// that reason `ENTRY_SIZE` is set to the ERASE_SIZE of the RP2040 (4096 bits), otherwise we
/// would get misaligned erasures.
///
/// Each key-vaue pair is encoded as [<value_length>,<value>], value_length being one byte
/// long.
pub fn write_config(&mut self, key: &str, val: &str) -> Result<(), FlashError> {
let entry_cursor = self.get_config_cursor(key).ok_or(FlashError::NoSuchKey)?;
let val_data = val.as_bytes();
if val_data.len() > ENTRY_SIZE as usize {
error!(
"data of length {} for {} is longer than the max entry size of {}",
val_data.len(),
key,
ENTRY_SIZE
);
return Err(FlashError::ValueTooLong);
}
self.flash
.blocking_erase(entry_cursor, entry_cursor + ERASE_SIZE as u32)?;
self.flash
.blocking_write(entry_cursor, &[val_data.len() as u8])?; // value length
self.flash.blocking_write(entry_cursor + 1, val_data)?;
Ok(())
}
/// Read the value of a key from flash storage.
pub fn read_config<'b>(
&mut self,
key: &str,
buffer: &'b mut [u8; ENTRY_SIZE as usize],
) -> Result<&'b str, FlashError> {
let entry_cursor = self.get_config_cursor(key).ok_or(FlashError::NoSuchKey)?;
let mut length_buffer: [u8; 1] = [0; 1];
self.flash.blocking_read(entry_cursor, &mut length_buffer)?;
let length = length_buffer[0] as usize;
if length < 1 || length > ENTRY_SIZE as usize {
error!("there is no valid value for {}", key);
return Err(FlashError::NoValue);
}
self.flash.blocking_read(entry_cursor + 1, buffer)?;
let val = core::str::from_utf8(&buffer[0..length])?;
Ok(val)
}
}

101
controller/src/main.rs Normal file
View file

@ -0,0 +1,101 @@
//! Read the temperature at regular intervals and publish that data via MQTT:
//! Configure the microcontroller with commands sent across an usb-serial connection.
#![no_std]
#![no_main]
use core::str::FromStr;
use byte_handler::cmd_task;
use cyw43::Control;
use defmt::{error, info};
use embassy_executor::Spawner;
use embassy_net::Stack;
use embassy_time::Timer;
use flash::{Config, FlashError};
use static_cell::StaticCell;
use temperature::{temperature_task, Temperature};
use usb_serial::{init_usb, usb_task};
use wifi::connect;
use {defmt_rtt as _, panic_probe as _};
use crate::{mqtt::BUFFER_SIZE, wifi::init_wifi};
mod byte_handler;
mod cmd_handlers;
mod flash;
mod mqtt;
mod temperature;
mod usb_serial;
mod wifi;
async fn connect_wifi<'a>(
config: &mut Config<'static>,
wifi: &mut Control<'a>,
net_stack: &'a Stack<cyw43::NetDriver<'a>>,
) -> Result<(), FlashError> {
let mut ssid_buffer = [0; flash::ENTRY_SIZE as usize];
let ssid = config.read_config(flash::SSID_KEY, &mut ssid_buffer)?;
let mut ssid_pw_buffer = [0; flash::ENTRY_SIZE as usize];
let ssid_pw = config.read_config(flash::SSID_PW_KEY, &mut ssid_pw_buffer)?;
if !ssid.is_empty() {
connect(wifi, net_stack, ssid, ssid_pw).await;
}
Ok(())
}
static CONFIG: StaticCell<Config> = StaticCell::new();
static RX_BUFFER: StaticCell<[u8; BUFFER_SIZE]> = StaticCell::new();
static TX_BUFFER: StaticCell<[u8; BUFFER_SIZE]> = StaticCell::new();
static CLIENT_ID_BUFFER: StaticCell<[u8; flash::ENTRY_SIZE as usize]> = StaticCell::new();
static MQTT_IP_BUFFER: StaticCell<[u8; flash::ENTRY_SIZE as usize]> = StaticCell::new();
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
Timer::after_millis(100).await;
let config = CONFIG.init(Config::init(p.FLASH, p.DMA_CH0));
let (mut wifi, net_stack) = init_wifi(
spawner, p.PIN_23, p.PIN_24, p.PIN_25, p.PIN_29, p.PIO0, p.DMA_CH1,
)
.await;
match connect_wifi(config, &mut wifi, net_stack).await {
Ok(_) => info!("Connected to WiFi."),
Err(_) => error!("Failed to connect to WiFi"),
}
let rx_buffer = RX_BUFFER.init([0u8; BUFFER_SIZE]);
let tx_buffer = TX_BUFFER.init([0u8; BUFFER_SIZE]);
let client_id_buffer = CLIENT_ID_BUFFER.init([0u8; flash::ENTRY_SIZE as usize]);
let client_id = config
.read_config(flash::CLIENT_ID_KEY, client_id_buffer)
.unwrap_or("tomato");
let mqtt_ip_buffer = MQTT_IP_BUFFER.init([0u8; flash::ENTRY_SIZE as usize]);
let mqtt_ip = config
.read_config(flash::MQTT_KEY, mqtt_ip_buffer)
.unwrap_or("127.0.0.1");
let mqtt = mqtt::Client::new(client_id, net_stack, rx_buffer, tx_buffer);
let host_addr = embassy_net::IpAddress::from_str(mqtt_ip)
.unwrap_or(embassy_net::IpAddress::v4(127, 0, 1, 1));
let temps = Temperature::new(p.ADC, p.ADC_TEMP_SENSOR);
let (usb, class) = init_usb(p.USB);
spawner
.spawn(usb_task(usb))
.expect("failed to run usb task");
spawner
.spawn(cmd_task(class, config))
.expect("failed initializing usb command handler");
spawner
.spawn(temperature_task(client_id, host_addr, mqtt, temps))
.expect("failed starting temperature task");
}

73
controller/src/mqtt.rs Normal file
View file

@ -0,0 +1,73 @@
use defmt::Format;
use embassy_net::{
tcp::{ConnectError, Error, TcpSocket},
IpAddress, Stack,
};
use embassy_time::Duration;
use mqtt_protocol::{connect::Connect, publish::Publish};
pub const BUFFER_SIZE: usize = 4096;
const TCP_TIMEOUT_SECS: u64 = 5;
#[derive(Debug, Format)]
pub enum MqttError {
TcpError(Error),
ConnectError(ConnectError),
}
impl From<ConnectError> for MqttError {
fn from(err: ConnectError) -> Self {
MqttError::ConnectError(err)
}
}
impl From<Error> for MqttError {
fn from(err: Error) -> Self {
MqttError::TcpError(err)
}
}
/// Very simple MQTT client, capable of sending simple publish packets over unencrypted TCP/IP.
pub struct Client<'a> {
pub client_id: &'a str,
socket: TcpSocket<'a>,
}
impl<'a> Client<'a> {
/// Initialize the socket for sending MQTT data and set the socket timeout to `TCP_TIMEOUT_SECS`.
///
/// - `client_id`: Identification of this client.
/// - `net_stack`: network stack for sending data.
/// - `rx_buffer`: byte buffer for receiving data.
/// - `tx_buffer`: byte buffer for sending data.
pub fn new(
client_id: &'a str,
net_stack: &'a Stack<cyw43::NetDriver<'a>>,
rx_buffer: &'static mut [u8; BUFFER_SIZE],
tx_buffer: &'static mut [u8; BUFFER_SIZE],
) -> Self {
let mut socket = embassy_net::tcp::TcpSocket::new(net_stack, rx_buffer, tx_buffer);
socket.set_timeout(Some(Duration::from_secs(TCP_TIMEOUT_SECS)));
Self { client_id, socket }
}
//// Publish the payload `payload` to the MQTT topic `topic` on the broker running at `address`:1883.
pub async fn publish(
&mut self,
address: IpAddress,
topic: &str,
payload: &[u8],
) -> Result<(), MqttError> {
self.socket.connect((address, 1883)).await?;
let connect = Connect::new(self.client_id);
self.socket.write(&connect.data[0..connect.length]).await?;
let publish = Publish::new(topic, payload);
self.socket.write(&publish.data[0..publish.length]).await?;
self.socket.close();
Ok(())
}
}

View file

@ -0,0 +1,86 @@
use defmt::{error, info};
use embassy_net::IpAddress;
use embassy_rp::{
adc::{Adc, Async, Channel, Config, Error, InterruptHandler},
bind_interrupts,
peripherals::{ADC, ADC_TEMP_SENSOR},
};
use embassy_time::Timer;
use mqtt_protocol::publish::MAX_TOPIC_LENGTH;
use crate::mqtt;
bind_interrupts!(struct Irqs {
ADC_IRQ_FIFO => InterruptHandler;
});
#[embassy_executor::task]
pub async fn temperature_task(
client_id: &'static str,
host_addr: IpAddress,
mut mqtt: mqtt::Client<'static>,
mut temps: Temperature<'static>,
) -> ! {
const TOPIC_PREFIX: &str = "temps/";
let mut topic_buffer = [0u8; MAX_TOPIC_LENGTH - TOPIC_PREFIX.len()];
topic_buffer[..TOPIC_PREFIX.len()].copy_from_slice(TOPIC_PREFIX.as_bytes());
let client_id_bytes = client_id.as_bytes();
topic_buffer[TOPIC_PREFIX.len()..TOPIC_PREFIX.len() + client_id_bytes.len()]
.copy_from_slice(client_id_bytes);
let topic_full =
core::str::from_utf8(&topic_buffer[..TOPIC_PREFIX.len() + client_id_bytes.len()])
.unwrap_or("temps/tomato");
loop {
match temps.read().await {
Ok(temp) => {
info!("publishing temperature data...");
if let Err(e) = mqtt
.publish(host_addr, topic_full, &temp.to_le_bytes())
.await
{
error!("failed to publish temperature data: {}", e);
}
}
Err(e) => error!("failed to read temperature: {}", e),
}
Timer::after_secs(10).await;
}
}
/// Convert the raw temperature sensor reading into degrees celsius.
///
/// Taken from the [embassy examples](https://github.com/embassy-rs/embassy/blob/b6fc682117a41e8e63a9632e06da5a17f46d9ab0/examples/rp/src/bin/adc.rs#L43).
fn convert_to_celsius(raw_temp: u16) -> f32 {
// According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet
let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721;
let sign = if temp < 0.0 { -1.0 } else { 1.0 };
let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16;
(rounded_temp_x10 as f32) / 10.0
}
pub struct Temperature<'a> {
adc: Adc<'a, Async>,
ts: Channel<'a>,
}
impl<'a> Temperature<'a> {
/// Initialize the temperature sensor.
pub fn new(adc_pin: ADC, adc_temp_pin: ADC_TEMP_SENSOR) -> Self {
let adc = Adc::new(adc_pin, Irqs, Config::default());
let ts = Channel::new_temp_sensor(adc_temp_pin);
Self { adc, ts }
}
/// Read from the temperature sensor and return the value in degree celsius.
pub async fn read(&mut self) -> Result<f32, Error> {
let temp = self.adc.read(&mut self.ts).await?;
let temp = convert_to_celsius(temp);
Ok(temp)
}
}

View file

@ -0,0 +1,74 @@
use embassy_rp::{
bind_interrupts,
peripherals::USB,
usb::{Driver, InterruptHandler},
};
use embassy_usb::{
class::cdc_acm::{CdcAcmClass, State},
Builder, Config, UsbDevice,
};
use static_cell::StaticCell;
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
pub type SerialDevice = UsbDevice<'static, Driver<'static, USB>>;
pub type DeviceClass = CdcAcmClass<'static, Driver<'static, USB>>;
const DESCRIPTOR_SIZE: usize = 256;
type Descriptor = [u8; DESCRIPTOR_SIZE];
const BUFFER_SIZE: usize = 64;
type ControlBuffer = [u8; BUFFER_SIZE];
static DEVICE_DESCRIPTOR: StaticCell<Descriptor> = StaticCell::new();
static CONFIG_DESCRIPTOR: StaticCell<Descriptor> = StaticCell::new();
static BOS_DESCRIPTOR: StaticCell<Descriptor> = StaticCell::new();
static CONTROL_BUFFER: StaticCell<ControlBuffer> = StaticCell::new();
static STATE: StaticCell<State> = StaticCell::new();
static CLASS: StaticCell<DeviceClass> = StaticCell::new();
#[embassy_executor::task]
pub async fn usb_task(mut usb: SerialDevice) -> ! {
usb.run().await
}
/// Initialize the USB serial interface and return the device and CdcAcm class (Abstract Control Model).
pub fn init_usb(usb: USB) -> (SerialDevice, &'static mut DeviceClass) {
let driver = Driver::new(usb, Irqs);
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("USB-serial");
config.serial_number = Some("123456789");
config.max_power = 100;
config.max_packet_size_0 = 64;
// Required for windows 7 compatibility.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;
let device_descriptor = DEVICE_DESCRIPTOR.init([0u8; DESCRIPTOR_SIZE]);
let config_descriptor = CONFIG_DESCRIPTOR.init([0u8; DESCRIPTOR_SIZE]);
let bos_descriptor = BOS_DESCRIPTOR.init([0u8; DESCRIPTOR_SIZE]);
let control_buf = CONTROL_BUFFER.init([0u8; BUFFER_SIZE]);
let state = STATE.init(State::new());
let mut builder = Builder::new(
driver,
config,
device_descriptor,
config_descriptor,
bos_descriptor,
control_buf,
);
let class = CLASS.init(CdcAcmClass::new(&mut builder, state, 64));
let usb = builder.build();
(usb, &mut *class)
}

128
controller/src/wifi.rs Normal file
View file

@ -0,0 +1,128 @@
use cyw43::{Control, ControlError, State};
use cyw43_pio::PioSpi;
use defmt::{error, info, unwrap};
use embassy_executor::Spawner;
use embassy_net::{Config, Stack, StackResources};
use embassy_rp::{
bind_interrupts,
clocks::RoscRng,
gpio::{Level, Output},
peripherals::{DMA_CH1, PIN_23, PIN_24, PIN_25, PIN_29, PIO0},
pio::{InterruptHandler, Pio},
};
use embassy_time::Timer;
use rand::RngCore;
use static_cell::StaticCell;
const MAX_WIFI_TRIES: u8 = 3;
const MAX_DHCP_TRIES: u8 = 20;
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
#[embassy_executor::task]
async fn wifi_task(
runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH1>>,
) -> ! {
runner.run().await
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! {
stack.run().await
}
static STATE: StaticCell<State> = StaticCell::new();
static STACK: StaticCell<Stack<cyw43::NetDriver<'static>>> = StaticCell::new();
const STACK_RESOURCES_COUNT: usize = 2;
static STACK_RESOURCES: StaticCell<StackResources<STACK_RESOURCES_COUNT>> = StaticCell::new();
// Initialise the wwifi chip of the RP2040 and return the control interface as well as the network
// stack.
pub async fn init_wifi(
spawner: Spawner,
pin_23: PIN_23,
pin_24: PIN_24,
pin_25: PIN_25,
pin_29: PIN_29,
pio0: PIO0,
dma_ch1: DMA_CH1,
) -> (Control<'static>, &'static Stack<cyw43::NetDriver<'static>>) {
let fw = include_bytes!("../cyw43-firmware/43439A0.bin");
let clm = include_bytes!("../cyw43-firmware/43439A0_clm.bin");
let pwr = Output::new(pin_23, Level::Low);
let cs = Output::new(pin_25, Level::High);
let mut pio = Pio::new(pio0, Irqs);
let spi = PioSpi::new(
&mut pio.common,
pio.sm0,
pio.irq0,
cs,
pin_24,
pin_29,
dma_ch1,
);
let state = STATE.init(State::new());
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await;
unwrap!(spawner.spawn(wifi_task(runner)));
control.init(clm).await;
control
.set_power_management(cyw43::PowerManagementMode::PowerSave)
.await;
let config = Config::dhcpv4(Default::default());
let mut rng = RoscRng;
let seed = rng.next_u64();
// Init network stack
let resources = STACK_RESOURCES.init(StackResources::<STACK_RESOURCES_COUNT>::new());
let stack = &*STACK.init(Stack::new(net_device, config, resources, seed));
unwrap!(spawner.spawn(net_task(stack)));
(control, stack)
}
/// Connect to a WiFi network using WPA2 and try to get an IP from DHCP (IPv4).
pub async fn connect<'a>(
wifi: &mut Control<'a>,
net_stack: &Stack<cyw43::NetDriver<'a>>,
ssid: &str,
pw: &str,
) {
info!("trying to connect to {}...", ssid);
let mut idx = 0;
let mut status: Result<(), ControlError> = Err(ControlError { status: 0 });
while idx < MAX_WIFI_TRIES && status.is_err() {
status = wifi.join_wpa2(ssid, pw).await;
Timer::after_millis(100).await;
idx += 1;
}
if let Err(e) = status {
error!("WiFi connection failed: {}", e.status);
} else {
info!("waiting for DHCP...");
idx = 0;
while idx < MAX_DHCP_TRIES && !net_stack.is_config_up() {
Timer::after_millis(1000).await;
idx += 1;
}
if net_stack.is_config_up() {
info!("DHCP is up!");
} else {
error!("Failed to get an IP from DHCP");
}
}
}