Initial version
This commit is contained in:
commit
d0efb48941
8
.cargo/config.toml
Normal file
8
.cargo/config.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
runner = "./wrapper.sh"
|
||||
|
||||
[build]
|
||||
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "debug"
|
2
.embed.toml
Normal file
2
.embed.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[default.general]
|
||||
chip = "rp2040"
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
.env
|
2284
Cargo.lock
generated
Normal file
2284
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
109
Cargo.toml
Normal file
109
Cargo.toml
Normal file
|
@ -0,0 +1,109 @@
|
|||
[package]
|
||||
name = "air_filter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = { version = "0.7", features = ["inline-asm"] }
|
||||
cortex-m-rt = "0.7"
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.4"
|
||||
embassy-embedded-hal = { version = "0.1.0", features = ["nightly", "defmt"] }
|
||||
embassy-executor = { version = "0.3", features = [
|
||||
"arch-cortex-m",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
"defmt",
|
||||
"nightly",
|
||||
"integrated-timers",
|
||||
] }
|
||||
embassy-rp = { version = "0.1", features = [
|
||||
"defmt",
|
||||
"unstable-traits",
|
||||
"nightly",
|
||||
"unstable-pac",
|
||||
"time-driver",
|
||||
"critical-section-impl",
|
||||
] }
|
||||
embassy-boot-rp = { version = "0.1", features = ["nightly", "defmt"] }
|
||||
embassy-boot = { version = "0.1", features = ["nightly", "defmt"] }
|
||||
embassy-time = { version = "0.1", features = [
|
||||
"defmt",
|
||||
"unstable-traits",
|
||||
"defmt-timestamp-uptime",
|
||||
"nightly",
|
||||
] }
|
||||
embassy-net = { version = "0.1", features = [
|
||||
"tcp",
|
||||
"dhcpv4",
|
||||
"nightly",
|
||||
"medium-ethernet",
|
||||
"defmt",
|
||||
"dns",
|
||||
] }
|
||||
embassy-sync = { version = "0.2", features = ["defmt"] }
|
||||
embassy-futures = { version = "0.1", features = ["defmt"] }
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"] }
|
||||
cfg-if = "1.0.0"
|
||||
static_cell = { version = "1.1", features = ["nightly"] }
|
||||
cyw43 = { git = "https://github.com/embassy-rs/embassy", features = [
|
||||
"defmt",
|
||||
"firmware-logs",
|
||||
] }
|
||||
cyw43-pio = { git = "https://github.com/embassy-rs/embassy", features = [
|
||||
"defmt",
|
||||
] }
|
||||
rand = { version = "0.8.5", features = [
|
||||
"nightly",
|
||||
"small_rng",
|
||||
"std_rng",
|
||||
], default-features = false }
|
||||
rust-mqtt = { version = "0.1.5", features = [
|
||||
"defmt",
|
||||
"no_std",
|
||||
"tls",
|
||||
], default-features = false }
|
||||
const_format = "0.2.31"
|
||||
git-version = "0.3.5"
|
||||
serde = { version = "1.0.188", default-features = false, features = ["derive"] }
|
||||
heapless = { version = "0.7.16", features = ["defmt", "serde"] }
|
||||
serde-json-core = "0.5.1"
|
||||
nourl = { version = "0.1.1", features = ["defmt"] }
|
||||
# Embassy harfcodes a max of 6 dns servers, if there are more it crashes. This is a workaround
|
||||
# Ideally embassy returns an error instead of crashing...
|
||||
# Interestingly though, I only get 2 DNS servers...
|
||||
smoltcp = { version = "0.10.0", default-features = false, features = [
|
||||
"dns-max-server-count-4",
|
||||
] }
|
||||
updater = { version = "0.1.0", path = "../iot_tools/updater" }
|
||||
|
||||
[patch.crates-io]
|
||||
embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-executor = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-rp = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-time = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-net = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-sync = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-futures = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-boot-rp = { git = "https://github.com/embassy-rs/embassy" }
|
||||
embassy-boot = { git = "https://github.com/embassy-rs/embassy" }
|
||||
|
||||
# Updated to embedded-io 0.5.0
|
||||
rust-mqtt = { git = "https://git.huizinga.dev/Dreaded_X/rust-mqtt" }
|
||||
# Make mqtt:// and mqtts:// actually work
|
||||
nourl = { git = "https://git.huizinga.dev/Dreaded_X/nourl" }
|
||||
|
||||
# Waiting for this to get updated to embedded-io 0.5 properly
|
||||
reqwless = { path = "../reqwless" }
|
||||
|
||||
[features]
|
||||
include_firmwares = []
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
opt-level = 's'
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[build-dependencies]
|
||||
dotenvy = "0.15.7"
|
42
build.rs
Normal file
42
build.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
|
||||
// By default cortex-m-rt expects memory.x, however this causes issues with workspaces as it
|
||||
// will pick the first file that is found.
|
||||
// In order to get around this we make a dummy memory.x file
|
||||
File::create(out.join("memory.x")).unwrap();
|
||||
|
||||
// Use memory.x.in as a template for the actual memory.x
|
||||
let memory = include_str!("memory.x.in")
|
||||
.replace("{BOOTLOADER}", "BOOTLOADER")
|
||||
.replace("{ACTIVE}", "FLASH");
|
||||
|
||||
// And then include it with a unique name
|
||||
File::create(out.join("memory_app.x"))
|
||||
.unwrap()
|
||||
.write_all(memory.as_bytes())
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rerun-if-changed=memory.x.in");
|
||||
|
||||
// And link with that one
|
||||
println!("cargo:rustc-link-arg-bins=-Tmemory_app.x");
|
||||
|
||||
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||
|
||||
if let Ok(dotenv_path) = dotenvy::dotenv() {
|
||||
println!("cargo:rerun-if-changed={}", dotenv_path.display());
|
||||
|
||||
for env_var in dotenvy::dotenv_iter().unwrap() {
|
||||
let (key, value) = env_var.unwrap();
|
||||
println!("cargo:rustc-env={key}={value}");
|
||||
}
|
||||
}
|
||||
}
|
BIN
firmware/43439A0.bin
Normal file
BIN
firmware/43439A0.bin
Normal file
Binary file not shown.
BIN
firmware/43439A0_clm.bin
Normal file
BIN
firmware/43439A0_clm.bin
Normal file
Binary file not shown.
49
firmware/LICENSE-permissive-binary-license-1.0.txt
Normal file
49
firmware/LICENSE-permissive-binary-license-1.0.txt
Normal file
|
@ -0,0 +1,49 @@
|
|||
Permissive Binary License
|
||||
|
||||
Version 1.0, July 2019
|
||||
|
||||
Redistribution. Redistribution and use in binary form, without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1) Redistributions must reproduce the above copyright notice and the
|
||||
following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
2) Unless to the extent explicitly permitted by law, no reverse
|
||||
engineering, decompilation, or disassembly of this software is
|
||||
permitted.
|
||||
|
||||
3) Redistribution as part of a software development kit must include the
|
||||
accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in
|
||||
that file.
|
||||
|
||||
4) Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
Limited patent license. The copyright holders (and contributors) grant a
|
||||
worldwide, non-exclusive, no-charge, royalty-free patent license to
|
||||
make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer this software, where such license applies only to those patent
|
||||
claims licensable by the copyright holders (and contributors) that are
|
||||
necessarily infringed by this software. This patent license shall not
|
||||
apply to any combinations that include this software. No hardware is
|
||||
licensed hereunder.
|
||||
|
||||
If you institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the software
|
||||
itself infringes your patent(s), then your rights granted under this
|
||||
license shall terminate as of the date such litigation is filed.
|
||||
|
||||
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
||||
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
9
firmware/README.md
Normal file
9
firmware/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# WiFi firmware
|
||||
|
||||
Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439
|
||||
|
||||
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
|
||||
|
||||
## Changelog
|
||||
|
||||
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62
|
22
memory.x.in
Normal file
22
memory.x.in
Normal file
|
@ -0,0 +1,22 @@
|
|||
MEMORY {
|
||||
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||
{BOOTLOADER} : ORIGIN = 0x10000100, LENGTH = 24k - 0x100
|
||||
BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4k
|
||||
{ACTIVE} : ORIGIN = 0x10007000, LENGTH = 876k
|
||||
DFU : ORIGIN = 0x100E2000, LENGTH = 880k
|
||||
FW : ORIGIN = 0x101BE000, LENGTH = 256k
|
||||
CLM : ORIGIN = 0x101FE000, LENGTH = 8k
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 264K
|
||||
}
|
||||
|
||||
__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2);
|
||||
__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2);
|
||||
|
||||
__bootloader_active_start = ORIGIN({ACTIVE}) - ORIGIN(BOOT2);
|
||||
__bootloader_active_end = ORIGIN({ACTIVE}) + LENGTH({ACTIVE}) - ORIGIN(BOOT2);
|
||||
|
||||
__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2);
|
||||
__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2);
|
||||
|
||||
__fw_start = ORIGIN(FW);
|
||||
__clm_start = ORIGIN(CLM);
|
9
release.sh
Executable file
9
release.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
mkdir -p target/firmware
|
||||
cargo objcopy --release --features=include_firmwares -- -O binary target/firmware/firmware
|
||||
shasum -a 512 -b target/firmware/firmware | dd ibs=128 count=1 | xxd -p -r > target/firmware/checksum
|
||||
signify -S -m target/firmware/checksum -s ~/Projects/crypt/R0/private/keys/firmware/airfilter.sec -x target/firmware/checksum.sig
|
||||
tail -n1 target/firmware/checksum.sig | base64 -d -i | dd ibs=10 skip=1 > target/firmware/signed
|
||||
cat target/firmware/signed > target/firmware/firmware+signed
|
||||
cat target/firmware/firmware >> target/firmware/firmware+signed
|
3
rust-toolchain.toml
Normal file
3
rust-toolchain.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
targets = ["thumbv6m-none-eabi"]
|
470
src/main.rs
Normal file
470
src/main.rs
Normal file
|
@ -0,0 +1,470 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use core::{cell::RefCell, str::from_utf8};
|
||||
|
||||
use const_format::formatcp;
|
||||
use cyw43::PowerManagementMode;
|
||||
use cyw43_pio::PioSpi;
|
||||
use defmt::{debug, error, info, warn, Display2Format, Format};
|
||||
use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::{
|
||||
select::{select3, select4, Either3},
|
||||
yield_now,
|
||||
};
|
||||
use embassy_net::{dns::DnsQueryType, tcp::TcpSocket, Config, Stack, StackResources};
|
||||
use embassy_rp::{
|
||||
bind_interrupts,
|
||||
clocks::RoscRng,
|
||||
flash::{Flash, WRITE_SIZE},
|
||||
gpio::{Flex, Input, Level, Output, Pin, Pull},
|
||||
peripherals::{DMA_CH1, PIN_23, PIN_25, PIO0},
|
||||
pio::{self, Pio},
|
||||
Peripheral,
|
||||
};
|
||||
use embassy_sync::blocking_mutex::Mutex;
|
||||
use embassy_time::{Duration, Ticker, Timer};
|
||||
use heapless::Vec;
|
||||
use nourl::Url;
|
||||
use rand::{
|
||||
rngs::{SmallRng, StdRng},
|
||||
RngCore, SeedableRng,
|
||||
};
|
||||
use rust_mqtt::{
|
||||
client::{
|
||||
client::MqttClient,
|
||||
client_config::{ClientConfig, MqttVersion},
|
||||
},
|
||||
packet::v5::publish_packet::QualityOfService,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use static_cell::make_static;
|
||||
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
|
||||
});
|
||||
|
||||
const ID: &str = env!("ID");
|
||||
const TOPIC_BASE: &str = formatcp!("pico/{}", ID);
|
||||
const TOPIC_STATUS: &str = formatcp!("{}/status", TOPIC_BASE);
|
||||
const TOPIC_UPDATE: &str = formatcp!("{}/update", TOPIC_BASE);
|
||||
const TOPIC_SET: &str = formatcp!("{}/set", TOPIC_BASE);
|
||||
const VERSION: &str = git_version::git_version!();
|
||||
const PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("../key.pub");
|
||||
const FLASH_SIZE: usize = 2 * 1024 * 1024;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SetMessage {
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StateMessage {
|
||||
state: State,
|
||||
manual: bool,
|
||||
}
|
||||
|
||||
impl StateMessage {
|
||||
pub fn new((state, manual): (State, bool)) -> Self {
|
||||
Self { state, manual }
|
||||
}
|
||||
|
||||
pub fn vec(&self) -> Vec<u8, 64> {
|
||||
serde_json_core::to_vec(self)
|
||||
.expect("The buffer should be large enough to contain all the data")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Format, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum State {
|
||||
Off,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
impl SetMessage {
|
||||
fn get_state(&self) -> State {
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
struct Controller<'a, O: Pin, L: Pin, M: Pin, H: Pin> {
|
||||
off: Input<'a, O>,
|
||||
low: Flex<'a, L>,
|
||||
medium: Flex<'a, M>,
|
||||
high: Flex<'a, H>,
|
||||
}
|
||||
|
||||
impl<'a, O: Pin, L: Pin, M: Pin, H: Pin> Controller<'a, O, L, M, H> {
|
||||
pub fn new(
|
||||
off: impl Peripheral<P = O> + 'a,
|
||||
low: impl Peripheral<P = L> + 'a,
|
||||
medium: impl Peripheral<P = M> + 'a,
|
||||
high: impl Peripheral<P = H> + 'a,
|
||||
) -> Self {
|
||||
let off = Input::new(off, Pull::None);
|
||||
|
||||
let mut low = Flex::new(low);
|
||||
low.set_low();
|
||||
low.set_as_input();
|
||||
|
||||
let mut medium = Flex::new(medium);
|
||||
medium.set_low();
|
||||
medium.set_as_input();
|
||||
|
||||
let mut high = Flex::new(high);
|
||||
high.set_low();
|
||||
high.set_as_input();
|
||||
|
||||
Self {
|
||||
off,
|
||||
low,
|
||||
medium,
|
||||
high,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_state(&mut self) -> (State, bool) {
|
||||
let manual = self.off.is_high();
|
||||
let state = match (self.low.is_low(), self.medium.is_low(), self.high.is_low()) {
|
||||
(false, false, false) => State::Off,
|
||||
(true, false, false) => State::Low,
|
||||
(false, true, false) => State::Medium,
|
||||
(false, false, true) => State::High,
|
||||
(a, b, c) => {
|
||||
// This happens if the user turns the knob, in this case we should turn off remote
|
||||
// control
|
||||
debug!("Unknown state: ({}, {}, {})", a, b, c);
|
||||
self.set_state(State::Off);
|
||||
State::Off
|
||||
}
|
||||
};
|
||||
|
||||
(state, manual)
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: State) {
|
||||
let manual = self.off.is_high();
|
||||
|
||||
if manual && state != State::Off {
|
||||
warn!("Filter is manual controlled, cannot control remotely");
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("Setting state: {}", state);
|
||||
|
||||
match state {
|
||||
State::Off => {
|
||||
self.low.set_as_input();
|
||||
self.medium.set_as_input();
|
||||
self.high.set_as_input();
|
||||
}
|
||||
State::Low => {
|
||||
self.low.set_as_output();
|
||||
self.low.set_drive_strength(embassy_rp::gpio::Drive::_12mA);
|
||||
self.medium.set_as_input();
|
||||
self.high.set_as_input();
|
||||
}
|
||||
State::Medium => {
|
||||
self.low.set_as_input();
|
||||
self.medium.set_as_output();
|
||||
self.medium
|
||||
.set_drive_strength(embassy_rp::gpio::Drive::_12mA);
|
||||
self.high.set_as_input();
|
||||
}
|
||||
State::High => {
|
||||
self.low.set_as_input();
|
||||
self.medium.set_as_input();
|
||||
self.high.set_as_output();
|
||||
self.high.set_drive_strength(embassy_rp::gpio::Drive::_12mA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn watch(&mut self) -> (State, bool) {
|
||||
// Wait for change on any of the pins
|
||||
select4(
|
||||
self.off.wait_for_any_edge(),
|
||||
self.low.wait_for_any_edge(),
|
||||
self.medium.wait_for_any_edge(),
|
||||
self.high.wait_for_any_edge(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Give it some time to stabilze
|
||||
Timer::after(Duration::from_millis(500)).await;
|
||||
|
||||
if self.off.is_high() {
|
||||
// If the filter is in manual mode, set the pico outputs to off
|
||||
self.set_state(State::Off);
|
||||
}
|
||||
|
||||
// Get the current state
|
||||
self.get_state()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the cyw43 firmware blobs
|
||||
///
|
||||
/// # Safety
|
||||
/// When building without `include_firmwares` make sure to flash the firmwares using the following
|
||||
/// commands:
|
||||
/// ```bash
|
||||
/// probe-rs download firmware/43439A0.bin --format bin --chip RP2040 --base-address 0x101BE000
|
||||
/// probe-rs download firmware/43439A0_clm.bin --format bin --chip RP2040 --base-address 0x101FE000
|
||||
/// ```
|
||||
unsafe fn get_firmware() -> (&'static [u8], &'static [u8]) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "include_firmwares")] {
|
||||
let fw = include_bytes!("../firmware/43439A0.bin");
|
||||
let clm = include_bytes!("../firmware/43439A0_clm.bin");
|
||||
|
||||
(fw, clm)
|
||||
} else {
|
||||
// TODO: It would be nice if it could automatically get the correct size
|
||||
extern "C" {
|
||||
#[link_name = "__fw_start"]
|
||||
static fw: [u8; 230321];
|
||||
#[link_name = "__clm_start"]
|
||||
static clm: [u8; 4752];
|
||||
}
|
||||
|
||||
(&fw, &clm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_config(
|
||||
stack: &'static Stack<cyw43::NetDriver<'static>>,
|
||||
) -> embassy_net::StaticConfigV4 {
|
||||
loop {
|
||||
// We are essentially busy looping here since there is no Async API for this
|
||||
if let Some(config) = stack.config_v4() {
|
||||
return config;
|
||||
}
|
||||
|
||||
yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn wifi_task(
|
||||
runner: cyw43::Runner<
|
||||
'static,
|
||||
Output<'static, PIN_23>,
|
||||
PioSpi<'static, PIN_25, PIO0, 0, DMA_CH1>,
|
||||
>,
|
||||
) -> ! {
|
||||
runner.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn net_task(stack: &'static Stack<cyw43::NetDriver<'static>>) -> ! {
|
||||
stack.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
info!("Starting...");
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
// TODO: Ideally we use async flash
|
||||
// This has issues with alignment right now
|
||||
let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH);
|
||||
let flash = Mutex::new(RefCell::new(flash));
|
||||
|
||||
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
|
||||
let mut aligned = AlignedBuffer([0; WRITE_SIZE]);
|
||||
let updater = BlockingFirmwareUpdater::new(config, &mut aligned.0);
|
||||
|
||||
let mut updater = updater::Updater::new(updater, TOPIC_STATUS, VERSION, PUBLIC_SIGNING_KEY);
|
||||
|
||||
let mut controller = Controller::new(p.PIN_28, p.PIN_27, p.PIN_26, p.PIN_22);
|
||||
|
||||
let pwr = Output::new(p.PIN_23, Level::Low);
|
||||
let cs = Output::new(p.PIN_25, Level::High);
|
||||
|
||||
let mut pio = Pio::new(p.PIO0, Irqs);
|
||||
let spi = PioSpi::new(
|
||||
&mut pio.common,
|
||||
pio.sm0,
|
||||
pio.irq0,
|
||||
cs,
|
||||
p.PIN_24,
|
||||
p.PIN_29,
|
||||
p.DMA_CH1,
|
||||
);
|
||||
|
||||
let (fw, clm) = unsafe { get_firmware() };
|
||||
|
||||
let state = make_static!(cyw43::State::new());
|
||||
let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await;
|
||||
spawner.spawn(wifi_task(runner)).unwrap();
|
||||
|
||||
control.init(clm).await;
|
||||
control
|
||||
.set_power_management(PowerManagementMode::PowerSave)
|
||||
.await;
|
||||
|
||||
// Turn LED on while trying to connect
|
||||
control.gpio_set(0, true).await;
|
||||
|
||||
let config = Config::dhcpv4(Default::default());
|
||||
|
||||
// Use the Ring Oscillator of the RP2040 as a source of true randomness to seed the
|
||||
// cryptographically secure PRNG
|
||||
let mut rng = StdRng::from_rng(&mut RoscRng).unwrap();
|
||||
|
||||
let stack = make_static!(Stack::new(
|
||||
net_device,
|
||||
config,
|
||||
make_static!(StackResources::<6>::new()),
|
||||
rng.next_u64(),
|
||||
));
|
||||
|
||||
spawner.spawn(net_task(stack)).unwrap();
|
||||
|
||||
// Connect to wifi
|
||||
loop {
|
||||
match control
|
||||
.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD"))
|
||||
.await
|
||||
{
|
||||
Ok(_) => break,
|
||||
Err(err) => {
|
||||
info!("Failed to join with status = {}", err.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Waiting for DHCP...");
|
||||
let cfg = wait_for_config(stack).await;
|
||||
info!("IP Address: {}", cfg.address.address());
|
||||
|
||||
let mut rx_buffer = [0; 1024];
|
||||
let mut tx_buffer = [0; 1024];
|
||||
|
||||
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
|
||||
// socket.set_timeout(Some(Duration::from_secs(10)));
|
||||
let url = Url::parse(env!("MQTT_ADDRESS")).unwrap();
|
||||
debug!("MQTT URL: {}", url);
|
||||
let ip = stack.dns_query(url.host(), DnsQueryType::A).await.unwrap()[0];
|
||||
let addr = (ip, url.port_or_default());
|
||||
debug!("MQTT ADDR: {}", addr);
|
||||
|
||||
while let Err(e) = socket.connect(addr).await {
|
||||
warn!("Connect error: {:?}", e);
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
}
|
||||
info!("TCP Connected!");
|
||||
|
||||
let mut config = ClientConfig::new(
|
||||
MqttVersion::MQTTv5,
|
||||
// Use fast and simple PRNG to generate packet identifiers, there is no need for this to be
|
||||
// cryptographically secure
|
||||
SmallRng::from_rng(&mut RoscRng).unwrap(),
|
||||
);
|
||||
|
||||
config.add_username(env!("MQTT_USERNAME"));
|
||||
config.add_password(env!("MQTT_PASSWORD"));
|
||||
config.add_max_subscribe_qos(QualityOfService::QoS1);
|
||||
config.add_client_id(ID);
|
||||
updater.add_will(&mut config);
|
||||
|
||||
let mut recv_buffer = [0; 1024];
|
||||
let mut write_buffer = [0; 1024];
|
||||
|
||||
let mut client =
|
||||
MqttClient::<_, 5, _>::new(socket, &mut write_buffer, &mut recv_buffer, config);
|
||||
|
||||
info!("Connecting to MQTT...");
|
||||
client.connect_to_broker().await.unwrap();
|
||||
info!("MQTT Connected!");
|
||||
|
||||
// We wait with marking as booted until everything is connected
|
||||
client.subscribe_to_topic(TOPIC_UPDATE).await.unwrap();
|
||||
client.subscribe_to_topic(TOPIC_SET).await.unwrap();
|
||||
updater.ready(&mut client).await.unwrap();
|
||||
|
||||
// Turn LED off when connected
|
||||
control.gpio_set(0, false).await;
|
||||
|
||||
let mut keep_alive = Ticker::every(Duration::from_secs(30));
|
||||
loop {
|
||||
let message = match select3(
|
||||
keep_alive.next(),
|
||||
client.receive_message(),
|
||||
controller.watch(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Either3::First(_) => {
|
||||
client.send_ping().await.unwrap();
|
||||
None
|
||||
}
|
||||
Either3::Second(message) => match message {
|
||||
Ok((TOPIC_UPDATE, url)) => {
|
||||
let url: Vec<_, 256> = match Vec::from_slice(url) {
|
||||
Ok(url) => url,
|
||||
Err(_) => {
|
||||
error!("URL is longer then buffer size");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let url = match from_utf8(&url) {
|
||||
Ok(url) => url,
|
||||
Err(err) => {
|
||||
error!("Url is not valid utf-8 string: {}", Display2Format(&err));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let url = match Url::parse(url) {
|
||||
Ok(url) => url,
|
||||
Err(err) => {
|
||||
error!("Failed to parse url: {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = updater.update(url, stack, &mut rng, &mut client).await {
|
||||
error!("Update failed: {}", err);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
Ok((TOPIC_SET, message)) => {
|
||||
let message: SetMessage = match serde_json_core::from_slice(message) {
|
||||
Ok((message, _)) => message,
|
||||
Err(_) => {
|
||||
error!("Unable to parse set message");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
controller.set_state(message.get_state());
|
||||
|
||||
Some(StateMessage::new(controller.get_state()))
|
||||
}
|
||||
Ok(_) => None,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
Either3::Third(state) => Some(StateMessage::new(state)),
|
||||
};
|
||||
|
||||
if let Some(message) = message {
|
||||
client
|
||||
.send_message(TOPIC_BASE, &message.vec(), QualityOfService::QoS1, true)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
3
wrapper.sh
Executable file
3
wrapper.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
probe-run --chip RP2040 --log-format="{L} {s}
|
||||
└─ [{t}] {m} @ {F}:{l}" $@
|
Loading…
Reference in New Issue
Block a user