From c5c868557af5315a17a97e332c53eed29847f2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Thu, 9 Jun 2022 20:19:44 +0200 Subject: [PATCH 1/9] intermediate commit --- Cargo.lock | 21 ++ Cargo.toml | 1 + apps/manifest.json | 8 + apps/wlanman/Cargo.toml | 25 +++ apps/wlanman/locales/i18n.json | 8 + apps/wlanman/src/main.rs | 354 +++++++++++++++++++++++++++++++++ 6 files changed, 417 insertions(+) create mode 100644 apps/wlanman/Cargo.toml create mode 100644 apps/wlanman/locales/i18n.json create mode 100644 apps/wlanman/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 9038db236..585ea2173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4022,6 +4022,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wlanman" +version = "0.1.0" +dependencies = [ + "com", + "gam", + "graphics-server", + "locales", + "log", + "log-server", + "modals", + "net", + "num-derive", + "num-traits", + "pddb", + "tts-frontend", + "xous", + "xous-ipc 0.9.7", + "xous-names", +] + [[package]] name = "wycheproof-import" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6424ce93c..cbb476455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ members = [ "apps/hello", "apps/repl", "apps/vault", + "apps/wlanman", "services/libstd-test", "services/ffi-test", "services/tts", diff --git a/apps/manifest.json b/apps/manifest.json index 94fcadf48..8562a7259 100644 --- a/apps/manifest.json +++ b/apps/manifest.json @@ -21,6 +21,14 @@ } } }, + "wlanman": { + "context_name": "WLAN manager", + "menu_name": { + "appmenu.wlanman": { + "en": "WLAN manager" + } + } + }, "vault": { "context_name": "Key Vault", "menu_name": { diff --git a/apps/wlanman/Cargo.toml b/apps/wlanman/Cargo.toml new file mode 100644 index 000000000..322e8479c --- /dev/null +++ b/apps/wlanman/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "wlanman" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4.14" +num-derive = {version = "0.3.3", default-features = false} +num-traits = {version = "0.2.14", default-features = false} +xous = { path = "../../xous-rs" } +xous-ipc = { path = "../../xous-ipc" } +log-server = { path = "../../services/log-server" } +xous-names = { path = "../../services/xous-names" } +gam = {path = "../../services/gam" } +graphics-server = {path = "../../services/graphics-server" } +locales = {path = "../../locales"} +tts-frontend = {path="../../services/tts"} +com = {path="../../services/com"} +net = {path="../../services/net"} +modals = {path = "../../services/modals"} +pddb = {path = "../../services/pddb"} + +[features] +tts = [] +default = [] diff --git a/apps/wlanman/locales/i18n.json b/apps/wlanman/locales/i18n.json new file mode 100644 index 000000000..19f8bf49e --- /dev/null +++ b/apps/wlanman/locales/i18n.json @@ -0,0 +1,8 @@ +{ + "helloworld.hello": { + "en": "Hello World!", + "ja": "Hello World!", + "zh": "大家好!", + "en-tts": "Hello World!" + } +} \ No newline at end of file diff --git a/apps/wlanman/src/main.rs b/apps/wlanman/src/main.rs new file mode 100644 index 000000000..9a5b93d43 --- /dev/null +++ b/apps/wlanman/src/main.rs @@ -0,0 +1,354 @@ +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] + +use core::fmt::{Display, Write}; +use gam::modal; +use graphics_server::api::GlyphStyle; +use graphics_server::{DrawStyle, Gid, PixelColor, Point, Rectangle, TextBounds, TextView}; +use locales::t; +use num_traits::*; +use std::collections::HashMap; +use std::io::Write as IoWrite; + +pub(crate) const SERVER_NAME_WLANMAN: &str = "_WLAN manager_"; + +/// Top level application events. +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub(crate) enum HelloOp { + /// Redraw the screen + Redraw = 0, + + Char, + Quit, +} + +#[derive(Debug)] +enum WLANError { + UnderlyingError(xous::Error), + PDDBWriteError(usize, usize), + PDDBIoError(std::io::Error), +} + +impl Display for WLANError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + WLANError::UnderlyingError(inner_error) => { + write!(f, "Underlying Xous error: {:?}", inner_error) + } + WLANError::PDDBWriteError(written, total) => { + write!( + f, + "PDDB write error, written {} of {} total", + written, total + ) + } + WLANError::PDDBIoError(err) => { + write!(f, "PDDB IO error, {}", err) + } + } + } +} + +impl From for WLANError { + fn from(v: xous::Error) -> Self { + Self::UnderlyingError(v) + } +} + +impl From for WLANError { + fn from(v: std::io::Error) -> Self { + Self::PDDBIoError(v) + } +} + +struct WLANMan { + content: Gid, + gam: gam::Gam, + _gam_token: [u32; 4], + screensize: Point, + + // Deps for wlan management + com: com::Com, + netmgr: net::NetManager, + modals: modals::Modals, + pddb: pddb::Pddb, +} + +impl WLANMan { + fn new(xns: &xous_names::XousNames, sid: xous::SID) -> Self { + let gam = gam::Gam::new(&xns).expect("Can't connect to GAM"); + let gam_token = gam + .register_ux(gam::UxRegistration { + app_name: xous_ipc::String::<128>::from_str(gam::APP_NAME_WLANMAN), + ux_type: gam::UxType::Framebuffer, + predictor: None, + listener: sid.to_array(), + redraw_id: HelloOp::Redraw.to_u32().unwrap(), + gotinput_id: None, + audioframe_id: None, + rawkeys_id: Some(HelloOp::Char.to_u32().unwrap()), + focuschange_id: None, + }) + .expect("Could not register GAM UX") + .unwrap(); + + let content = gam + .request_content_canvas(gam_token) + .expect("Could not get content canvas"); + let screensize = gam + .get_canvas_bounds(content) + .expect("Could not get canvas dimensions"); + Self { + gam, + _gam_token: gam_token, + content, + screensize, + + com: com::Com::new(&xns).unwrap(), + netmgr: net::NetManager::new(), + modals: modals::Modals::new(&xns).unwrap(), + pddb: pddb::Pddb::new(), + } + } + + fn root_actions(&self) -> Vec<&str> { + vec![ + "Turn WLAN on", + "Turn WLAN off", + "Manually add a network", + "Scan for networks", + "Network status", + ] + } + + fn set_wlan_state(&mut self, state: bool) -> Result<(), WLANError> { + match state { + true => { + // on + self.com.wlan_set_on()?; + self.netmgr.connection_manager_run()?; + } + false => { + // off + self.netmgr.connection_manager_stop()?; + self.com.wlan_set_off()?; + } + }; + + Ok(()) + } + + fn add_new_ssid(&mut self) -> Result<(), WLANError> { + let connection_data = self + .modals + .alert_builder("Fill this form with your connection SSID and password.") + .field( + Some("SSID".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("SSID cannot be empty")); + } + + None + }), + ) + .field( + Some("Password".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("Password cannot be empty")); + } + + None + }), + ) + .build() + .unwrap(); + + let content = connection_data.content(); + + self.store_connection_info(content[0].as_str(), content[1].as_str()) + } + + fn store_connection_info(&mut self, ssid: &str, pass: &str) -> Result<(), WLANError> { + self.netmgr.connection_manager_stop().unwrap(); + match self.pddb.get( + net::AP_DICT_NAME, + &ssid, + None, + true, + true, + Some(com::api::WF200_PASS_MAX_LEN), + Some(|| {}), + ) { + Ok(mut entry) => { + match entry.write(&pass.as_bytes()) { + Ok(len) => { + if len != pass.len() { + Err(WLANError::PDDBWriteError(len, pass.len())) + } else { + // for now, we should always call flush at the end of a routine; perhaps in the + // future we'll have a timer that automatically syncs the pddb + entry.flush().expect("couldn't sync pddb cache"); + // restart the connection manager now that the key combo has been committed + self.netmgr.connection_manager_run().unwrap(); + Ok(()) + } + } + Err(e) => Err(WLANError::PDDBIoError(e)), + } + } + Err(e) => Err(WLANError::PDDBIoError(e)), + } + } + + fn scan_networks(&self) -> Result, WLANError> { + let scan_result = self.netmgr.wifi_get_ssid_list()?; + + Ok(scan_result + .iter() + .map(|ssid| ssid.name.to_string()) + .collect()) + } + + fn show_available_networks(&mut self) -> Result<(), WLANError> { + let networks = self.scan_networks()?; + let networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect(); + + if networks.is_empty() { + self.modals + .show_notification("No networks available.", None) + .unwrap(); + return Ok(()); + } + + self.modals.add_list(networks).unwrap(); + + let ssid = self.modals.get_radiobutton("Choose an action:").unwrap(); + + self.fill_password_for_ssid(&ssid) + } + + fn fill_password_for_ssid(&mut self, ssid: &str) -> Result<(), WLANError> { + let connection_data = self + .modals + .alert_builder(&format!("Fill this form with the password for {ssid}.")) + .field( + Some("Password".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("Password cannot be empty")); + } + + None + }), + ) + .build() + .unwrap(); + + let content = connection_data.content(); + + self.store_connection_info(ssid, content[0].as_str()) + } + + fn network_status(&mut self) -> Result<(), WLANError> { + let status = self.com.wlan_status()?; + let ssid = match status.ssid { + Some(s) => s.name.to_string(), + None => "not connected".to_string(), + }; + + let ls = status.link_state; + let ip = &status.ipv4; + + let status_str = format!( + "Connection status: \n\n- SSID: {}\n- Link state: {:?}\n- IP: {}\n- Gateway: {}\n- Subnet mask: {}\n- DNS 1: {}\n- DNS 2: {}\n- DHCP state: {:?}", + ssid, + ls, + format_ip(ip.addr), + format_ip(ip.gtwy), + format_ip(ip.mask), + format_ip(ip.dns1), + format_ip(ip.dns2), + ip.dhcp, + ); + + self.modals.show_notification(&status_str, None).unwrap(); + Ok(()) + } + + fn draw_menu(&self) -> usize { + let actions = self.root_actions(); + + self.modals.add_list(actions).unwrap(); + self.modals.get_radiobutton("Choose an action:").unwrap(); + + self.modals.get_radio_index().unwrap() + } + + fn consume_menu_index(&mut self, idx: usize) { + let resp = match idx { + 0 => self.set_wlan_state(true), + 1 => self.set_wlan_state(false), + 2 => self.add_new_ssid(), + 3 => self.show_available_networks(), + 4 => self.network_status(), + _ => panic!("idx {idx} not covered"), + }; + + resp.unwrap_or_else(|error| self.show_error_modal(error)); + } + + fn show_error_modal(&self, e: WLANError) { + self.modals + .show_notification(format!("Error: {}", e).as_str(), None) + .unwrap() + } +} + +fn main() -> ! { + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Info); + log::info!("Hello world PID is {}", xous::process::id()); + + let xns = xous_names::XousNames::new().unwrap(); + + // Register the server with xous + let sid = xns + .register_name(SERVER_NAME_WLANMAN, None) + .expect("can't register server"); + + let mut hello = WLANMan::new(&xns, sid); + + loop { + let msg = xous::receive_message(sid).unwrap(); + log::debug!("Got message: {:?}", msg); + + match FromPrimitive::from_usize(msg.body.id()) { + Some(HelloOp::Redraw) => { + log::debug!("Got redraw"); + } + Some(HelloOp::Quit) => { + log::info!("Quitting application"); + break; + } + Some(HelloOp::Char) => { + let idx = hello.draw_menu(); + hello.consume_menu_index(idx); + } + _ => { + log::error!("Got unknown message"); + } + } + } + + log::info!("Quitting"); + xous::terminate_process(0) +} + +fn format_ip(src: [u8; 4]) -> String { + src.iter() + .map(|&id| id.to_string()) + .collect::>() + .join(".") +} From 014dcfd612f672bb03f22acd2c8e8a14013de579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 10 Jun 2022 12:20:39 +0200 Subject: [PATCH 2/9] status: add wifi management menu --- services/gam/src/lib.rs | 2 + services/status/locales/i18n.json | 4 + services/status/src/main.rs | 10 + services/status/src/mainmenu.rs | 8 + services/status/src/wifi.rs | 351 ++++++++++++++++++++++++++++++ 5 files changed, 375 insertions(+) create mode 100644 services/status/src/wifi.rs diff --git a/services/gam/src/lib.rs b/services/gam/src/lib.rs index 3f4d75045..48c80b5c3 100644 --- a/services/gam/src/lib.rs +++ b/services/gam/src/lib.rs @@ -35,6 +35,7 @@ pub const STATUS_BAR_NAME: &'static str = "status"; pub const APP_NAME_SHELLCHAT: &'static str = "shellchat"; pub const APP_MENU_NAME: &'static str = "app menu"; pub const KBD_MENU_NAME: &'static str = "keyboard menu"; +pub const WIFI_MENU_NAME: &'static str = "WLAN menu"; /// UX context registry. Names here are authorized by the GAM to have Canvases. pub const EXPECTED_BOOT_CONTEXTS: &[&'static str] = &[ @@ -48,6 +49,7 @@ pub const EXPECTED_BOOT_CONTEXTS: &[&'static str] = &[ PDDB_MENU_NAME, APP_MENU_NAME, KBD_MENU_NAME, + WIFI_MENU_NAME, ]; #[derive(Copy, Clone, PartialEq, Eq, Debug)] diff --git a/services/status/locales/i18n.json b/services/status/locales/i18n.json index 915c5a702..b0b5bd1f3 100644 --- a/services/status/locales/i18n.json +++ b/services/status/locales/i18n.json @@ -78,6 +78,10 @@ "zh": "睡眠模式", "en-tts": "Sleep now" }, + "mainmenu.wifimenu": { + "en": "Wi-Fi menu", + "en-tts": "Wi-Fi menu" + }, "mainmenu.backlighton": { "en": "Backlight on", "ja": "バックライト点灯", diff --git a/services/status/src/main.rs b/services/status/src/main.rs index bf042c287..23ee1ce44 100644 --- a/services/status/src/main.rs +++ b/services/status/src/main.rs @@ -9,6 +9,7 @@ mod kbdmenu; use kbdmenu::*; mod app_autogen; mod time; +mod wifi; use com::api::*; use core::fmt::Write; @@ -62,6 +63,9 @@ pub(crate) enum StatusOpcode { BatteryDisconnect, /// for returning wifi stats WifiStats, + + /// Raise the wifi menu + WifiMenu, Quit, } @@ -359,6 +363,8 @@ fn main() -> ! { #[cfg(any(target_os = "none", target_os = "xous"))] llio.clear_wakeup_alarm().unwrap(); // this is here to clear any wake-up alarms that were set by a prior coldboot command + wifi::start_background_thread(); + pump_run.store(true, Ordering::Relaxed); // start status thread updating loop { let msg = xous::receive_message(status_sid).unwrap(); @@ -423,6 +429,10 @@ fn main() -> ! { }; wifi_status = WlanStatus::from_ipc(buffer.to_original::().unwrap()); }, + Some(StatusOpcode::WifiMenu) => { + ticktimer.sleep_ms(100).ok(); // yield for a moment to allow the previous menu to close + gam.raise_menu(gam::WIFI_MENU_NAME).unwrap(); + }, Some(StatusOpcode::Pump) => { let elapsed_time = ticktimer.elapsed_ms(); { // update the CPU load bar diff --git a/services/status/src/mainmenu.rs b/services/status/src/mainmenu.rs index e53719d30..b6d1ff299 100644 --- a/services/status/src/mainmenu.rs +++ b/services/status/src/mainmenu.rs @@ -32,6 +32,14 @@ pub fn create_main_menu(keys: Arc>, status_conn: xous::CID, com: close_on_select: true, }); + menuitems.push(MenuItem { + name: String::from_str(t!("mainmenu.wifimenu", xous::LANG)), + action_conn: Some(status_conn), + action_opcode: StatusOpcode::WifiMenu.to_u32().unwrap(), + action_payload: MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }); + menuitems.push(MenuItem { name: String::from_str(t!("mainmenu.sleep", xous::LANG)), action_conn: Some(status_conn), diff --git a/services/status/src/wifi.rs b/services/status/src/wifi.rs new file mode 100644 index 000000000..43eb34456 --- /dev/null +++ b/services/status/src/wifi.rs @@ -0,0 +1,351 @@ +use core::fmt::Display; + +use num_traits::*; + +use std::io::Write; + +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, PartialEq, PartialOrd)] +enum WlanManOp { + TurnWlanOn = 3, + TurnWlanOff, + AddNetworkManually, + ScanForNetworks, + Status, + KnownNetworks = 8, +} + +#[derive(Debug)] +enum WLANError { + UnderlyingError(xous::Error), + PDDBWriteError(usize, usize), + PDDBIoError(std::io::Error), +} + +impl Display for WLANError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + WLANError::UnderlyingError(inner_error) => { + write!(f, "Underlying Xous error: {:?}", inner_error) + } + WLANError::PDDBWriteError(written, total) => { + write!( + f, + "PDDB write error, written {} of {} total", + written, total + ) + } + WLANError::PDDBIoError(err) => { + write!(f, "PDDB IO error, {}", err) + } + } + } +} + +impl From for WLANError { + fn from(v: xous::Error) -> Self { + Self::UnderlyingError(v) + } +} + +impl From for WLANError { + fn from(v: std::io::Error) -> Self { + Self::PDDBIoError(v) + } +} + +struct WLANMan { + com: com::Com, + netmgr: net::NetManager, + modals: modals::Modals, + pddb: pddb::Pddb, +} + +impl WLANMan { + fn new(xns: &xous_names::XousNames) -> Self { + Self { + com: com::Com::new(&xns).unwrap(), + netmgr: net::NetManager::new(), + modals: modals::Modals::new(&xns).unwrap(), + pddb: pddb::Pddb::new(), + } + } + + fn actions(&self) -> Vec<(&str, WlanManOp)> { + use WlanManOp::*; + + vec![ + ("Turn WLAN on", TurnWlanOn), + ("Turn WLAN off", TurnWlanOff), + ("Manually add a network", AddNetworkManually), + ("Scan for networks", ScanForNetworks), + ("Network status", Status), + ("List known networks", KnownNetworks), + ] + } + + fn set_wlan_state(&mut self, state: bool) -> Result<(), WLANError> { + match state { + true => { + // on + self.com.wlan_set_on()?; + self.netmgr.connection_manager_run()?; + } + false => { + // off + self.netmgr.connection_manager_stop()?; + self.com.wlan_set_off()?; + } + }; + + Ok(()) + } + + fn add_new_ssid(&mut self) -> Result<(), WLANError> { + let connection_data = self + .modals + .alert_builder("Fill this form with your connection SSID and password.") + .field( + Some("SSID".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("SSID cannot be empty")); + } + + None + }), + ) + .field( + Some("Password".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("Password cannot be empty")); + } + + None + }), + ) + .build() + .unwrap(); + + let content = connection_data.content(); + + self.store_connection_info(content[0].as_str(), content[1].as_str()) + } + + fn store_connection_info(&mut self, ssid: &str, pass: &str) -> Result<(), WLANError> { + self.netmgr.connection_manager_stop().unwrap(); + match self.pddb.get( + net::AP_DICT_NAME, + &ssid, + None, + true, + true, + Some(com::api::WF200_PASS_MAX_LEN), + Some(|| {}), + ) { + Ok(mut entry) => { + match entry.write(&pass.as_bytes()) { + Ok(len) => { + if len != pass.len() { + Err(WLANError::PDDBWriteError(len, pass.len())) + } else { + // for now, we should always call flush at the end of a routine; perhaps in the + // future we'll have a timer that automatically syncs the pddb + entry.flush().expect("couldn't sync pddb cache"); + // restart the connection manager now that the key combo has been committed + self.netmgr.connection_manager_run().unwrap(); + Ok(()) + } + } + Err(e) => Err(WLANError::PDDBIoError(e)), + } + } + Err(e) => Err(WLANError::PDDBIoError(e)), + } + } + + fn scan_networks(&self) -> Result, WLANError> { + let scan_result = self.netmgr.wifi_get_ssid_list()?; + + Ok(scan_result + .iter() + .map(|ssid| ssid.name.to_string()) + .collect()) + } + + fn show_available_networks(&mut self) -> Result<(), WLANError> { + let networks = self.scan_networks()?; + let networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect(); + + if networks.is_empty() { + self.modals + .show_notification("No networks available.", None) + .unwrap(); + return Ok(()); + } + + self.modals.add_list(networks).unwrap(); + + let ssid = self.modals.get_radiobutton("Choose an action:").unwrap(); + + self.fill_password_for_ssid(&ssid) + } + + fn fill_password_for_ssid(&mut self, ssid: &str) -> Result<(), WLANError> { + let connection_data = self + .modals + .alert_builder(&format!("Fill this form with the password for {ssid}.")) + .field( + Some("Password".to_string()), + Some(|text| { + if text.as_str().is_empty() { + return Some(xous_ipc::String::from_str("Password cannot be empty")); + } + + None + }), + ) + .build() + .unwrap(); + + let content = connection_data.content(); + + self.store_connection_info(ssid, content[0].as_str()) + } + + fn network_status(&mut self) -> Result<(), WLANError> { + let status = self.com.wlan_status()?; + let ssid = match status.ssid { + Some(s) => s.name.to_string(), + None => "not connected".to_string(), + }; + + let ls = status.link_state; + let ip = &status.ipv4; + + let status_str = format!( + "Connection status: \n\n - SSID: {}\n - Link state: {:?}\n - IP: {}\n - Gateway: {}\n - Subnet mask: {}\n - DNS 1: {}\n - DNS 2: {}\n - DHCP state: {:?}", + ssid, + ls, + format_ip(ip.addr), + format_ip(ip.gtwy), + format_ip(ip.mask), + format_ip(ip.dns1), + format_ip(ip.dns2), + ip.dhcp, + ); + + self.modals.show_notification(&status_str, None).unwrap(); + Ok(()) + } + + fn known_networks(&self) -> Result<(), WLANError> { + let networks = self.pddb.list_keys(net::AP_DICT_NAME, None)?; + + let mut networks_string = String::from("No known networks."); + + if networks.is_empty() { + self.modals + .show_notification(&networks_string, None) + .unwrap(); + return Ok(()); + } + + networks_string = String::from("Known networks:\n"); + + networks_string += &networks + .iter() + .map(|s| format!(" - {}", s)) + .collect::>() + .join("\n"); + + self.modals + .show_notification(&networks_string, None) + .unwrap(); + + Ok(()) + } + + fn claim_menumatic_menu(&self, cid: xous::CID) { + let mut menus = self + .actions() + .iter() + .map(|action| gam::MenuItem { + name: xous_ipc::String::from_str(action.0), + action_conn: Some(cid), + action_opcode: action.1.to_u32().unwrap(), + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }) + .collect::>(); + + menus.push(gam::MenuItem { + name: xous_ipc::String::from_str("Close menu"), + action_conn: None, + action_opcode: 0, + action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }); + + gam::menu_matic(menus, gam::WIFI_MENU_NAME, None); + } + + fn consume_menu_action(&mut self, action: WlanManOp) { + let resp = match action { + WlanManOp::TurnWlanOff => self.set_wlan_state(false), + WlanManOp::TurnWlanOn => self.set_wlan_state(true), + WlanManOp::AddNetworkManually => self.add_new_ssid(), + WlanManOp::ScanForNetworks => self.show_available_networks(), + WlanManOp::Status => self.network_status(), + WlanManOp::KnownNetworks => self.known_networks(), + }; + + resp.unwrap_or_else(|error| self.show_error_modal(error)); + } + + fn show_error_modal(&self, e: WLANError) { + self.modals + .show_notification(format!("Error: {}", e).as_str(), None) + .unwrap() + } +} + +fn format_ip(src: [u8; 4]) -> String { + src.iter() + .map(|&id| id.to_string()) + .collect::>() + .join(".") +} + +pub fn start_background_thread() { + std::thread::spawn(|| run_menu_thread()); +} + +fn run_menu_thread() { + let xns = xous_names::XousNames::new().unwrap(); + + let sid = xous::create_server().unwrap(); + + let mut hello = WLANMan::new(&xns); + + let menu_conn = xous::connect(sid).unwrap(); + hello.claim_menumatic_menu(menu_conn); + + loop { + let msg = xous::receive_message(sid).unwrap(); + log::debug!("Got message: {:?}", msg); + + match FromPrimitive::from_usize(msg.body.id()) { + Some(other) => { + if other >= WlanManOp::TurnWlanOn && other <= WlanManOp::KnownNetworks { + hello.consume_menu_action(other); + continue; + } + } + + _ => { + log::error!("Got unknown message"); + } + } + } +} \ No newline at end of file From cf6dfd06f8a5ff860ae910b2a371f78911646ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Fri, 10 Jun 2022 12:21:10 +0200 Subject: [PATCH 3/9] chore: remove wlanman demo app --- apps/wlanman/Cargo.toml | 25 --- apps/wlanman/locales/i18n.json | 8 - apps/wlanman/src/main.rs | 354 --------------------------------- 3 files changed, 387 deletions(-) delete mode 100644 apps/wlanman/Cargo.toml delete mode 100644 apps/wlanman/locales/i18n.json delete mode 100644 apps/wlanman/src/main.rs diff --git a/apps/wlanman/Cargo.toml b/apps/wlanman/Cargo.toml deleted file mode 100644 index 322e8479c..000000000 --- a/apps/wlanman/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "wlanman" -version = "0.1.0" -edition = "2021" - -[dependencies] -log = "0.4.14" -num-derive = {version = "0.3.3", default-features = false} -num-traits = {version = "0.2.14", default-features = false} -xous = { path = "../../xous-rs" } -xous-ipc = { path = "../../xous-ipc" } -log-server = { path = "../../services/log-server" } -xous-names = { path = "../../services/xous-names" } -gam = {path = "../../services/gam" } -graphics-server = {path = "../../services/graphics-server" } -locales = {path = "../../locales"} -tts-frontend = {path="../../services/tts"} -com = {path="../../services/com"} -net = {path="../../services/net"} -modals = {path = "../../services/modals"} -pddb = {path = "../../services/pddb"} - -[features] -tts = [] -default = [] diff --git a/apps/wlanman/locales/i18n.json b/apps/wlanman/locales/i18n.json deleted file mode 100644 index 19f8bf49e..000000000 --- a/apps/wlanman/locales/i18n.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "helloworld.hello": { - "en": "Hello World!", - "ja": "Hello World!", - "zh": "大家好!", - "en-tts": "Hello World!" - } -} \ No newline at end of file diff --git a/apps/wlanman/src/main.rs b/apps/wlanman/src/main.rs deleted file mode 100644 index 9a5b93d43..000000000 --- a/apps/wlanman/src/main.rs +++ /dev/null @@ -1,354 +0,0 @@ -#![cfg_attr(target_os = "none", no_std)] -#![cfg_attr(target_os = "none", no_main)] - -use core::fmt::{Display, Write}; -use gam::modal; -use graphics_server::api::GlyphStyle; -use graphics_server::{DrawStyle, Gid, PixelColor, Point, Rectangle, TextBounds, TextView}; -use locales::t; -use num_traits::*; -use std::collections::HashMap; -use std::io::Write as IoWrite; - -pub(crate) const SERVER_NAME_WLANMAN: &str = "_WLAN manager_"; - -/// Top level application events. -#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] -pub(crate) enum HelloOp { - /// Redraw the screen - Redraw = 0, - - Char, - Quit, -} - -#[derive(Debug)] -enum WLANError { - UnderlyingError(xous::Error), - PDDBWriteError(usize, usize), - PDDBIoError(std::io::Error), -} - -impl Display for WLANError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - WLANError::UnderlyingError(inner_error) => { - write!(f, "Underlying Xous error: {:?}", inner_error) - } - WLANError::PDDBWriteError(written, total) => { - write!( - f, - "PDDB write error, written {} of {} total", - written, total - ) - } - WLANError::PDDBIoError(err) => { - write!(f, "PDDB IO error, {}", err) - } - } - } -} - -impl From for WLANError { - fn from(v: xous::Error) -> Self { - Self::UnderlyingError(v) - } -} - -impl From for WLANError { - fn from(v: std::io::Error) -> Self { - Self::PDDBIoError(v) - } -} - -struct WLANMan { - content: Gid, - gam: gam::Gam, - _gam_token: [u32; 4], - screensize: Point, - - // Deps for wlan management - com: com::Com, - netmgr: net::NetManager, - modals: modals::Modals, - pddb: pddb::Pddb, -} - -impl WLANMan { - fn new(xns: &xous_names::XousNames, sid: xous::SID) -> Self { - let gam = gam::Gam::new(&xns).expect("Can't connect to GAM"); - let gam_token = gam - .register_ux(gam::UxRegistration { - app_name: xous_ipc::String::<128>::from_str(gam::APP_NAME_WLANMAN), - ux_type: gam::UxType::Framebuffer, - predictor: None, - listener: sid.to_array(), - redraw_id: HelloOp::Redraw.to_u32().unwrap(), - gotinput_id: None, - audioframe_id: None, - rawkeys_id: Some(HelloOp::Char.to_u32().unwrap()), - focuschange_id: None, - }) - .expect("Could not register GAM UX") - .unwrap(); - - let content = gam - .request_content_canvas(gam_token) - .expect("Could not get content canvas"); - let screensize = gam - .get_canvas_bounds(content) - .expect("Could not get canvas dimensions"); - Self { - gam, - _gam_token: gam_token, - content, - screensize, - - com: com::Com::new(&xns).unwrap(), - netmgr: net::NetManager::new(), - modals: modals::Modals::new(&xns).unwrap(), - pddb: pddb::Pddb::new(), - } - } - - fn root_actions(&self) -> Vec<&str> { - vec![ - "Turn WLAN on", - "Turn WLAN off", - "Manually add a network", - "Scan for networks", - "Network status", - ] - } - - fn set_wlan_state(&mut self, state: bool) -> Result<(), WLANError> { - match state { - true => { - // on - self.com.wlan_set_on()?; - self.netmgr.connection_manager_run()?; - } - false => { - // off - self.netmgr.connection_manager_stop()?; - self.com.wlan_set_off()?; - } - }; - - Ok(()) - } - - fn add_new_ssid(&mut self) -> Result<(), WLANError> { - let connection_data = self - .modals - .alert_builder("Fill this form with your connection SSID and password.") - .field( - Some("SSID".to_string()), - Some(|text| { - if text.as_str().is_empty() { - return Some(xous_ipc::String::from_str("SSID cannot be empty")); - } - - None - }), - ) - .field( - Some("Password".to_string()), - Some(|text| { - if text.as_str().is_empty() { - return Some(xous_ipc::String::from_str("Password cannot be empty")); - } - - None - }), - ) - .build() - .unwrap(); - - let content = connection_data.content(); - - self.store_connection_info(content[0].as_str(), content[1].as_str()) - } - - fn store_connection_info(&mut self, ssid: &str, pass: &str) -> Result<(), WLANError> { - self.netmgr.connection_manager_stop().unwrap(); - match self.pddb.get( - net::AP_DICT_NAME, - &ssid, - None, - true, - true, - Some(com::api::WF200_PASS_MAX_LEN), - Some(|| {}), - ) { - Ok(mut entry) => { - match entry.write(&pass.as_bytes()) { - Ok(len) => { - if len != pass.len() { - Err(WLANError::PDDBWriteError(len, pass.len())) - } else { - // for now, we should always call flush at the end of a routine; perhaps in the - // future we'll have a timer that automatically syncs the pddb - entry.flush().expect("couldn't sync pddb cache"); - // restart the connection manager now that the key combo has been committed - self.netmgr.connection_manager_run().unwrap(); - Ok(()) - } - } - Err(e) => Err(WLANError::PDDBIoError(e)), - } - } - Err(e) => Err(WLANError::PDDBIoError(e)), - } - } - - fn scan_networks(&self) -> Result, WLANError> { - let scan_result = self.netmgr.wifi_get_ssid_list()?; - - Ok(scan_result - .iter() - .map(|ssid| ssid.name.to_string()) - .collect()) - } - - fn show_available_networks(&mut self) -> Result<(), WLANError> { - let networks = self.scan_networks()?; - let networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect(); - - if networks.is_empty() { - self.modals - .show_notification("No networks available.", None) - .unwrap(); - return Ok(()); - } - - self.modals.add_list(networks).unwrap(); - - let ssid = self.modals.get_radiobutton("Choose an action:").unwrap(); - - self.fill_password_for_ssid(&ssid) - } - - fn fill_password_for_ssid(&mut self, ssid: &str) -> Result<(), WLANError> { - let connection_data = self - .modals - .alert_builder(&format!("Fill this form with the password for {ssid}.")) - .field( - Some("Password".to_string()), - Some(|text| { - if text.as_str().is_empty() { - return Some(xous_ipc::String::from_str("Password cannot be empty")); - } - - None - }), - ) - .build() - .unwrap(); - - let content = connection_data.content(); - - self.store_connection_info(ssid, content[0].as_str()) - } - - fn network_status(&mut self) -> Result<(), WLANError> { - let status = self.com.wlan_status()?; - let ssid = match status.ssid { - Some(s) => s.name.to_string(), - None => "not connected".to_string(), - }; - - let ls = status.link_state; - let ip = &status.ipv4; - - let status_str = format!( - "Connection status: \n\n- SSID: {}\n- Link state: {:?}\n- IP: {}\n- Gateway: {}\n- Subnet mask: {}\n- DNS 1: {}\n- DNS 2: {}\n- DHCP state: {:?}", - ssid, - ls, - format_ip(ip.addr), - format_ip(ip.gtwy), - format_ip(ip.mask), - format_ip(ip.dns1), - format_ip(ip.dns2), - ip.dhcp, - ); - - self.modals.show_notification(&status_str, None).unwrap(); - Ok(()) - } - - fn draw_menu(&self) -> usize { - let actions = self.root_actions(); - - self.modals.add_list(actions).unwrap(); - self.modals.get_radiobutton("Choose an action:").unwrap(); - - self.modals.get_radio_index().unwrap() - } - - fn consume_menu_index(&mut self, idx: usize) { - let resp = match idx { - 0 => self.set_wlan_state(true), - 1 => self.set_wlan_state(false), - 2 => self.add_new_ssid(), - 3 => self.show_available_networks(), - 4 => self.network_status(), - _ => panic!("idx {idx} not covered"), - }; - - resp.unwrap_or_else(|error| self.show_error_modal(error)); - } - - fn show_error_modal(&self, e: WLANError) { - self.modals - .show_notification(format!("Error: {}", e).as_str(), None) - .unwrap() - } -} - -fn main() -> ! { - log_server::init_wait().unwrap(); - log::set_max_level(log::LevelFilter::Info); - log::info!("Hello world PID is {}", xous::process::id()); - - let xns = xous_names::XousNames::new().unwrap(); - - // Register the server with xous - let sid = xns - .register_name(SERVER_NAME_WLANMAN, None) - .expect("can't register server"); - - let mut hello = WLANMan::new(&xns, sid); - - loop { - let msg = xous::receive_message(sid).unwrap(); - log::debug!("Got message: {:?}", msg); - - match FromPrimitive::from_usize(msg.body.id()) { - Some(HelloOp::Redraw) => { - log::debug!("Got redraw"); - } - Some(HelloOp::Quit) => { - log::info!("Quitting application"); - break; - } - Some(HelloOp::Char) => { - let idx = hello.draw_menu(); - hello.consume_menu_index(idx); - } - _ => { - log::error!("Got unknown message"); - } - } - } - - log::info!("Quitting"); - xous::terminate_process(0) -} - -fn format_ip(src: [u8; 4]) -> String { - src.iter() - .map(|&id| id.to_string()) - .collect::>() - .join(".") -} From 31bfaa0928c3b4d50d6ec4863fdda31027151285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Sun, 12 Jun 2022 13:48:30 +0200 Subject: [PATCH 4/9] chore: remove wlanman app entry in cargo.toml members --- Cargo.lock | 21 --------------------- Cargo.toml | 1 - 2 files changed, 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 585ea2173..9038db236 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4022,27 +4022,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "wlanman" -version = "0.1.0" -dependencies = [ - "com", - "gam", - "graphics-server", - "locales", - "log", - "log-server", - "modals", - "net", - "num-derive", - "num-traits", - "pddb", - "tts-frontend", - "xous", - "xous-ipc 0.9.7", - "xous-names", -] - [[package]] name = "wycheproof-import" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index cbb476455..6424ce93c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,6 @@ members = [ "apps/hello", "apps/repl", "apps/vault", - "apps/wlanman", "services/libstd-test", "services/ffi-test", "services/tts", From cfbc6eeed3d6d40fd83f2197f039384ef2bb111f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Tue, 14 Jun 2022 16:22:23 +0200 Subject: [PATCH 5/9] feat: embed plain-text name in op num, implement stored network delete --- services/status/src/wifi.rs | 71 +++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/services/status/src/wifi.rs b/services/status/src/wifi.rs index 43eb34456..17a43d72b 100644 --- a/services/status/src/wifi.rs +++ b/services/status/src/wifi.rs @@ -11,7 +11,22 @@ enum WlanManOp { AddNetworkManually, ScanForNetworks, Status, - KnownNetworks = 8, + DeleteNetwork, + KnownNetworks = 9, +} + +impl Display for WlanManOp { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::TurnWlanOn => write!(f, "Turn WLAN on"), + Self::TurnWlanOff => write!(f, "Turn WLAN off"), + Self::AddNetworkManually => write!(f, "Manually add a network"), + Self::ScanForNetworks => write!(f, "Scan for networks"), + Self::Status => write!(f, "Network status"), + Self::DeleteNetwork => write!(f, "Delete network"), + Self::KnownNetworks => write!(f, "List known networks"), + } + } } #[derive(Debug)] @@ -70,16 +85,17 @@ impl WLANMan { } } - fn actions(&self) -> Vec<(&str, WlanManOp)> { + fn actions(&self) -> Vec { use WlanManOp::*; vec![ - ("Turn WLAN on", TurnWlanOn), - ("Turn WLAN off", TurnWlanOff), - ("Manually add a network", AddNetworkManually), - ("Scan for networks", ScanForNetworks), - ("Network status", Status), - ("List known networks", KnownNetworks), + TurnWlanOn, + TurnWlanOff, + AddNetworkManually, + ScanForNetworks, + Status, + KnownNetworks, + DeleteNetwork, ] } @@ -266,14 +282,46 @@ impl WLANMan { Ok(()) } + fn delete_network(&mut self) -> Result<(), WLANError> { + let networks = self.pddb.list_keys(net::AP_DICT_NAME, None)?; + + if networks.is_empty() { + self.modals + .show_notification("No known networks.", None) + .unwrap(); + return Ok(()); + } + + let cancel_item = "Cancel"; + self.modals + .add_list(networks.iter().map(|s| s.as_str()).collect()) + .unwrap(); + self.modals.add_list_item(cancel_item).unwrap(); + + let ssid_to_be_deleted = self + .modals + .get_radiobutton("Choose network to delete:") + .unwrap(); + + if ssid_to_be_deleted.eq(cancel_item) { + return Ok(()); + } + + self + .pddb + .delete_key(net::AP_DICT_NAME, &ssid_to_be_deleted, None).map_err(|e| WLANError::PDDBIoError(e))?; + + self.pddb.sync().map_err(|e| WLANError::PDDBIoError(e)) + } + fn claim_menumatic_menu(&self, cid: xous::CID) { let mut menus = self .actions() .iter() .map(|action| gam::MenuItem { - name: xous_ipc::String::from_str(action.0), + name: xous_ipc::String::from_str(&action.to_string()), action_conn: Some(cid), - action_opcode: action.1.to_u32().unwrap(), + action_opcode: action.to_u32().unwrap(), action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), close_on_select: true, }) @@ -297,6 +345,7 @@ impl WLANMan { WlanManOp::AddNetworkManually => self.add_new_ssid(), WlanManOp::ScanForNetworks => self.show_available_networks(), WlanManOp::Status => self.network_status(), + WlanManOp::DeleteNetwork => self.delete_network(), WlanManOp::KnownNetworks => self.known_networks(), }; @@ -348,4 +397,4 @@ fn run_menu_thread() { } } } -} \ No newline at end of file +} From 851604f471c693c122d5ce8dbcf837780faddf35 Mon Sep 17 00:00:00 2001 From: bunnie Date: Tue, 20 Sep 2022 01:51:40 +0800 Subject: [PATCH 6/9] add translations and minor fixes to workflow --- apps/manifest.json | 8 -- services/status/locales/i18n.json | 120 +++++++++++++++++++++++++++++- services/status/src/mainmenu.rs | 17 +++-- services/status/src/wifi.rs | 60 ++++++++------- 4 files changed, 162 insertions(+), 43 deletions(-) diff --git a/apps/manifest.json b/apps/manifest.json index 14344c120..0e117f2bb 100644 --- a/apps/manifest.json +++ b/apps/manifest.json @@ -21,14 +21,6 @@ } } }, - "wlanman": { - "context_name": "WLAN manager", - "menu_name": { - "appmenu.wlanman": { - "en": "WLAN manager" - } - } - }, "vault": { "context_name": "Key Vault", "menu_name": { diff --git a/services/status/locales/i18n.json b/services/status/locales/i18n.json index acc849b39..983e60cc1 100644 --- a/services/status/locales/i18n.json +++ b/services/status/locales/i18n.json @@ -139,8 +139,10 @@ "en-tts": "Sleep now" }, "mainmenu.wifimenu": { - "en": "Wi-Fi menu", - "en-tts": "Wi-Fi menu" + "en": "WiFi menu", + "en-tts": "WiFi menu", + "ja": "WiFiメニュー", + "zh": "无线网络菜单" }, "mainmenu.backlighton": { "en": "Backlight on", @@ -250,6 +252,120 @@ "zh": "外壳聊天", "en-tts": "Shellchat" }, + "wlan.turnon": { + "en": "Turn WiFi on", + "ja": "Wi-Fiをオンにする", + "zh": "打开wifi", + "en-tts": "Turn WiFi on" + }, + "wlan.turnoff": { + "en": "Turn WiFi off", + "ja": "Wi-Fiをオフにする", + "zh": "关闭无线网络", + "en-tts": "Turn WiFi off" + }, + "wlan.manual_add": { + "en": "Manually add a network", + "ja": "ネットワークを手動で追加する", + "zh": "手动添加网络", + "en-tts": "Manually add a network" + }, + "wlan.scan": { + "en": "Scan for networks", + "ja": "ネットワークのスキャン", + "zh": "扫描网络", + "en-tts": "Scan for networks" + }, + "wlan.status": { + "en": "Network status", + "ja": "ネットワークの状態", + "zh": "网络状态", + "en-tts": "Network status" + }, + "wlan.delete": { + "en": "Delete network", + "ja": "ネットワークを削除", + "zh": "删除网络", + "en-tts": "Delete network" + }, + "wlan.list_known": { + "en": "List known networks", + "ja": "既知のネットワークを一覧表示する", + "zh": "列出已知网络", + "en-tts": "List known networks" + }, + "wlan.ssid_entry": { + "en": "Fill in SSID and password:", + "ja": "SSID とパスワードを入力します:", + "zh": "填写SSID和密码:", + "en-tts": "Fill in SSID and password:" + }, + "wlan.ssid": { + "en": "SSID", + "ja": "SSID", + "zh": "SSID", + "en-tts": "SSID" + }, + "wlan.password": { + "en": "Password", + "ja": "パスワード", + "zh": "密码", + "en-tts": "Password" + }, + "wlan.no_networks": { + "en": "No networks available.", + "ja": "利用可能なネットワークがありません。", + "zh": "没有可用的网络。", + "en-tts": "No networks available." + }, + "wlan.ssid_choose": { + "en": "Choose a network:", + "ja": "ネットワークを選択してください:", + "zh": "选择网络:", + "en-tts": "Choose a network:" + }, + "wlan.ssid_password": { + "en": "Please enter the password for {ssid}:", + "ja": "{ssid} のパスワードを入力してください:", + "zh": "请输入 {ssid} 的密码:", + "en-tts": "Please enter the password for {ssid}:" + }, + "wlan.password_empty": { + "en": "Password field cannot be empty", + "ja": "パスワードを空にすることはできません", + "zh": "密码不能为空", + "en-tts": "Password field cannot be empty" + }, + "wlan.no_known_networks": { + "en": "No known networks.", + "ja": "既知のネットワークはありません。", + "zh": "没有已知的网络。", + "en-tts": "No known networks." + }, + "wlan.known_networks": { + "en": "Known networks:\n", + "ja": "既知のネットワーク:\n", + "zh": "已知网络:\n", + "en-tts": "Known networks:\n" + }, + "wlan.cancel": { + "en": "Cancel", + "ja": "キャンセル", + "zh": "取消", + "en-tts": "Cancel" + }, + "wlan.choose_delete": { + "en": "Choose a network to delete:", + "ja": "削除するネットワークを選択してください:", + "zh": "选择要删除的网络:", + "en-tts": "Choose a network to delete:" + }, + "wlan.error": { + "en": "Error", + "ja": "エラー", + "zh": "错误", + "en-tts": "Error" + }, "rtc.try_ntp": { "en": "Attempt to automatically set time with NTP?", "ja": "NTPで時間を設定しようとしますか?", diff --git a/services/status/src/mainmenu.rs b/services/status/src/mainmenu.rs index 46eef0f72..ab7d693ac 100644 --- a/services/status/src/mainmenu.rs +++ b/services/status/src/mainmenu.rs @@ -50,14 +50,6 @@ pub fn create_main_menu(keys: Arc>, menu_management_sid: xous::S // close_on_select: true, // }); - menuitems.push(MenuItem { - name: String::from_str(t!("mainmenu.wifimenu", xous::LANG)), - action_conn: Some(status_conn), - action_opcode: StatusOpcode::WifiMenu.to_u32().unwrap(), - action_payload: MenuPayload::Scalar([0, 0, 0, 0]), - close_on_select: true, - }); - menuitems.push(MenuItem { name: String::from_str(t!("mainmenu.sleep", xous::LANG)), action_conn: Some(status_conn), @@ -148,6 +140,15 @@ pub fn create_main_menu(keys: Arc>, menu_management_sid: xous::S action_payload: MenuPayload::Scalar([0, 0, 0, 0]), close_on_select: true, }); + + menuitems.push(MenuItem { + name: String::from_str(t!("mainmenu.wifimenu", xous::LANG)), + action_conn: Some(status_conn), + action_opcode: StatusOpcode::WifiMenu.to_u32().unwrap(), + action_payload: MenuPayload::Scalar([0, 0, 0, 0]), + close_on_select: true, + }); + if key_init { menuitems.push(MenuItem { name: String::from_str(t!("mainmenu.prep_backup", xous::LANG)), diff --git a/services/status/src/wifi.rs b/services/status/src/wifi.rs index 17a43d72b..1eb19d187 100644 --- a/services/status/src/wifi.rs +++ b/services/status/src/wifi.rs @@ -4,6 +4,8 @@ use num_traits::*; use std::io::Write; +use locales::t; + #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, PartialEq, PartialOrd)] enum WlanManOp { TurnWlanOn = 3, @@ -18,13 +20,13 @@ enum WlanManOp { impl Display for WlanManOp { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::TurnWlanOn => write!(f, "Turn WLAN on"), - Self::TurnWlanOff => write!(f, "Turn WLAN off"), - Self::AddNetworkManually => write!(f, "Manually add a network"), - Self::ScanForNetworks => write!(f, "Scan for networks"), - Self::Status => write!(f, "Network status"), - Self::DeleteNetwork => write!(f, "Delete network"), - Self::KnownNetworks => write!(f, "List known networks"), + Self::TurnWlanOn => write!(f, "{}", t!("wlan.turnon", xous::LANG)), + Self::TurnWlanOff => write!(f, "{}", t!("wlan.turnoff", xous::LANG)), + Self::AddNetworkManually => write!(f, "{}", t!("wlan.manual_add", xous::LANG)), + Self::ScanForNetworks => write!(f, "{}", t!("wlan.scan", xous::LANG)), + Self::Status => write!(f, "{}", t!("wlan.status", xous::LANG)), + Self::DeleteNetwork => write!(f, "{}", t!("wlan.delete", xous::LANG)), + Self::KnownNetworks => write!(f, "{}", t!("wlan.list_known", xous::LANG)), } } } @@ -119,7 +121,7 @@ impl WLANMan { fn add_new_ssid(&mut self) -> Result<(), WLANError> { let connection_data = self .modals - .alert_builder("Fill this form with your connection SSID and password.") + .alert_builder(t!("wlan.ssid_entry", xous::LANG)) .field( Some("SSID".to_string()), Some(|text| { @@ -131,7 +133,7 @@ impl WLANMan { }), ) .field( - Some("Password".to_string()), + Some(t!("wlan.password", xous::LANG).to_string()), Some(|text| { if text.as_str().is_empty() { return Some(xous_ipc::String::from_str("Password cannot be empty")); @@ -195,14 +197,14 @@ impl WLANMan { if networks.is_empty() { self.modals - .show_notification("No networks available.", None) + .show_notification(t!("wlan.no_networks", xous::LANG), None) .unwrap(); return Ok(()); } self.modals.add_list(networks).unwrap(); - let ssid = self.modals.get_radiobutton("Choose an action:").unwrap(); + let ssid = self.modals.get_radiobutton(t!("wlan.ssid_choose", xous::LANG)).unwrap(); self.fill_password_for_ssid(&ssid) } @@ -210,12 +212,12 @@ impl WLANMan { fn fill_password_for_ssid(&mut self, ssid: &str) -> Result<(), WLANError> { let connection_data = self .modals - .alert_builder(&format!("Fill this form with the password for {ssid}.")) + .alert_builder(&t!("wlan.ssid_password", xous::LANG).replace("{ssid}", ssid)) .field( - Some("Password".to_string()), + Some(t!("wlan.password", xous::LANG).to_string()), Some(|text| { if text.as_str().is_empty() { - return Some(xous_ipc::String::from_str("Password cannot be empty")); + return Some(xous_ipc::String::from_str(t!("wlan.password_empty", xous::LANG))); } None @@ -233,12 +235,14 @@ impl WLANMan { let status = self.com.wlan_status()?; let ssid = match status.ssid { Some(s) => s.name.to_string(), - None => "not connected".to_string(), + None => t!("stats.disconnected", xous::LANG).to_string(), }; let ls = status.link_state; let ip = &status.ipv4; + // TODO: make a proper translation for this. But, I think for now, this is a fairly + // technical screen that we can leave in English. let status_str = format!( "Connection status: \n\n - SSID: {}\n - Link state: {:?}\n - IP: {}\n - Gateway: {}\n - Subnet mask: {}\n - DNS 1: {}\n - DNS 2: {}\n - DHCP state: {:?}", ssid, @@ -256,9 +260,12 @@ impl WLANMan { } fn known_networks(&self) -> Result<(), WLANError> { - let networks = self.pddb.list_keys(net::AP_DICT_NAME, None)?; + let networks = match self.pddb.list_keys(net::AP_DICT_NAME, None) { + Ok(list) => list, + Err(_) => Vec::new(), + }; - let mut networks_string = String::from("No known networks."); + let mut networks_string = String::from(t!("wlan.no_known_networks", xous::LANG)); if networks.is_empty() { self.modals @@ -267,7 +274,7 @@ impl WLANMan { return Ok(()); } - networks_string = String::from("Known networks:\n"); + networks_string = String::from(t!("wlan.known_networks", xous::LANG)); networks_string += &networks .iter() @@ -283,16 +290,19 @@ impl WLANMan { } fn delete_network(&mut self) -> Result<(), WLANError> { - let networks = self.pddb.list_keys(net::AP_DICT_NAME, None)?; + let networks = match self.pddb.list_keys(net::AP_DICT_NAME, None) { + Ok(list) => list, + Err(_) => Vec::new(), + }; if networks.is_empty() { self.modals - .show_notification("No known networks.", None) + .show_notification(t!("wlan.no_known_networks", xous::LANG), None) .unwrap(); return Ok(()); } - let cancel_item = "Cancel"; + let cancel_item = t!("wlan.cancel", xous::LANG); self.modals .add_list(networks.iter().map(|s| s.as_str()).collect()) .unwrap(); @@ -300,13 +310,13 @@ impl WLANMan { let ssid_to_be_deleted = self .modals - .get_radiobutton("Choose network to delete:") + .get_radiobutton(t!("wlan.choose_delete", xous::LANG)) .unwrap(); if ssid_to_be_deleted.eq(cancel_item) { return Ok(()); } - + self .pddb .delete_key(net::AP_DICT_NAME, &ssid_to_be_deleted, None).map_err(|e| WLANError::PDDBIoError(e))?; @@ -328,7 +338,7 @@ impl WLANMan { .collect::>(); menus.push(gam::MenuItem { - name: xous_ipc::String::from_str("Close menu"), + name: xous_ipc::String::from_str(t!("mainmenu.closemenu", xous::LANG)), action_conn: None, action_opcode: 0, action_payload: gam::MenuPayload::Scalar([0, 0, 0, 0]), @@ -354,7 +364,7 @@ impl WLANMan { fn show_error_modal(&self, e: WLANError) { self.modals - .show_notification(format!("Error: {}", e).as_str(), None) + .show_notification(format!("{}: {}", t!("wlan.error", xous::LANG), e).as_str(), None) .unwrap() } } From d561e07beb0325af49ec9f586303ce28258e9ff9 Mon Sep 17 00:00:00 2001 From: bunnie Date: Tue, 20 Sep 2022 15:54:43 +0800 Subject: [PATCH 7/9] bump release notes --- RELEASE-v0.9.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE-v0.9.md b/RELEASE-v0.9.md index 75d93feee..679bafba2 100644 --- a/RELEASE-v0.9.md +++ b/RELEASE-v0.9.md @@ -252,6 +252,8 @@ perform the Xous firmware upgrade. This requires running manual update commands, - highly experimental TLS support when the "tls" feature is enabled. Relies on a pure-Rust implementation of `ring` located in the `ring-xous` fork. - performance monitoring framework added. see `tools/perflib` and `services/shellchat/net_cmd.rs` for examples of how to use it. - HOTP support added to `vault`. HOTPs display in the TOTP window with the notation "HOTP" next to them, and they auto-increment on autotype. When creating new items in TOTP you are given the option to make an HOTP record, and you can convert between the two by editing the record and changing the bottom line from totp to hotp and vice-versa. The `timestep` field is re-used from the TOTP record to store the `count` for HOTP. +- `ball` app demo removed from default build +- WLAN submenu added, thanks to a huge effort by @gsora. ## Roadmap to 1.0 From ec8a16f3d75cc28dd330ce84af3d98482e34c4a8 Mon Sep 17 00:00:00 2001 From: bunnie Date: Tue, 20 Sep 2022 15:54:48 +0800 Subject: [PATCH 8/9] touch up some UI interactions --- services/status/locales/i18n.json | 36 +++++++++++++++---------------- services/status/src/wifi.rs | 32 ++++++++++++++++----------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/services/status/locales/i18n.json b/services/status/locales/i18n.json index 983e60cc1..7083f8f3f 100644 --- a/services/status/locales/i18n.json +++ b/services/status/locales/i18n.json @@ -139,10 +139,10 @@ "en-tts": "Sleep now" }, "mainmenu.wifimenu": { - "en": "WiFi menu", - "en-tts": "WiFi menu", - "ja": "WiFiメニュー", - "zh": "无线网络菜单" + "en": "WiFi submenu...", + "en-tts": "WiFi submenu...", + "ja": "WiFiメニュー...", + "zh": "无线网络菜单..." }, "mainmenu.backlighton": { "en": "Backlight on", @@ -199,10 +199,10 @@ "en-tts": "Set timezone" }, "mainmenu.pddb": { - "en": "PDDB Submenu", + "en": "PDDB submenu...", "ja": "PDDBサブメニュー", "zh": "PDDB子菜单", - "en-tts": "PDDB submenu" + "en-tts": "PDDB submenu..." }, "mainmenu.app": { "en": "Switch to App...", @@ -301,16 +301,16 @@ "en-tts": "Fill in SSID and password:" }, "wlan.ssid": { - "en": "SSID", - "ja": "SSID", - "zh": "SSID", - "en-tts": "SSID" + "en": "[ SSID ]", + "ja": "[ SSID ]", + "zh": "[ SSID ]", + "en-tts": "[ SSID ]" }, "wlan.password": { - "en": "Password", - "ja": "パスワード", - "zh": "密码", - "en-tts": "Password" + "en": "[ password ]", + "ja": "[ パスワード ]", + "zh": "[ 密码 ]", + "en-tts": "[ password ]" }, "wlan.no_networks": { "en": "No networks available.", @@ -349,10 +349,10 @@ "en-tts": "Known networks:\n" }, "wlan.cancel": { - "en": "Cancel", - "ja": "キャンセル", - "zh": "取消", - "en-tts": "Cancel" + "en": "❌ Cancel request ❌", + "ja": "❌ キャンセル ❌", + "zh": "❌ 取消请求 ❌", + "en-tts": "Cancel the requested action" }, "wlan.choose_delete": { "en": "Choose a network to delete:", diff --git a/services/status/src/wifi.rs b/services/status/src/wifi.rs index 1eb19d187..0f0458b24 100644 --- a/services/status/src/wifi.rs +++ b/services/status/src/wifi.rs @@ -8,13 +8,13 @@ use locales::t; #[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, PartialEq, PartialOrd)] enum WlanManOp { - TurnWlanOn = 3, - TurnWlanOff, - AddNetworkManually, - ScanForNetworks, + ScanForNetworks = 3, Status, + AddNetworkManually, + KnownNetworks, DeleteNetwork, - KnownNetworks = 9, + TurnWlanOn, + TurnWlanOff = 9, } impl Display for WlanManOp { @@ -91,13 +91,13 @@ impl WLANMan { use WlanManOp::*; vec![ - TurnWlanOn, - TurnWlanOff, - AddNetworkManually, ScanForNetworks, Status, + AddNetworkManually, KnownNetworks, DeleteNetwork, + TurnWlanOn, + TurnWlanOff, ] } @@ -193,7 +193,7 @@ impl WLANMan { fn show_available_networks(&mut self) -> Result<(), WLANError> { let networks = self.scan_networks()?; - let networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect(); + let mut networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect(); if networks.is_empty() { self.modals @@ -202,10 +202,16 @@ impl WLANMan { return Ok(()); } + networks.push(t!("wlan.cancel", xous::LANG)); + self.modals.add_list(networks).unwrap(); let ssid = self.modals.get_radiobutton(t!("wlan.ssid_choose", xous::LANG)).unwrap(); + if ssid == t!("wlan.cancel", xous::LANG) { + return Ok(()) + } + self.fill_password_for_ssid(&ssid) } @@ -244,7 +250,7 @@ impl WLANMan { // TODO: make a proper translation for this. But, I think for now, this is a fairly // technical screen that we can leave in English. let status_str = format!( - "Connection status: \n\n - SSID: {}\n - Link state: {:?}\n - IP: {}\n - Gateway: {}\n - Subnet mask: {}\n - DNS 1: {}\n - DNS 2: {}\n - DHCP state: {:?}", + "Connection status: \n\n ▪ SSID: {}\n ▪ Link state: {:?}\n ▪ IP: {}\n ▪ Gateway: {}\n ▪ Subnet mask: {}\n ▪ DNS 1: {}\n ▪ DNS 2: {}\n ▪ DHCP state: {:?}", ssid, ls, format_ip(ip.addr), @@ -278,7 +284,7 @@ impl WLANMan { networks_string += &networks .iter() - .map(|s| format!(" - {}", s)) + .map(|s| format!(" ▪ {}", s)) .collect::>() .join("\n"); @@ -396,9 +402,11 @@ fn run_menu_thread() { match FromPrimitive::from_usize(msg.body.id()) { Some(other) => { - if other >= WlanManOp::TurnWlanOn && other <= WlanManOp::KnownNetworks { + if other >= WlanManOp::ScanForNetworks && other <= WlanManOp::TurnWlanOff { hello.consume_menu_action(other); continue; + } else { + log::warn!("Received out of range opcode. Did WlanManOp get re-ordered, but the bounds check was not updated as well?"); } } From 035bdaf98628674f435a0cd696abbc1669c992cb Mon Sep 17 00:00:00 2001 From: bunnie Date: Tue, 20 Sep 2022 19:36:55 +0800 Subject: [PATCH 9/9] complete wifi on/off interface fully integrates wifi on and off commands. measured standby power when wifi is off is "immeasurable" (0 mA), so probably < 1-2mA. fix ui transitions so that the off request is respected, through sleep/resume cycles as well. --- services/com/src/lib.rs | 13 +++- services/com/src/main.rs | 30 +++++++- services/net/src/connection_manager.rs | 95 ++++++++++++++++++++++---- services/net/src/lib.rs | 10 +++ services/net/src/main.rs | 28 ++++++++ services/status/locales/i18n.json | 6 ++ services/status/src/main.rs | 18 +++-- services/status/src/wifi.rs | 6 +- 8 files changed, 179 insertions(+), 27 deletions(-) diff --git a/services/com/src/lib.rs b/services/com/src/lib.rs index 951639c95..4a5aacda4 100644 --- a/services/com/src/lib.rs +++ b/services/com/src/lib.rs @@ -451,10 +451,11 @@ impl Com { } } + /// This blocks until the COM responds from initializing the Wifi chip pub fn wlan_set_on(&mut self) -> Result { send_message( self.conn, - Message::new_scalar(Opcode::WlanOn.to_usize().unwrap(), 0, 0, 0, 0), + Message::new_blocking_scalar(Opcode::WlanOn.to_usize().unwrap(), 0, 0, 0, 0), ) } @@ -505,7 +506,7 @@ impl Com { /// Note: applications should poll the `NetManager::read_wifi_state()` call for wifi status information, not the COM directly. /// This is because the `wlan_status` call is fairly heavy weight, and the `NetManager::read_wifi_state()` will cache /// this information making the status check lighter-weight overall. - pub fn wlan_status(&mut self) -> Result { + pub fn wlan_status(&self) -> Result { let status = WlanStatusIpc::default(); let mut buf = Buffer::into_buf(status).or(Err(xous::Error::InternalError))?; buf.lend_mut(self.conn, Opcode::WlanStatus.to_u32().unwrap()).or(Err(xous::Error::InternalError))?; @@ -616,6 +617,14 @@ impl Com { Err(xous::Error::InternalError) } } + pub fn wlan_is_reset_hold(&self) -> Result { + let status = self.wlan_status()?; + if status.link_state == com_rs_ref::LinkState::ResetHold { + Ok(true) + } else { + Ok(false) + } + } pub fn ints_enable(&self, int_list: &[ComIntSources]) { let mut mask_val: u16 = 0; diff --git a/services/com/src/main.rs b/services/com/src/main.rs index c99662797..bd1245b04 100644 --- a/services/com/src/main.rs +++ b/services/com/src/main.rs @@ -891,11 +891,37 @@ fn main() -> ! { buffer.replace(ssid_ret).unwrap(); } Some(Opcode::WlanOn) => { - info!("TODO: implement WlanOn"); com.txrx(ComState::WLAN_ON.verb); + // re-sync the link, because the COM will take about a second to reload the Wifi drivers + #[cfg(not(any(windows, unix)))] // avoid errors in hosted mode + { + ticktimer.sleep_ms(100).unwrap(); // give the EC a moment to de-chatter + let mut attempts = 0; + let ping_value = 0xaeedu16; // for various reasons, this is a string that is "unlikely" to happen randomly + loop { + com.txrx(ComState::LINK_PING.verb); + com.txrx(ping_value); + let pong = com.wait_txrx(ComState::LINK_READ.verb, Some(150)); // this should "stall" until the EC comes out of reset + let phase = com.wait_txrx(ComState::LINK_READ.verb, Some(150)); + if pong == !ping_value && + phase == 0x600d { // 0x600d is a hard-coded constant. It's included to confirm that we aren't "wedged" just sending one value back at us + log::info!("Wifi on: link established"); + break; + } else { + log::info!("Wifi on: establishing link sync, attempt {} [{:04x}/{:04x}]", attempts, pong, phase); + com.txrx(ComState::LINK_SYNC.verb); + ticktimer.sleep_ms(200).unwrap(); + attempts += 1; + } + if attempts > 50 { + log::error!("EC didn't sync after wifi on...continuing and praying for the best."); + break; + } + } + } + xous::return_scalar(msg.sender, 1).ok(); } Some(Opcode::WlanOff) => { - info!("TODO: implement WlanOff"); com.txrx(ComState::WLAN_OFF.verb); } Some(Opcode::WlanRssi) => msg_blocking_scalar_unpack!(msg, _, _, _, _, { diff --git a/services/net/src/connection_manager.rs b/services/net/src/connection_manager.rs index cb345e77c..372225767 100644 --- a/services/net/src/connection_manager.rs +++ b/services/net/src/connection_manager.rs @@ -24,6 +24,8 @@ pub(crate) enum ConnectionManagerOpcode { Run, Poll, Stop, + DisconnectAndStop, + WifiOnAndRun, SubscribeWifiStats, UnsubWifiStats, FetchSsidList, @@ -48,6 +50,7 @@ enum WifiState { InvalidAuth, Connected, Disconnected, + Off, Error } #[derive(Eq, PartialEq)] @@ -191,6 +194,10 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc { + // wifi was manually forced "off", leave it off; presume connection manager is also stopped. + wifi_state = WifiState::Off; + } _ => { // should approximately be a "disconnected" state. match wifi_state { WifiState::Connected => { @@ -239,10 +246,14 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc { log::info!("{:?}", source); - ssid_list.clear(); // clear the ssid list because a likely cause of disconnect is we've moved out of range - com.set_ssid_scanning(true).unwrap(); - scan_state = SsidScanState::Scanning; - wifi_state = WifiState::Disconnected; + if wifi_state != WifiState::Off { + ssid_list.clear(); // clear the ssid list because a likely cause of disconnect is we've moved out of range + com.set_ssid_scanning(true).unwrap(); + scan_state = SsidScanState::Scanning; + wifi_state = WifiState::Disconnected; + } else { + log::info!("Wifi intent is off. Ignoring disconnect interrupt."); + } }, ComIntSources::WlanSsidScanUpdate => { log::info!("{:?}", source); @@ -273,18 +284,22 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc { log::info!("{:?}", source); @@ -392,6 +407,10 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc { + // this state should not be reachable + log::warn!("Wifi state was manually forced off; did you remember to turn it on before setting the connection manager to RUN?"); + } } } } @@ -467,6 +486,54 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc msg_scalar_unpack!(msg, _, _, _, _, { run.store(false, Ordering::SeqCst); }), + Some(ConnectionManagerOpcode::WifiOnAndRun) => msg_scalar_unpack!(msg, _, _, _, _, { + com.wlan_set_on().expect("couldn't turn on wifi"); + wifi_state = WifiState::Disconnected; + ssid_list.clear(); + com.set_ssid_scanning(true).unwrap(); + scan_state = SsidScanState::Scanning; + intervals_without_activity = 0; + scan_count = 0; + // this will force the UI to transition from 'WiFi Off' -> 'Not connected' + wifi_stats_cache = WlanStatus { + ssid: None, + link_state: com_rs_ref::LinkState::Disconnected, + ipv4: com::Ipv4Conf::default(), + }; + log::debug!("stats update: {:?}", wifi_stats_cache); + for &sub in status_subscribers.keys() { + let buf = Buffer::into_buf(com::WlanStatusIpc::from_status(wifi_stats_cache)).or(Err(xous::Error::InternalError)).unwrap(); + buf.send(sub, WifiStateCallback::Update.to_u32().unwrap()).or(Err(xous::Error::InternalError)).unwrap(); + } + if !run.swap(true, Ordering::SeqCst) { + if !pumping.load(Ordering::SeqCst) { // avoid having multiple pump messages being sent if a user tries to rapidly toggle the run/stop switch + send_message(run_cid, Message::new_scalar(PumpOp::Pump.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't kick off next poll"); + } + } + }), + Some(ConnectionManagerOpcode::DisconnectAndStop) => { + run.store(false, Ordering::SeqCst); + wifi_state = WifiState::Off; + com.wlan_leave().expect("couldn't issue leave command"); + ssid_list.clear(); + intervals_without_activity = 0; + scan_count = 0; + com.set_ssid_scanning(false).unwrap(); + scan_state = SsidScanState::Idle; + + tt.sleep_ms(250).unwrap(); // give a moment to clean-up after leave before turning things off + com.wlan_set_off().expect("couldn't turn off wifi"); + wifi_stats_cache = WlanStatus { + ssid: None, + link_state: com_rs_ref::LinkState::ResetHold, + ipv4: com::Ipv4Conf::default(), + }; + log::debug!("stats update: {:?}", wifi_stats_cache); + for &sub in status_subscribers.keys() { + let buf = Buffer::into_buf(com::WlanStatusIpc::from_status(wifi_stats_cache)).or(Err(xous::Error::InternalError)).unwrap(); + buf.send(sub, WifiStateCallback::Update.to_u32().unwrap()).or(Err(xous::Error::InternalError)).unwrap(); + } + } Some(ConnectionManagerOpcode::EcReset) => msg_scalar_unpack!(msg, _, _, _, _, { // this opcode is used by other processes to inform us that the net link was reset by something other than us. // (e.g. an update) diff --git a/services/net/src/lib.rs b/services/net/src/lib.rs index 142722c29..1acda17c1 100644 --- a/services/net/src/lib.rs +++ b/services/net/src/lib.rs @@ -170,6 +170,16 @@ impl NetManager { Message::new_scalar(Opcode::ConnMgrStartStop.to_usize().unwrap(), 1, 0,0, 0) ).map(|_| ()) } + pub fn connection_manager_wifi_off_and_stop(&self) -> Result<(), xous::Error> { + send_message(self.netconn.conn(), + Message::new_scalar(Opcode::ConnMgrStartStop.to_usize().unwrap(), 2, 0,0, 0) + ).map(|_| ()) + } + pub fn connection_manager_wifi_on_and_run(&self) -> Result<(), xous::Error> { + send_message(self.netconn.conn(), + Message::new_scalar(Opcode::ConnMgrStartStop.to_usize().unwrap(), 3, 0,0, 0) + ).map(|_| ()) + } } impl Drop for NetManager { fn drop(&mut self) { diff --git a/services/net/src/main.rs b/services/net/src/main.rs index d5a557c77..b772691c5 100644 --- a/services/net/src/main.rs +++ b/services/net/src/main.rs @@ -2118,6 +2118,34 @@ fn main() -> ! { ), ) .expect("couldn't send stop message"); + } else if code == 2 { + send_message( + cm_cid, + Message::new_scalar( + connection_manager::ConnectionManagerOpcode::DisconnectAndStop + .to_usize() + .unwrap(), + 0, + 0, + 0, + 0, + ), + ) + .expect("couldn't send disconnect and stop message"); + } else if code == 3 { + send_message( + cm_cid, + Message::new_scalar( + connection_manager::ConnectionManagerOpcode::WifiOnAndRun + .to_usize() + .unwrap(), + 0, + 0, + 0, + 0, + ), + ) + .expect("couldn't send disconnect and stop message"); } else { log::error!("Got incorrect start/stop code: {}", code); } diff --git a/services/status/locales/i18n.json b/services/status/locales/i18n.json index 7083f8f3f..30c4b7c89 100644 --- a/services/status/locales/i18n.json +++ b/services/status/locales/i18n.json @@ -65,6 +65,12 @@ "zh": "没有连接", "en-tts": "Not connected" }, + "stats.wifi_off": { + "ja": "WiFiオフ", + "en": "WiFi off", + "zh": "WiFi关闭", + "en-tts": "WiFi off" + }, "stats.uptime": { "translator-note": "This needs to be a very short string, 2 chars max. Trailing space is necessary for English due to proportional font.", "ja": "稼働", diff --git a/services/status/src/main.rs b/services/status/src/main.rs index b5c681290..a0de16afe 100644 --- a/services/status/src/main.rs +++ b/services/status/src/main.rs @@ -733,11 +733,19 @@ fn wrapped_main() -> ! { ssid.rssi, ).unwrap(); } else { - write!( - &mut battstats_tv, - "{}", - t!("stats.disconnected", xous::LANG) - ).unwrap(); + if wifi_status.link_state == com_rs_ref::LinkState::ResetHold { + write!( + &mut battstats_tv, + "{}", + t!("stats.wifi_off", xous::LANG) + ).unwrap(); + } else { + write!( + &mut battstats_tv, + "{}", + t!("stats.disconnected", xous::LANG) + ).unwrap(); + } } } } diff --git a/services/status/src/wifi.rs b/services/status/src/wifi.rs index 0f0458b24..fa5d6fc0b 100644 --- a/services/status/src/wifi.rs +++ b/services/status/src/wifi.rs @@ -105,13 +105,11 @@ impl WLANMan { match state { true => { // on - self.com.wlan_set_on()?; - self.netmgr.connection_manager_run()?; + self.netmgr.connection_manager_wifi_on_and_run()?; } false => { // off - self.netmgr.connection_manager_stop()?; - self.com.wlan_set_off()?; + self.netmgr.connection_manager_wifi_off_and_stop()?; } };