771 lines
29 KiB
Rust
771 lines
29 KiB
Rust
use crate::config::{NetworkConfig, PlantControllerConfig};
|
|
use crate::hal::{GW_IP_ADDR_ENV, PLANT_COUNT};
|
|
use crate::log::{log, LogMessage};
|
|
use crate::STAY_ALIVE;
|
|
use anyhow::{anyhow, bail, Context};
|
|
use chrono::{DateTime, Utc};
|
|
use serde::Serialize;
|
|
|
|
use alloc::{string::String, vec::Vec};
|
|
use core::marker::PhantomData;
|
|
use core::net::{IpAddr, Ipv4Addr};
|
|
use core::str::FromStr;
|
|
use embassy_executor::{SendSpawner, Spawner};
|
|
use embassy_net::tcp::TcpSocket;
|
|
use embassy_net::{IpListenEndpoint, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4};
|
|
use embassy_time::{Duration, Instant, Timer};
|
|
use esp_bootloader_esp_idf::ota::OtaImageState;
|
|
use esp_hal::gpio::Input;
|
|
use esp_hal::rng::Rng;
|
|
use esp_println::{print, println};
|
|
use esp_storage::FlashStorage;
|
|
use esp_wifi::wifi::{
|
|
AccessPointConfiguration, Configuration, Interfaces, WifiController, WifiDevice, WifiEvent,
|
|
WifiState,
|
|
};
|
|
|
|
#[link_section = ".rtc.data"]
|
|
static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT];
|
|
#[link_section = ".rtc.data"]
|
|
static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT];
|
|
#[link_section = ".rtc.data"]
|
|
static mut LOW_VOLTAGE_DETECTED: bool = false;
|
|
#[link_section = ".rtc.data"]
|
|
static mut RESTART_TO_CONF: bool = false;
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct FileInfo {
|
|
filename: String,
|
|
size: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct FileList {
|
|
total: usize,
|
|
used: usize,
|
|
files: Vec<FileInfo>,
|
|
file_system_corrupt: Option<String>,
|
|
iter_error: Option<String>,
|
|
}
|
|
|
|
pub struct FileSystemSizeInfo {
|
|
pub total_size: usize,
|
|
pub used_size: usize,
|
|
pub free_size: usize,
|
|
}
|
|
|
|
pub struct MqttClient<'a> {
|
|
dummy: PhantomData<&'a ()>,
|
|
//mqtt_client: EspMqttClient<'a>,
|
|
base_topic: heapless::String<64>,
|
|
}
|
|
pub struct Esp<'a> {
|
|
pub rng: Rng,
|
|
//first starter (ap or sta will take these)
|
|
pub interfaces: Option<Interfaces<'static>>,
|
|
pub controller: Option<WifiController<'static>>,
|
|
|
|
//only filled, if a useable mqtt client with working roundtrip could be established
|
|
pub(crate) mqtt_client: Option<MqttClient<'a>>,
|
|
|
|
pub boot_button: Input<'a>,
|
|
pub(crate) wall_clock_offset: u64,
|
|
|
|
pub storage: FlashStorage,
|
|
pub slot: usize,
|
|
pub next_slot: usize,
|
|
pub ota_state: OtaImageState,
|
|
}
|
|
|
|
pub struct IpInfo {
|
|
pub(crate) ip: IpAddr,
|
|
netmask: IpAddr,
|
|
gateway: IpAddr,
|
|
}
|
|
|
|
struct AccessPointInfo {}
|
|
|
|
macro_rules! mk_static {
|
|
($t:ty,$val:expr) => {{
|
|
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
|
#[deny(unused_attributes)]
|
|
let x = STATIC_CELL.uninit().write(($val));
|
|
x
|
|
}};
|
|
}
|
|
|
|
static WIFI_CONTROLLER: static_cell::StaticCell<WifiController> = static_cell::StaticCell::new();
|
|
|
|
impl Esp<'_> {
|
|
const SPIFFS_PARTITION_NAME: &'static str = "storage";
|
|
const CONFIG_FILE: &'static str = "/spiffs/config.cfg";
|
|
const BASE_PATH: &'static str = "/spiffs";
|
|
|
|
pub(crate) fn mode_override_pressed(&mut self) -> bool {
|
|
self.boot_button.is_low()
|
|
}
|
|
pub(crate) async fn sntp(&mut self, _max_wait_ms: u32) -> anyhow::Result<DateTime<Utc>> {
|
|
//let sntp = sntp::EspSntp::new_default()?;
|
|
//let mut counter = 0;
|
|
//while sntp.get_sync_status() != SyncStatus::Completed {
|
|
// self.delay.delay_ms(100);
|
|
// counter += 100;
|
|
// if counter > max_wait_ms {
|
|
// bail!("Reached sntp timeout, aborting")
|
|
// }
|
|
//}
|
|
//self.time()
|
|
todo!();
|
|
}
|
|
pub(crate) fn time(&mut self) -> DateTime<Utc> {
|
|
let wall_clock = Instant::now().as_millis() + self.wall_clock_offset;
|
|
DateTime::from_timestamp_millis(wall_clock as i64).unwrap()
|
|
}
|
|
|
|
pub(crate) async fn wifi_scan(&mut self) -> anyhow::Result<Vec<AccessPointInfo>> {
|
|
bail!("todo");
|
|
// self.wifi_driver.start_scan(
|
|
// &ScanConfig {
|
|
// scan_type: ScanType::Passive(Duration::from_secs(5)),
|
|
// show_hidden: false,
|
|
// ..Default::default()
|
|
// },
|
|
// true,
|
|
// )?;
|
|
// anyhow::Ok(self.wifi_driver.get_scan_result()?)
|
|
}
|
|
|
|
pub(crate) fn last_pump_time(&self, plant: usize) -> Option<DateTime<Utc>> {
|
|
let ts = unsafe { LAST_WATERING_TIMESTAMP }[plant];
|
|
DateTime::from_timestamp_millis(ts)
|
|
}
|
|
pub(crate) fn store_last_pump_time(&mut self, plant: usize, time: DateTime<Utc>) {
|
|
unsafe {
|
|
LAST_WATERING_TIMESTAMP[plant] = time.timestamp_millis();
|
|
}
|
|
}
|
|
pub(crate) fn set_low_voltage_in_cycle(&mut self) {
|
|
unsafe {
|
|
LOW_VOLTAGE_DETECTED = true;
|
|
}
|
|
}
|
|
pub(crate) fn clear_low_voltage_in_cycle(&mut self) {
|
|
unsafe {
|
|
LOW_VOLTAGE_DETECTED = false;
|
|
}
|
|
}
|
|
|
|
pub(crate) fn low_voltage_in_cycle(&mut self) -> bool {
|
|
unsafe { LOW_VOLTAGE_DETECTED }
|
|
}
|
|
pub(crate) fn store_consecutive_pump_count(&mut self, plant: usize, count: u32) {
|
|
unsafe {
|
|
CONSECUTIVE_WATERING_PLANT[plant] = count;
|
|
}
|
|
}
|
|
pub(crate) fn consecutive_pump_count(&mut self, plant: usize) -> u32 {
|
|
unsafe { CONSECUTIVE_WATERING_PLANT[plant] }
|
|
}
|
|
pub(crate) fn get_restart_to_conf(&mut self) -> bool {
|
|
unsafe { RESTART_TO_CONF }
|
|
}
|
|
pub(crate) fn set_restart_to_conf(&mut self, to_conf: bool) {
|
|
unsafe {
|
|
RESTART_TO_CONF = to_conf;
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn wifi_ap(&mut self) -> anyhow::Result<Stack<'static>> {
|
|
let _ssid = match self.load_config() {
|
|
Ok(config) => config.network.ap_ssid.clone(),
|
|
Err(_) => heapless::String::from_str("PlantCtrl Emergency Mode").unwrap(),
|
|
};
|
|
|
|
let spawner = Spawner::for_current_executor().await;
|
|
|
|
let device = self.interfaces.take().unwrap().ap;
|
|
let gw_ip_addr_str = GW_IP_ADDR_ENV.unwrap_or("192.168.2.1");
|
|
let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip");
|
|
|
|
let config = embassy_net::Config::ipv4_static(StaticConfigV4 {
|
|
address: Ipv4Cidr::new(gw_ip_addr, 24),
|
|
gateway: Some(gw_ip_addr),
|
|
dns_servers: Default::default(),
|
|
});
|
|
|
|
let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64;
|
|
|
|
// Init network stack
|
|
let (stack, runner) = embassy_net::new(
|
|
device,
|
|
config,
|
|
mk_static!(StackResources<3>, StackResources::<3>::new()),
|
|
seed,
|
|
);
|
|
let stack = mk_static!(Stack, stack);
|
|
|
|
let controller = self.controller.take().unwrap();
|
|
spawner.spawn(connection(controller)).ok();
|
|
spawner.spawn(net_task(runner)).ok();
|
|
spawner.spawn(run_dhcp(stack.clone(), gw_ip_addr_str)).ok();
|
|
|
|
loop {
|
|
if stack.is_link_up() {
|
|
break;
|
|
}
|
|
Timer::after(Duration::from_millis(500)).await;
|
|
}
|
|
println!(
|
|
"Connect to the AP `esp-wifi` and point your browser to http://{gw_ip_addr_str}:8080/"
|
|
);
|
|
println!("DHCP is enabled so there's no need to configure a static IP, just in case:");
|
|
while !stack.is_config_up() {
|
|
Timer::after(Duration::from_millis(100)).await
|
|
}
|
|
stack
|
|
.config_v4()
|
|
.inspect(|c| println!("ipv4 config: {c:?}"));
|
|
|
|
anyhow::Ok(stack.clone())
|
|
}
|
|
|
|
pub(crate) async fn wifi(&mut self, network_config: &NetworkConfig) -> anyhow::Result<IpInfo> {
|
|
let _ssid = network_config
|
|
.ssid
|
|
.clone()
|
|
.ok_or(anyhow!("No ssid configured"))?;
|
|
let _password = network_config.password.clone();
|
|
let _max_wait = network_config.max_wait;
|
|
bail!("todo")
|
|
// match password {
|
|
// Some(pw) => {
|
|
// //TODO expect error due to invalid pw or similar! //call this during configuration and check if works, revert to config mode if not
|
|
// self.wifi_driver.set_configuration(&Configuration::Client(
|
|
// ClientConfiguration {
|
|
// ssid,
|
|
// password: pw,
|
|
// ..Default::default()
|
|
// },
|
|
// ))?;
|
|
// }
|
|
// None => {
|
|
// self.wifi_driver.set_configuration(&Configuration::Client(
|
|
// ClientConfiguration {
|
|
// ssid,
|
|
// auth_method: AuthMethod::None,
|
|
// ..Default::default()
|
|
// },
|
|
// ))?;
|
|
// }
|
|
// }
|
|
//
|
|
// self.wifi_driver.start()?;
|
|
// self.wifi_driver.connect()?;
|
|
//
|
|
// let delay = Delay::new_default();
|
|
// let mut counter = 0_u32;
|
|
// while !self.wifi_driver.is_connected()? {
|
|
// delay.delay_ms(250);
|
|
// counter += 250;
|
|
// if counter > max_wait {
|
|
// //ignore these errors, Wi-Fi will not be used this
|
|
// self.wifi_driver.disconnect().unwrap_or(());
|
|
// self.wifi_driver.stop().unwrap_or(());
|
|
// bail!("Did not manage wifi connection within timeout");
|
|
// }
|
|
// }
|
|
// log::info!("Should be connected now, waiting for link to be up");
|
|
//
|
|
// while !self.wifi_driver.is_up()? {
|
|
// delay.delay_ms(250);
|
|
// counter += 250;
|
|
// if counter > max_wait {
|
|
// //ignore these errors, Wi-Fi will not be used this
|
|
// self.wifi_driver.disconnect().unwrap_or(());
|
|
// self.wifi_driver.stop().unwrap_or(());
|
|
// bail!("Did not manage wifi connection within timeout");
|
|
// }
|
|
// }
|
|
// //update freertos registers ;)
|
|
// let address = self.wifi_driver.sta_netif().get_ip_info()?;
|
|
// log(LogMessage::WifiInfo, 0, 0, "", &format!("{address:?}"));
|
|
// anyhow::Ok(address)
|
|
}
|
|
pub(crate) fn load_config(&mut self) -> anyhow::Result<PlantControllerConfig> {
|
|
bail!("todo");
|
|
// let cfg = File::open(Self::CONFIG_FILE)?;
|
|
// let config: PlantControllerConfig = serde_json::from_reader(cfg)?;
|
|
// anyhow::Ok(config)
|
|
}
|
|
pub(crate) async fn save_config(
|
|
&mut self,
|
|
_config: &PlantControllerConfig,
|
|
) -> anyhow::Result<()> {
|
|
bail!("todo");
|
|
// let mut cfg = File::create(Self::CONFIG_FILE)?;
|
|
// serde_json::to_writer(&mut cfg, &config)?;
|
|
// log::info!("Wrote config config {:?}", config);
|
|
// anyhow::Ok(())
|
|
}
|
|
pub(crate) fn mount_file_system(&mut self) -> anyhow::Result<()> {
|
|
bail!("fail");
|
|
// log(LogMessage::MountingFilesystem, 0, 0, "", "");
|
|
// let base_path = String::try_from("/spiffs")?;
|
|
// let storage = String::try_from(Self::SPIFFS_PARTITION_NAME)?;
|
|
//let conf = todo!();
|
|
|
|
//let conf = esp_idf_sys::esp_vfs_spiffs_conf_t {
|
|
//base_path: base_path.as_ptr(),
|
|
//partition_label: storage.as_ptr(),
|
|
//max_files: 5,
|
|
//format_if_mount_failed: true,
|
|
//};
|
|
|
|
//TODO
|
|
//unsafe {
|
|
//esp_idf_sys::esp!(esp_idf_sys::esp_vfs_spiffs_register(&conf))?;
|
|
//}
|
|
|
|
// let free_space = self.file_system_size()?;
|
|
// log(
|
|
// LogMessage::FilesystemMount,
|
|
// free_space.free_size as u32,
|
|
// free_space.total_size as u32,
|
|
// &free_space.used_size.to_string(),
|
|
// "",
|
|
// );
|
|
// anyhow::Ok(())
|
|
}
|
|
async fn file_system_size(&mut self) -> anyhow::Result<FileSystemSizeInfo> {
|
|
bail!("fail");
|
|
// let storage = CString::new(Self::SPIFFS_PARTITION_NAME)?;
|
|
// let mut total_size = 0;
|
|
// let mut used_size = 0;
|
|
// unsafe {
|
|
// esp_idf_sys::esp!(esp_spiffs_info(
|
|
// storage.as_ptr(),
|
|
// &mut total_size,
|
|
// &mut used_size
|
|
// ))?;
|
|
// }
|
|
// anyhow::Ok(FileSystemSizeInfo {
|
|
// total_size,
|
|
// used_size,
|
|
// free_size: total_size - used_size,
|
|
// })
|
|
}
|
|
|
|
pub(crate) async fn list_files(&self) -> FileList {
|
|
return FileList {
|
|
total: 0,
|
|
used: 0,
|
|
file_system_corrupt: None,
|
|
files: Vec::new(),
|
|
iter_error: None,
|
|
};
|
|
//
|
|
// let storage = CString::new(Self::SPIFFS_PARTITION_NAME).unwrap();
|
|
//
|
|
// let mut file_system_corrupt = None;
|
|
//
|
|
// let mut iter_error = None;
|
|
// let mut result = Vec::new();
|
|
//
|
|
// let filepath = Path::new(Self::BASE_PATH);
|
|
// let read_dir = fs::read_dir(filepath);
|
|
// match read_dir {
|
|
// OkStd(read_dir) => {
|
|
// for item in read_dir {
|
|
// match item {
|
|
// OkStd(file) => {
|
|
// let f = FileInfo {
|
|
// filename: file.file_name().into_string().unwrap(),
|
|
// size: file.metadata().map(|it| it.len()).unwrap_or_default()
|
|
// as usize,
|
|
// };
|
|
// result.push(f);
|
|
// }
|
|
// Err(err) => {
|
|
// iter_error = Some(format!("{err:?}"));
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// Err(err) => {
|
|
// file_system_corrupt = Some(format!("{err:?}"));
|
|
// }
|
|
// }
|
|
// let mut total: usize = 0;
|
|
// let mut used: usize = 0;
|
|
// unsafe {
|
|
// esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
|
|
// }
|
|
//
|
|
// FileList {
|
|
// total,
|
|
// used,
|
|
// file_system_corrupt,
|
|
// files: result,
|
|
// iter_error,
|
|
// }
|
|
}
|
|
pub(crate) async fn delete_file(&self, _filename: &str) -> anyhow::Result<()> {
|
|
bail!("todo");
|
|
// let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
|
|
// match fs::remove_file(filepath) {
|
|
// OkStd(_) => anyhow::Ok(()),
|
|
// Err(err) => {
|
|
// bail!(format!("{err:?}"))
|
|
// }
|
|
// }
|
|
}
|
|
// pub(crate) async fn get_file_handle(
|
|
// &self,
|
|
// filename: &str,
|
|
// write: bool,
|
|
// ) -> anyhow::Result<File> {
|
|
// let filepath = Path::new(Self::BASE_PATH).join(Path::new(filename));
|
|
// anyhow::Ok(if write {
|
|
// File::create(filepath)?
|
|
// } else {
|
|
// File::open(filepath)?
|
|
// })
|
|
// }
|
|
|
|
pub(crate) fn init_rtc_deepsleep_memory(&self, init_rtc_store: bool, to_config_mode: bool) {
|
|
if init_rtc_store {
|
|
unsafe {
|
|
LAST_WATERING_TIMESTAMP = [0; PLANT_COUNT];
|
|
CONSECUTIVE_WATERING_PLANT = [0; PLANT_COUNT];
|
|
LOW_VOLTAGE_DETECTED = false;
|
|
crate::log::init();
|
|
RESTART_TO_CONF = to_config_mode;
|
|
};
|
|
} else {
|
|
unsafe {
|
|
if to_config_mode {
|
|
RESTART_TO_CONF = true;
|
|
}
|
|
log(
|
|
LogMessage::RestartToConfig,
|
|
RESTART_TO_CONF as u32,
|
|
0,
|
|
"",
|
|
"",
|
|
);
|
|
log(
|
|
LogMessage::LowVoltage,
|
|
LOW_VOLTAGE_DETECTED as u32,
|
|
0,
|
|
"",
|
|
"",
|
|
);
|
|
for i in 0..PLANT_COUNT {
|
|
log::info!(
|
|
"LAST_WATERING_TIMESTAMP[{}] = UTC {}",
|
|
i,
|
|
LAST_WATERING_TIMESTAMP[i]
|
|
);
|
|
}
|
|
for i in 0..PLANT_COUNT {
|
|
log::info!(
|
|
"CONSECUTIVE_WATERING_PLANT[{}] = {}",
|
|
i,
|
|
CONSECUTIVE_WATERING_PLANT[i]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn mqtt(&mut self, network_config: &NetworkConfig) -> anyhow::Result<()> {
|
|
let base_topic = network_config
|
|
.base_topic
|
|
.as_ref()
|
|
.context("missing base topic")?;
|
|
if base_topic.is_empty() {
|
|
bail!("Mqtt base_topic was empty")
|
|
}
|
|
let _base_topic_copy = base_topic.clone();
|
|
let mqtt_url = network_config
|
|
.mqtt_url
|
|
.as_ref()
|
|
.context("missing mqtt url")?;
|
|
if mqtt_url.is_empty() {
|
|
bail!("Mqtt url was empty")
|
|
}
|
|
|
|
bail!("todo");
|
|
//
|
|
// let last_will_topic = format!("{}/state", base_topic);
|
|
// let mqtt_client_config = MqttClientConfiguration {
|
|
// lwt: Some(LwtConfiguration {
|
|
// topic: &last_will_topic,
|
|
// payload: "lost".as_bytes(),
|
|
// qos: AtLeastOnce,
|
|
// retain: true,
|
|
// }),
|
|
// client_id: Some("plantctrl"),
|
|
// keep_alive_interval: Some(Duration::from_secs(60 * 60 * 2)),
|
|
// username: network_config.mqtt_user.as_ref().map(|v| &**v),
|
|
// password: network_config.mqtt_password.as_ref().map(|v| &**v),
|
|
// //room for improvement
|
|
// ..Default::default()
|
|
// };
|
|
//
|
|
// let mqtt_connected_event_received = Arc::new(AtomicBool::new(false));
|
|
// let mqtt_connected_event_ok = Arc::new(AtomicBool::new(false));
|
|
//
|
|
// let round_trip_ok = Arc::new(AtomicBool::new(false));
|
|
// let round_trip_topic = format!("{}/internal/roundtrip", base_topic);
|
|
// let stay_alive_topic = format!("{}/stay_alive", base_topic);
|
|
// log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic);
|
|
//
|
|
// let mqtt_connected_event_received_copy = mqtt_connected_event_received.clone();
|
|
// let mqtt_connected_event_ok_copy = mqtt_connected_event_ok.clone();
|
|
// let stay_alive_topic_copy = stay_alive_topic.clone();
|
|
// let round_trip_topic_copy = round_trip_topic.clone();
|
|
// let round_trip_ok_copy = round_trip_ok.clone();
|
|
// let client_id = mqtt_client_config.client_id.unwrap_or("not set");
|
|
// log(LogMessage::MqttInfo, 0, 0, client_id, mqtt_url);
|
|
// let mut client = EspMqttClient::new_cb(mqtt_url, &mqtt_client_config, move |event| {
|
|
// let payload = event.payload();
|
|
// match payload {
|
|
// embedded_svc::mqtt::client::EventPayload::Received {
|
|
// id: _,
|
|
// topic,
|
|
// data,
|
|
// details: _,
|
|
// } => {
|
|
// let data = String::from_utf8_lossy(data);
|
|
// if let Some(topic) = topic {
|
|
// //todo use enums
|
|
// if topic.eq(round_trip_topic_copy.as_str()) {
|
|
// round_trip_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
// } else if topic.eq(stay_alive_topic_copy.as_str()) {
|
|
// let value =
|
|
// data.eq_ignore_ascii_case("true") || data.eq_ignore_ascii_case("1");
|
|
// log(LogMessage::MqttStayAliveRec, 0, 0, &data, "");
|
|
// STAY_ALIVE.store(value, std::sync::atomic::Ordering::Relaxed);
|
|
// } else {
|
|
// log(LogMessage::UnknownTopic, 0, 0, "", topic);
|
|
// }
|
|
// }
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Connected(_) => {
|
|
// mqtt_connected_event_received_copy
|
|
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
// mqtt_connected_event_ok_copy.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
// log::info!("Mqtt connected");
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Disconnected => {
|
|
// mqtt_connected_event_received_copy
|
|
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
// mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
// log::info!("Mqtt disconnected");
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Error(esp_error) => {
|
|
// log::info!("EspMqttError reported {:?}", esp_error);
|
|
// mqtt_connected_event_received_copy
|
|
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
|
// mqtt_connected_event_ok_copy.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
// log::info!("Mqtt error");
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => {
|
|
// log::info!("Mqtt before connect")
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => {
|
|
// log::info!("Mqtt subscribed")
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => {
|
|
// log::info!("Mqtt unsubscribed")
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Published(_) => {
|
|
// log::info!("Mqtt published")
|
|
// }
|
|
// esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => {
|
|
// log::info!("Mqtt deleted")
|
|
// }
|
|
// }
|
|
// })?;
|
|
//
|
|
// let mut wait_for_connections_event = 0;
|
|
// while wait_for_connections_event < 100 {
|
|
// wait_for_connections_event += 1;
|
|
// match mqtt_connected_event_received.load(std::sync::atomic::Ordering::Relaxed) {
|
|
// true => {
|
|
// log::info!("Mqtt connection callback received, progressing");
|
|
// match mqtt_connected_event_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
|
// true => {
|
|
// log::info!(
|
|
// "Mqtt did callback as connected, testing with roundtrip now"
|
|
// );
|
|
// //subscribe to roundtrip
|
|
// client.subscribe(round_trip_topic.as_str(), ExactlyOnce)?;
|
|
// client.subscribe(stay_alive_topic.as_str(), ExactlyOnce)?;
|
|
// //publish to roundtrip
|
|
// client.publish(
|
|
// round_trip_topic.as_str(),
|
|
// ExactlyOnce,
|
|
// false,
|
|
// "online_test".as_bytes(),
|
|
// )?;
|
|
//
|
|
// let mut wait_for_roundtrip = 0;
|
|
// while wait_for_roundtrip < 100 {
|
|
// wait_for_roundtrip += 1;
|
|
// match round_trip_ok.load(std::sync::atomic::Ordering::Relaxed) {
|
|
// true => {
|
|
// log::info!("Round trip registered, proceeding");
|
|
// self.mqtt_client = Some(MqttClient {
|
|
// mqtt_client: client,
|
|
// base_topic: base_topic_copy,
|
|
// });
|
|
// return anyhow::Ok(());
|
|
// }
|
|
// false => {
|
|
// unsafe { vTaskDelay(10) };
|
|
// }
|
|
// }
|
|
// }
|
|
// bail!("Mqtt did not complete roundtrip in time");
|
|
// }
|
|
// false => {
|
|
// bail!("Mqtt did respond but with failure")
|
|
// }
|
|
// }
|
|
// }
|
|
// false => {
|
|
// unsafe { vTaskDelay(10) };
|
|
// }
|
|
// }
|
|
// }
|
|
// bail!("Mqtt did not fire connection callback in time");
|
|
}
|
|
pub(crate) async fn mqtt_publish(
|
|
&mut self,
|
|
_subtopic: &str,
|
|
_message: &[u8],
|
|
) -> anyhow::Result<()> {
|
|
bail!("todo");
|
|
//
|
|
// if self.mqtt_client.is_none() {
|
|
// return anyhow::Ok(());
|
|
// }
|
|
// if !subtopic.starts_with("/") {
|
|
// log::info!("Subtopic without / at start {}", subtopic);
|
|
// bail!("Subtopic without / at start {}", subtopic);
|
|
// }
|
|
// if subtopic.len() > 192 {
|
|
// log::info!("Subtopic exceeds 192 chars {}", subtopic);
|
|
// bail!("Subtopic exceeds 192 chars {}", subtopic);
|
|
// }
|
|
// let client = self.mqtt_client.as_mut().unwrap();
|
|
// let mut full_topic: heapless::String<256> = heapless::String::new();
|
|
// if full_topic.push_str(client.base_topic.as_str()).is_err() {
|
|
// log::info!("Some error assembling full_topic 1");
|
|
// bail!("Some error assembling full_topic 1")
|
|
// };
|
|
// if full_topic.push_str(subtopic).is_err() {
|
|
// log::info!("Some error assembling full_topic 2");
|
|
// bail!("Some error assembling full_topic 2")
|
|
// };
|
|
// let publish = client
|
|
// .mqtt_client
|
|
// .publish(&full_topic, ExactlyOnce, true, message);
|
|
// Timer::after_millis(10).await;
|
|
// match publish {
|
|
// OkStd(message_id) => {
|
|
// log::info!(
|
|
// "Published mqtt topic {} with message {:#?} msgid is {:?}",
|
|
// full_topic,
|
|
// String::from_utf8_lossy(message),
|
|
// message_id
|
|
// );
|
|
// anyhow::Ok(())
|
|
// }
|
|
// Err(err) => {
|
|
// log::info!(
|
|
// "Error during mqtt send on topic {} with message {:#?} error is {:?}",
|
|
// full_topic,
|
|
// String::from_utf8_lossy(message),
|
|
// err
|
|
// );
|
|
// Err(err)?
|
|
// }
|
|
// }
|
|
}
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
|
|
use core::net::{Ipv4Addr, SocketAddrV4};
|
|
|
|
use edge_dhcp::{
|
|
io::{self, DEFAULT_SERVER_PORT},
|
|
server::{Server, ServerOptions},
|
|
};
|
|
use edge_nal::UdpBind;
|
|
use edge_nal_embassy::{Udp, UdpBuffers};
|
|
|
|
let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip");
|
|
|
|
let mut buf = [0u8; 1500];
|
|
|
|
let mut gw_buf = [Ipv4Addr::UNSPECIFIED];
|
|
|
|
let buffers = UdpBuffers::<3, 1024, 1024, 10>::new();
|
|
let unbound_socket = Udp::new(stack, &buffers);
|
|
let mut bound_socket = unbound_socket
|
|
.bind(core::net::SocketAddr::V4(SocketAddrV4::new(
|
|
Ipv4Addr::UNSPECIFIED,
|
|
DEFAULT_SERVER_PORT,
|
|
)))
|
|
.await
|
|
.unwrap();
|
|
|
|
loop {
|
|
_ = io::server::run(
|
|
&mut Server::<_, 64>::new_with_et(ip),
|
|
&ServerOptions::new(ip, Some(&mut gw_buf)),
|
|
&mut bound_socket,
|
|
&mut buf,
|
|
)
|
|
.await
|
|
.inspect_err(|e| log::warn!("DHCP server error: {e:?}"));
|
|
Timer::after(Duration::from_millis(500)).await;
|
|
}
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
async fn connection(mut controller: WifiController<'static>) {
|
|
println!("start connection task");
|
|
println!("Device capabilities: {:?}", controller.capabilities());
|
|
loop {
|
|
match esp_wifi::wifi::wifi_state() {
|
|
WifiState::ApStarted => {
|
|
// wait until we're no longer connected
|
|
controller.wait_for_event(WifiEvent::ApStop).await;
|
|
Timer::after(Duration::from_millis(5000)).await
|
|
}
|
|
_ => {}
|
|
}
|
|
if !matches!(controller.is_started(), core::result::Result::Ok(true)) {
|
|
let client_config = Configuration::AccessPoint(AccessPointConfiguration {
|
|
ssid: "esp-wifi".try_into().unwrap(),
|
|
..Default::default()
|
|
});
|
|
controller.set_configuration(&client_config).unwrap();
|
|
println!("Starting wifi");
|
|
controller.start_async().await.unwrap();
|
|
println!("Wifi started!");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[embassy_executor::task]
|
|
async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) {
|
|
runner.run().await
|
|
}
|