status: add wifi management menu

This commit is contained in:
Gianguido Sorà 2022-06-10 12:20:39 +02:00
parent c5c868557a
commit 014dcfd612
5 changed files with 375 additions and 0 deletions

View file

@ -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)]

View file

@ -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": "バックライト点灯",

View file

@ -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::<com::WlanStatusIpc, _>().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

View file

@ -32,6 +32,14 @@ pub fn create_main_menu(keys: Arc<Mutex<RootKeys>>, 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),

351
services/status/src/wifi.rs Normal file
View file

@ -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<xous::Error> for WLANError {
fn from(v: xous::Error) -> Self {
Self::UnderlyingError(v)
}
}
impl From<std::io::Error> 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<Vec<String>, 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::<Vec<String>>()
.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::<Vec<gam::MenuItem>>();
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::<Vec<String>>()
.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");
}
}
}
}