Merge branch 'wlan-mgr' into main

This commit is contained in:
bunnie 2022-09-20 19:38:24 +08:00
commit ef65d1aa9f
11 changed files with 738 additions and 25 deletions

View file

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

View file

@ -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<xous::Result, xous::Error> {
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<WlanStatus, xous::Error> {
pub fn wlan_status(&self) -> Result<WlanStatus, xous::Error> {
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<bool, xous::Error> {
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;

View file

@ -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, _, _, _, _, {

View file

@ -42,6 +42,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] = &[
@ -55,6 +56,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

@ -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<AtomicU3
}
wifi_state = WifiState::Error;
}
LinkState::ResetHold => {
// 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<AtomicU3
}
ComIntSources::Disconnect => {
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<AtomicU3
activity_interval.store(0, Ordering::SeqCst);
// this is the "first" path -- it's hit immediately on connect.
// relay status updates to any subscribers that want to know if a state has changed
wifi_stats_cache = com.wlan_status().unwrap();
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 wifi_stats_cache.ipv4.dhcp == com_rs_ref::DhcpState::Bound {
wifi_state = WifiState::Connected;
if wifi_state != WifiState::Off {
wifi_stats_cache = com.wlan_status().unwrap();
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 wifi_stats_cache.ipv4.dhcp == com_rs_ref::DhcpState::Bound {
wifi_state = WifiState::Connected;
} else {
wifi_state = WifiState::WaitDhcp;
}
log::debug!("comint new wifi state: {:?}", wifi_state);
} else {
wifi_state = WifiState::WaitDhcp;
log::info!("Wifi intent is off, skipping wlan status query update.");
}
log::debug!("comint new wifi state: {:?}", wifi_state);
}
ComIntSources::WfxErr => {
log::info!("{:?}", source);
@ -392,6 +407,10 @@ pub(crate) fn connection_manager(sid: xous::SID, activity_interval: Arc<AtomicU3
buf.send(sub, WifiStateCallback::Update.to_u32().unwrap()).or(Err(xous::Error::InternalError)).unwrap();
}
}
WifiState::Off => {
// 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<AtomicU3
Some(ConnectionManagerOpcode::Stop) => 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)

View file

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

View file

@ -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);
}

View file

@ -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": "稼働",
@ -138,6 +144,12 @@
"zh": "睡眠模式",
"en-tts": "Sleep now"
},
"mainmenu.wifimenu": {
"en": "WiFi submenu...",
"en-tts": "WiFi submenu...",
"ja": "WiFiメニュー...",
"zh": "无线网络菜单..."
},
"mainmenu.backlighton": {
"en": "Backlight on",
"ja": "バックライト点灯",
@ -193,10 +205,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...",
@ -246,6 +258,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 request ❌",
"ja": "❌ キャンセル ❌",
"zh": "❌ 取消请求 ❌",
"en-tts": "Cancel the requested action"
},
"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で時間を設定しようとしますか?",

View file

@ -11,6 +11,7 @@ use kbdmenu::*;
mod app_autogen;
mod time;
mod ecup;
mod wifi;
use com::api::*;
use root_keys::api::{BackupOp, BackupKeyboardLayout};
@ -87,6 +88,9 @@ pub(crate) enum StatusOpcode {
BatteryDisconnect,
/// for returning wifi stats
WifiStats,
/// Raise the wifi menu
WifiMenu,
Quit,
}
@ -633,6 +637,8 @@ fn wrapped_main() -> ! {
}
});
wifi::start_background_thread();
pump_run.store(true, Ordering::Relaxed); // start status thread updating
loop {
let msg = xous::receive_message(status_sid).unwrap();
@ -727,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();
}
}
}
}
@ -753,6 +767,10 @@ fn wrapped_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

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

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

@ -0,0 +1,416 @@
use core::fmt::Display;
use num_traits::*;
use std::io::Write;
use locales::t;
#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive, PartialEq, PartialOrd)]
enum WlanManOp {
ScanForNetworks = 3,
Status,
AddNetworkManually,
KnownNetworks,
DeleteNetwork,
TurnWlanOn,
TurnWlanOff = 9,
}
impl Display for WlanManOp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
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)),
}
}
}
#[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<WlanManOp> {
use WlanManOp::*;
vec![
ScanForNetworks,
Status,
AddNetworkManually,
KnownNetworks,
DeleteNetwork,
TurnWlanOn,
TurnWlanOff,
]
}
fn set_wlan_state(&mut self, state: bool) -> Result<(), WLANError> {
match state {
true => {
// on
self.netmgr.connection_manager_wifi_on_and_run()?;
}
false => {
// off
self.netmgr.connection_manager_wifi_off_and_stop()?;
}
};
Ok(())
}
fn add_new_ssid(&mut self) -> Result<(), WLANError> {
let connection_data = self
.modals
.alert_builder(t!("wlan.ssid_entry", xous::LANG))
.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(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"));
}
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 mut networks: Vec<&str> = networks.iter().map(|s| s.as_str()).collect();
if networks.is_empty() {
self.modals
.show_notification(t!("wlan.no_networks", xous::LANG), None)
.unwrap();
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)
}
fn fill_password_for_ssid(&mut self, ssid: &str) -> Result<(), WLANError> {
let connection_data = self
.modals
.alert_builder(&t!("wlan.ssid_password", xous::LANG).replace("{ssid}", ssid))
.field(
Some(t!("wlan.password", xous::LANG).to_string()),
Some(|text| {
if text.as_str().is_empty() {
return Some(xous_ipc::String::from_str(t!("wlan.password_empty", xous::LANG)));
}
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 => 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,
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 = match self.pddb.list_keys(net::AP_DICT_NAME, None) {
Ok(list) => list,
Err(_) => Vec::new(),
};
let mut networks_string = String::from(t!("wlan.no_known_networks", xous::LANG));
if networks.is_empty() {
self.modals
.show_notification(&networks_string, None)
.unwrap();
return Ok(());
}
networks_string = String::from(t!("wlan.known_networks", xous::LANG));
networks_string += &networks
.iter()
.map(|s| format!(" ▪ {}", s))
.collect::<Vec<String>>()
.join("\n");
self.modals
.show_notification(&networks_string, None)
.unwrap();
Ok(())
}
fn delete_network(&mut self) -> Result<(), WLANError> {
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(t!("wlan.no_known_networks", xous::LANG), None)
.unwrap();
return Ok(());
}
let cancel_item = t!("wlan.cancel", xous::LANG);
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(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))?;
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.to_string()),
action_conn: Some(cid),
action_opcode: action.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(t!("mainmenu.closemenu", xous::LANG)),
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::DeleteNetwork => self.delete_network(),
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!("{}: {}", t!("wlan.error", xous::LANG), 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::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?");
}
}
_ => {
log::error!("Got unknown message");
}
}
}
}