WiFi fixes and improvements (#100)

This commit is contained in:
Ken Van Hoeylandt
2024-12-02 22:16:08 +01:00
committed by GitHub
parent 33bb742dfb
commit e9c02ab58e
18 changed files with 506 additions and 443 deletions
+2 -2
View File
@@ -38,7 +38,7 @@ typedef void (*AppOnShow)(App& app, lv_obj_t* parent);
typedef void (*AppOnHide)(App& app);
typedef void (*AppOnResult)(App& app, Result result, const Bundle& resultData);
typedef struct Manifest {
struct Manifest {
/**
* The identifier by which the app is launched by the system and other apps.
*/
@@ -83,7 +83,7 @@ typedef struct Manifest {
* Handle the result for apps that are launched
*/
const AppOnResult _Nullable onResult = nullptr;
} Manifest;
};
struct {
bool operator()(const Manifest* left, const Manifest* right) const { return left->name < right->name; }
@@ -6,10 +6,10 @@ typedef void (*OnWifiToggled)(bool enable);
typedef void (*OnConnectSsid)(const char* ssid);
typedef void (*OnDisconnect)();
typedef struct {
OnWifiToggled on_wifi_toggled;
OnConnectSsid on_connect_ssid;
OnDisconnect on_disconnect;
} WifiManageBindings;
struct Bindings{
OnWifiToggled onWifiToggled;
OnConnectSsid onConnectSsid;
OnDisconnect onDisconnect;
};
} // namespace
+33
View File
@@ -0,0 +1,33 @@
#include <Check.h>
#include "WifiManage.h"
namespace tt::app::wifimanage {
void State::setScanning(bool isScanning) {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
scanning = isScanning;
tt_check(mutex.release() == TtStatusOk);
}
void State::setRadioState(service::wifi::WifiRadioState state) {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
radioState = state;
tt_check(mutex.release() == TtStatusOk);
}
const std::vector<service::wifi::WifiApRecord>& State::lockApRecords() const {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
return apRecords;
}
void State::unlockApRecords() const {
tt_check(mutex.release() == TtStatusOk);
}
void State::updateApRecords() {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
apRecords = service::wifi::getScanResults();
tt_check(mutex.release() == TtStatusOk);
}
} // namespace
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "service/wifi/Wifi.h"
#include "Mutex.h"
namespace tt::app::wifimanage {
/**
* View's state
*/
class State {
Mutex mutex;
bool scanning;
service::wifi::WifiRadioState radioState;
std::vector<service::wifi::WifiApRecord> apRecords;
std::string connectSsid;
public:
State() {}
void setScanning(bool isScanning);
bool isScanning() const { return scanning; }
void setRadioState(service::wifi::WifiRadioState state);
service::wifi::WifiRadioState getRadioState() const { return radioState; }
void updateApRecords();
const std::vector<service::wifi::WifiApRecord>& lockApRecords() const;
void unlockApRecords() const;
void setConnectSsid(std::string ssid) { connectSsid = ssid; }
std::string getConnectSsid() const { return connectSsid; }
};
} // namespace
+215
View File
@@ -0,0 +1,215 @@
#include "View.h"
#include "Log.h"
#include "State.h"
#include "service/statusbar/Statusbar.h"
#include "service/wifi/Wifi.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
#include <string>
#include <set>
namespace tt::app::wifimanage {
#define TAG "wifi_main_view"
#define SPINNER_HEIGHT 40
static void on_enable_switch_changed(lv_event_t* event) {
lv_event_code_t code = lv_event_get_code(event);
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_target(event));
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
auto* bindings = static_cast<Bindings*>(lv_event_get_user_data(event));
bindings->onWifiToggled(is_on);
}
}
static void on_disconnect_pressed(lv_event_t* event) {
auto* bindings = static_cast<Bindings*>(lv_event_get_user_data(event));
bindings->onDisconnect();
}
// region Secondary updates
static void connect(lv_event_t* event) {
lv_obj_t* button = lv_event_get_current_target_obj(event);
// Assumes that the second child of the button is a label ... risky
lv_obj_t* label = lv_obj_get_child(button, 1);
// We get the SSID from the button label because it's safer than alloc'ing
// our own and passing it as the event data
const char* ssid = lv_label_get_text(label);
TT_LOG_I(TAG, "Clicked AP: %s", ssid);
auto* bindings = static_cast<Bindings*>(lv_event_get_user_data(event));
bindings->onConnectSsid(ssid);
}
void View::createNetworkButton(Bindings* bindings, const service::wifi::WifiApRecord& record) {
const char* icon = service::statusbar::getWifiStatusIconForRssi(record.rssi, record.auth_mode != WIFI_AUTH_OPEN);
lv_obj_t* ap_button = lv_list_add_btn(
networks_list,
icon,
record.ssid.c_str()
);
lv_obj_add_event_cb(ap_button, &connect, LV_EVENT_CLICKED, bindings);
}
void View::updateNetworkList(State* state, Bindings* bindings) {
lv_obj_clean(networks_list);
switch (state->getRadioState()) {
case service::wifi::WIFI_RADIO_ON_PENDING:
case service::wifi::WIFI_RADIO_ON:
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE: {
lv_obj_clear_flag(networks_label, LV_OBJ_FLAG_HIDDEN);
auto& ap_records = state->lockApRecords();
std::set<std::string> used_ssids;
if (!ap_records.empty()) {
for (auto& record : ap_records) {
if (used_ssids.find(record.ssid) == used_ssids.end()) {
createNetworkButton(bindings, record);
used_ssids.insert(record.ssid);
}
}
lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN);
} else if (state->isScanning()) {
lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* label = lv_label_create(networks_list);
lv_label_set_text(label, "No networks found.");
}
state->unlockApRecords();
break;
}
case service::wifi::WIFI_RADIO_OFF_PENDING:
case service::wifi::WIFI_RADIO_OFF: {
lv_obj_add_flag(networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(networks_label, LV_OBJ_FLAG_HIDDEN);
break;
}
}
}
void View::updateScanning(State* state) {
if (state->getRadioState() == service::wifi::WIFI_RADIO_ON && state->isScanning()) {
lv_obj_clear_flag(scanning_spinner, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(scanning_spinner, LV_OBJ_FLAG_HIDDEN);
}
}
void View::updateWifiToggle(State* state) {
lv_obj_clear_state(enable_switch, LV_STATE_ANY);
switch (state->getRadioState()) {
case service::wifi::WIFI_RADIO_ON:
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_add_state(enable_switch, LV_STATE_CHECKED);
break;
case service::wifi::WIFI_RADIO_ON_PENDING:
lv_obj_add_state(enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED);
break;
case service::wifi::WIFI_RADIO_OFF:
lv_obj_remove_state(enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED);
break;
case service::wifi::WIFI_RADIO_OFF_PENDING:
lv_obj_remove_state(enable_switch, LV_STATE_CHECKED);
lv_obj_add_state(enable_switch, LV_STATE_DISABLED);
break;
}
}
void View::updateConnectedAp(State* state, TT_UNUSED Bindings* bindings) {
switch (state->getRadioState()) {
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_clear_flag(connected_ap_container, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(connected_ap_label, state->getConnectSsid().c_str());
break;
default:
lv_obj_add_flag(connected_ap_container, LV_OBJ_FLAG_HIDDEN);
break;
}
}
// endregion Secondary updates
// region Main
void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) {
root = parent;
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_flex_grow(wrapper, 1);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
// Top row: enable/disable
lv_obj_t* switch_container = lv_obj_create(wrapper);
lv_obj_set_width(switch_container, LV_PCT(100));
lv_obj_set_height(switch_container, LV_SIZE_CONTENT);
lvgl::obj_set_style_no_padding(switch_container);
lvgl::obj_set_style_bg_invisible(switch_container);
lv_obj_t* enable_label = lv_label_create(switch_container);
lv_label_set_text(enable_label, "Wi-Fi");
lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID);
enable_switch = lv_switch_create(switch_container);
lv_obj_add_event_cb(enable_switch, on_enable_switch_changed, LV_EVENT_ALL, bindings);
lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID);
connected_ap_container = lv_obj_create(wrapper);
lv_obj_set_size(connected_ap_container, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(connected_ap_container, SPINNER_HEIGHT, 0);
lvgl::obj_set_style_no_padding(connected_ap_container);
lv_obj_set_style_border_width(connected_ap_container, 0, 0);
connected_ap_label = lv_label_create(connected_ap_container);
lv_obj_align(connected_ap_label, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_t* disconnect_button = lv_btn_create(connected_ap_container);
lv_obj_add_event_cb(disconnect_button, &on_disconnect_pressed, LV_EVENT_CLICKED, bindings);
lv_obj_t* disconnect_label = lv_label_create(disconnect_button);
lv_label_set_text(disconnect_label, "Disconnect");
lv_obj_align(disconnect_button, LV_ALIGN_RIGHT_MID, 0, 0);
// Networks
lv_obj_t* networks_header = lv_obj_create(wrapper);
lv_obj_set_size(networks_header, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(networks_header, SPINNER_HEIGHT, 0);
lvgl::obj_set_style_no_padding(networks_header);
lv_obj_set_style_border_width(networks_header, 0, 0);
networks_label = lv_label_create(networks_header);
lv_label_set_text(networks_label, "Networks");
lv_obj_align(networks_label, LV_ALIGN_LEFT_MID, 0, 0);
scanning_spinner = lv_spinner_create(networks_header);
lv_spinner_set_anim_params(scanning_spinner, 1000, 60);
lv_obj_set_size(scanning_spinner, SPINNER_HEIGHT, SPINNER_HEIGHT);
lv_obj_set_style_pad_top(scanning_spinner, 4, 0);
lv_obj_set_style_pad_bottom(scanning_spinner, 4, 0);
lv_obj_align_to(scanning_spinner, networks_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0);
networks_list = lv_obj_create(wrapper);
lv_obj_set_flex_flow(networks_list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(networks_list, LV_PCT(100));
lv_obj_set_height(networks_list, LV_SIZE_CONTENT);
lv_obj_set_style_pad_top(networks_list, 8, 0);
lv_obj_set_style_pad_bottom(networks_list, 8, 0);
}
void View::update(Bindings* bindings, State* state) {
updateWifiToggle(state);
updateScanning(state);
updateNetworkList(state, bindings);
updateConnectedAp(state, bindings);
}
} // namespace
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "app/App.h"
#include "Bindings.h"
#include "State.h"
#include "lvgl.h"
namespace tt::app::wifimanage {
class View {
private:
lv_obj_t* root = nullptr;
lv_obj_t* enable_switch = nullptr;
lv_obj_t* scanning_spinner = nullptr;
lv_obj_t* networks_label = nullptr;
lv_obj_t* networks_list = nullptr;
lv_obj_t* connected_ap_container = nullptr;
lv_obj_t* connected_ap_label = nullptr;
public:
View() {}
void init(const App& app, Bindings* bindings, lv_obj_t* parent);
void update(Bindings* bindings, State* state);
private:
void updateConnectedAp(State* state, TT_UNUSED Bindings* bindings);
void updateWifiToggle(State* state);
void updateScanning(State* state);
void updateNetworkList(State* state, Bindings* bindings);
void createNetworkButton(Bindings* bindings, const service::wifi::WifiApRecord& record);
};
} // namespace
+65 -80
View File
@@ -6,17 +6,14 @@
#include "service/loader/Loader.h"
#include "service/wifi/WifiSettings.h"
#include "lvgl/LvglSync.h"
#include "WifiManageStateUpdating.h"
#include "WifiManageView.h"
#include "View.h"
#include "State.h"
namespace tt::app::wifimanage {
#define TAG "wifi_manage"
// Forward declarations
static void event_callback(const void* message, void* context);
static void on_connect(const char* ssid) {
static void onConnect(const char* ssid) {
service::wifi::settings::WifiApSettings settings;
if (service::wifi::settings::load(ssid, &settings)) {
TT_LOG_I(TAG, "Connecting with known credentials");
@@ -30,79 +27,55 @@ static void on_connect(const char* ssid) {
}
}
static void on_disconnect() {
static void onDisconnect() {
service::wifi::disconnect();
}
static void on_wifi_toggled(bool enabled) {
static void onWifiToggled(bool enabled) {
service::wifi::setEnabled(enabled);
}
static WifiManage* wifi_manage_alloc() {
auto* wifi = static_cast<WifiManage*>(malloc(sizeof(WifiManage)));
wifi->wifi_subscription = nullptr;
wifi->mutex = tt_mutex_alloc(MutexTypeNormal);
wifi->state = (WifiManageState) {
.scanning = service::wifi::isScanning(),
.radio_state = service::wifi::getRadioState(),
.connect_ssid = { 0 },
.ap_records = { },
.ap_records_count = 0
WifiManage::WifiManage() {
bindings = (Bindings) {
.onWifiToggled = &onWifiToggled,
.onConnectSsid = &onConnect,
.onDisconnect = &onDisconnect
};
wifi->view_enabled = false;
wifi->bindings = (WifiManageBindings) {
.on_wifi_toggled = &on_wifi_toggled,
.on_connect_ssid = &on_connect,
.on_disconnect = &on_disconnect
};
return wifi;
}
static void wifi_manage_free(WifiManage* wifi) {
tt_mutex_free(wifi->mutex);
free(wifi);
void WifiManage::lock() {
tt_check(mutex.acquire(TtWaitForever) == TtStatusOk);
}
void lock(WifiManage* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_acquire(wifi->mutex, TtWaitForever);
void WifiManage::unlock() {
tt_check(mutex.release() == TtStatusOk);
}
void unlock(WifiManage* wifi) {
tt_assert(wifi);
tt_assert(wifi->mutex);
tt_mutex_release(wifi->mutex);
}
void request_view_update(WifiManage* wifi) {
lock(wifi);
if (wifi->view_enabled) {
void WifiManage::requestViewUpdate() {
lock();
if (isViewEnabled) {
if (lvgl::lock(1000)) {
view_update(&wifi->view, &wifi->bindings, &wifi->state);
view.update(&bindings, &state);
lvgl::unlock();
} else {
TT_LOG_E(TAG, "failed to lock lvgl");
}
}
unlock(wifi);
unlock();
}
static void wifi_manage_event_callback(const void* message, void* context) {
static void wifiManageEventCallback(const void* message, void* context) {
auto* event = (service::wifi::WifiEvent*)message;
auto* wifi = (WifiManage*)context;
TT_LOG_I(TAG, "Update with state %d", service::wifi::getRadioState());
state_set_radio_state(wifi, service::wifi::getRadioState());
wifi->getState().setRadioState(service::wifi::getRadioState());
switch (event->type) {
case tt::service::wifi::WifiEventTypeScanStarted:
state_set_scanning(wifi, true);
wifi->getState().setScanning(true);
break;
case tt::service::wifi::WifiEventTypeScanFinished:
state_set_scanning(wifi, false);
state_update_scanned_records(wifi);
wifi->getState().setScanning(false);
wifi->getState().updateApRecords();
break;
case tt::service::wifi::WifiEventTypeRadioStateOn:
if (!service::wifi::isScanning()) {
@@ -113,27 +86,25 @@ static void wifi_manage_event_callback(const void* message, void* context) {
break;
}
request_view_update(wifi);
wifi->requestViewUpdate();
}
static void app_show(App& app, lv_obj_t* parent) {
auto* wifi = (WifiManage*)app.getData();
void WifiManage::onShow(App& app, lv_obj_t* parent) {
PubSub* wifi_pubsub = service::wifi::getPubsub();
wifi->wifi_subscription = tt_pubsub_subscribe(wifi_pubsub, &wifi_manage_event_callback, wifi);
wifiSubscription = tt_pubsub_subscribe(wifi_pubsub, &wifiManageEventCallback, this);
// State update (it has its own locking)
state_set_radio_state(wifi, service::wifi::getRadioState());
state_set_scanning(wifi, service::wifi::isScanning());
state_update_scanned_records(wifi);
state.setRadioState(service::wifi::getRadioState());
state.setScanning(service::wifi::isScanning());
state.updateApRecords();
// View update
lock(wifi);
wifi->view_enabled = true;
strcpy((char*)wifi->state.connect_ssid, "Connected"); // TODO update with proper SSID
view_create(app, &wifi->view, &wifi->bindings, parent);
view_update(&wifi->view, &wifi->bindings, &wifi->state);
unlock(wifi);
lock();
isViewEnabled = true;
state.setConnectSsid("Connected"); // TODO update with proper SSID
view.init(app, &bindings, parent);
view.update(&bindings, &state);
unlock();
service::wifi::WifiRadioState radio_state = service::wifi::getRadioState();
bool can_scan = radio_state == service::wifi::WIFI_RADIO_ON ||
@@ -144,36 +115,50 @@ static void app_show(App& app, lv_obj_t* parent) {
}
}
static void app_hide(App& app) {
auto* wifi = (WifiManage*)app.getData();
lock(wifi);
void WifiManage::onHide(TT_UNUSED App& app) {
lock();
PubSub* wifi_pubsub = service::wifi::getPubsub();
tt_pubsub_unsubscribe(wifi_pubsub, wifi->wifi_subscription);
wifi->wifi_subscription = nullptr;
wifi->view_enabled = false;
unlock(wifi);
tt_pubsub_unsubscribe(wifi_pubsub, wifiSubscription);
wifiSubscription = nullptr;
isViewEnabled = false;
unlock();
}
static void app_start(App& app) {
WifiManage* wifi = wifi_manage_alloc();
// region Manifest methods
static void onStart(App& app) {
auto* wifi = new WifiManage();
app.setData(wifi);
}
static void app_stop(App& app) {
static void onStop(App& app) {
auto* wifi = (WifiManage*)app.getData();
tt_assert(wifi != nullptr);
wifi_manage_free(wifi);
delete wifi;
}
static void onShow(App& app, lv_obj_t* parent) {
auto* wifi = (WifiManage*)app.getData();
wifi->onShow(app, parent);
}
static void onHide(App& app) {
auto* wifi = (WifiManage*)app.getData();
wifi->onHide(app);
}
// endregion
extern const Manifest manifest = {
.id = "WifiManage",
.name = "Wi-Fi",
.icon = LV_SYMBOL_WIFI,
.type = TypeSettings,
.onStart = &app_start,
.onStop = &app_stop,
.onShow = &app_show,
.onHide = &app_hide
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide
};
} // namespace
+22 -12
View File
@@ -1,24 +1,34 @@
#pragma once
#include "Mutex.h"
#include "WifiManageView.h"
#include "View.h"
#include "State.h"
#include "service/wifi/Wifi.h"
namespace tt::app::wifimanage {
typedef struct {
PubSubSubscription* wifi_subscription;
Mutex* mutex;
WifiManageState state;
WifiManageView view;
bool view_enabled;
WifiManageBindings bindings;
} WifiManage;
class WifiManage {
void lock(WifiManage* wifi);
PubSubSubscription* wifiSubscription = nullptr;
Mutex mutex;
tt::app::wifimanage::State state;
tt::app::wifimanage::View view;
Bindings bindings = { };
bool isViewEnabled = false;
void unlock(WifiManage* wifi);
public:
void request_view_update(WifiManage* wifi);
WifiManage();
void lock();
void unlock();
void onShow(App& app, lv_obj_t* parent);
void onHide(App& app);
State& getState() { return state; }
void requestViewUpdate();
};
} // namespace
@@ -1,20 +0,0 @@
#pragma once
#include "service/wifi/Wifi.h"
namespace tt::app::wifimanage {
#define WIFI_SCAN_AP_RECORD_COUNT 16
/**
* View's state
*/
typedef struct {
bool scanning;
service::wifi::WifiRadioState radio_state;
uint8_t connect_ssid[33];
service::wifi::WifiApRecord ap_records[WIFI_SCAN_AP_RECORD_COUNT];
uint16_t ap_records_count;
} WifiManageState;
} // namespace
@@ -1,27 +0,0 @@
#include "WifiManage.h"
namespace tt::app::wifimanage {
void state_set_scanning(WifiManage* wifi, bool is_scanning) {
lock(wifi);
wifi->state.scanning = is_scanning;
unlock(wifi);
}
void state_set_radio_state(WifiManage* wifi, service::wifi::WifiRadioState state) {
lock(wifi);
wifi->state.radio_state = state;
unlock(wifi);
}
void state_update_scanned_records(WifiManage* wifi) {
lock(wifi);
service::wifi::getScanResults(
wifi->state.ap_records,
WIFI_SCAN_AP_RECORD_COUNT,
&wifi->state.ap_records_count
);
unlock(wifi);
}
} // namespace
@@ -1,11 +0,0 @@
#pragma once
#include "WifiManage.h"
namespace tt::app::wifimanage {
void state_set_scanning(WifiManage* wifi, bool is_scanning);
void state_set_radio_state(WifiManage* wifi, service::wifi::WifiRadioState state);
void state_update_scanned_records(WifiManage* wifi);
} // namespace
@@ -1,207 +0,0 @@
#include "WifiManageView.h"
#include "Log.h"
#include "WifiManageState.h"
#include "service/statusbar/Statusbar.h"
#include "service/wifi/Wifi.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
namespace tt::app::wifimanage {
#define TAG "wifi_main_view"
#define SPINNER_HEIGHT 40
static void on_enable_switch_changed(lv_event_t* event) {
lv_event_code_t code = lv_event_get_code(event);
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_target(event));
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
auto* bindings = static_cast<WifiManageBindings*>(lv_event_get_user_data(event));
bindings->on_wifi_toggled(is_on);
}
}
static void on_disconnect_pressed(lv_event_t* event) {
auto* bindings = static_cast<WifiManageBindings*>(lv_event_get_user_data(event));
bindings->on_disconnect();
}
// region Secondary updates
static void connect(lv_event_t* event) {
lv_obj_t* button = lv_event_get_current_target_obj(event);
// Assumes that the second child of the button is a label ... risky
lv_obj_t* label = lv_obj_get_child(button, 1);
// We get the SSID from the button label because it's safer than alloc'ing
// our own and passing it as the event data
const char* ssid = lv_label_get_text(label);
TT_LOG_I(TAG, "Clicked AP: %s", ssid);
auto* bindings = static_cast<WifiManageBindings*>(lv_event_get_user_data(event));
bindings->on_connect_ssid(ssid);
}
static void create_network_button(WifiManageView* view, WifiManageBindings* bindings, service::wifi::WifiApRecord* record) {
const char* ssid = (const char*)record->ssid;
const char* icon = service::statusbar::getWifiStatusIconForRssi(record->rssi, record->auth_mode != WIFI_AUTH_OPEN);
lv_obj_t* ap_button = lv_list_add_btn(
view->networks_list,
icon,
ssid
);
lv_obj_add_event_cb(ap_button, &connect, LV_EVENT_CLICKED, bindings);
}
static void update_network_list(WifiManageView* view, WifiManageState* state, WifiManageBindings* bindings) {
lv_obj_clean(view->networks_list);
switch (state->radio_state) {
case service::wifi::WIFI_RADIO_ON_PENDING:
case service::wifi::WIFI_RADIO_ON:
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE: {
lv_obj_clear_flag(view->networks_label, LV_OBJ_FLAG_HIDDEN);
if (state->ap_records_count > 0) {
for (int i = 0; i < state->ap_records_count; ++i) {
create_network_button(view, bindings, &state->ap_records[i]);
}
lv_obj_clear_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
} else if (state->scanning) {
lv_obj_add_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_clear_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_t* label = lv_label_create(view->networks_list);
lv_label_set_text(label, "No networks found.");
}
break;
}
case service::wifi::WIFI_RADIO_OFF_PENDING:
case service::wifi::WIFI_RADIO_OFF: {
lv_obj_add_flag(view->networks_list, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(view->networks_label, LV_OBJ_FLAG_HIDDEN);
break;
}
}
}
void update_scanning(WifiManageView* view, WifiManageState* state) {
if (state->radio_state == service::wifi::WIFI_RADIO_ON && state->scanning) {
lv_obj_clear_flag(view->scanning_spinner, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(view->scanning_spinner, LV_OBJ_FLAG_HIDDEN);
}
}
static void update_wifi_toggle(WifiManageView* view, WifiManageState* state) {
lv_obj_clear_state(view->enable_switch, LV_STATE_ANY);
switch (state->radio_state) {
case service::wifi::WIFI_RADIO_ON:
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_add_state(view->enable_switch, LV_STATE_CHECKED);
break;
case service::wifi::WIFI_RADIO_ON_PENDING:
lv_obj_add_state(view->enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED);
break;
case service::wifi::WIFI_RADIO_OFF:
lv_obj_remove_state(view->enable_switch, LV_STATE_CHECKED | LV_STATE_DISABLED);
break;
case service::wifi::WIFI_RADIO_OFF_PENDING:
lv_obj_remove_state(view->enable_switch, LV_STATE_CHECKED);
lv_obj_add_state(view->enable_switch, LV_STATE_DISABLED);
break;
}
}
static void update_connected_ap(WifiManageView* view, WifiManageState* state, TT_UNUSED WifiManageBindings* bindings) {
switch (state->radio_state) {
case service::wifi::WIFI_RADIO_CONNECTION_PENDING:
case service::wifi::WIFI_RADIO_CONNECTION_ACTIVE:
lv_obj_clear_flag(view->connected_ap_container, LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(view->connected_ap_label, (const char*)state->connect_ssid);
break;
default:
lv_obj_add_flag(view->connected_ap_container, LV_OBJ_FLAG_HIDDEN);
break;
}
}
// endregion Secondary updates
// region Main
void view_create(const App& app, WifiManageView* view, WifiManageBindings* bindings, lv_obj_t* parent) {
view->root = parent;
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
lv_obj_t* wrapper = lv_obj_create(parent);
lv_obj_set_width(wrapper, LV_PCT(100));
lv_obj_set_flex_grow(wrapper, 1);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
// Top row: enable/disable
lv_obj_t* switch_container = lv_obj_create(wrapper);
lv_obj_set_width(switch_container, LV_PCT(100));
lv_obj_set_height(switch_container, LV_SIZE_CONTENT);
lvgl::obj_set_style_no_padding(switch_container);
lvgl::obj_set_style_bg_invisible(switch_container);
lv_obj_t* enable_label = lv_label_create(switch_container);
lv_label_set_text(enable_label, "Wi-Fi");
lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID);
view->enable_switch = lv_switch_create(switch_container);
lv_obj_add_event_cb(view->enable_switch, on_enable_switch_changed, LV_EVENT_ALL, bindings);
lv_obj_set_align(view->enable_switch, LV_ALIGN_RIGHT_MID);
view->connected_ap_container = lv_obj_create(wrapper);
lv_obj_set_size(view->connected_ap_container, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(view->connected_ap_container, SPINNER_HEIGHT, 0);
lvgl::obj_set_style_no_padding(view->connected_ap_container);
lv_obj_set_style_border_width(view->connected_ap_container, 0, 0);
view->connected_ap_label = lv_label_create(view->connected_ap_container);
lv_obj_align(view->connected_ap_label, LV_ALIGN_LEFT_MID, 0, 0);
lv_obj_t* disconnect_button = lv_btn_create(view->connected_ap_container);
lv_obj_add_event_cb(disconnect_button, &on_disconnect_pressed, LV_EVENT_CLICKED, bindings);
lv_obj_t* disconnect_label = lv_label_create(disconnect_button);
lv_label_set_text(disconnect_label, "Disconnect");
lv_obj_align(disconnect_button, LV_ALIGN_RIGHT_MID, 0, 0);
// Networks
lv_obj_t* networks_header = lv_obj_create(wrapper);
lv_obj_set_size(networks_header, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_min_height(networks_header, SPINNER_HEIGHT, 0);
lvgl::obj_set_style_no_padding(networks_header);
lv_obj_set_style_border_width(networks_header, 0, 0);
view->networks_label = lv_label_create(networks_header);
lv_label_set_text(view->networks_label, "Networks");
lv_obj_align(view->networks_label, LV_ALIGN_LEFT_MID, 0, 0);
view->scanning_spinner = lv_spinner_create(networks_header);
lv_spinner_set_anim_params(view->scanning_spinner, 1000, 60);
lv_obj_set_size(view->scanning_spinner, SPINNER_HEIGHT, SPINNER_HEIGHT);
lv_obj_set_style_pad_top(view->scanning_spinner, 4, 0);
lv_obj_set_style_pad_bottom(view->scanning_spinner, 4, 0);
lv_obj_align_to(view->scanning_spinner, view->networks_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0);
view->networks_list = lv_obj_create(wrapper);
lv_obj_set_flex_flow(view->networks_list, LV_FLEX_FLOW_COLUMN);
lv_obj_set_width(view->networks_list, LV_PCT(100));
lv_obj_set_height(view->networks_list, LV_SIZE_CONTENT);
lv_obj_set_style_pad_top(view->networks_list, 8, 0);
lv_obj_set_style_pad_bottom(view->networks_list, 8, 0);
}
void view_update(WifiManageView* view, WifiManageBindings* bindings, WifiManageState* state) {
update_wifi_toggle(view, state);
update_scanning(view, state);
update_network_list(view, state, bindings);
update_connected_ap(view, state, bindings);
}
} // namespace
@@ -1,23 +0,0 @@
#pragma once
#include "app/App.h"
#include "WifiManageBindings.h"
#include "WifiManageState.h"
#include "lvgl.h"
namespace tt::app::wifimanage {
typedef struct {
lv_obj_t* root;
lv_obj_t* enable_switch;
lv_obj_t* scanning_spinner;
lv_obj_t* networks_label;
lv_obj_t* networks_list;
lv_obj_t* connected_ap_container;
lv_obj_t* connected_ap_label;
} WifiManageView;
void view_create(const App& app, WifiManageView* view, WifiManageBindings* bindings, lv_obj_t* parent);
void view_update(WifiManageView* view, WifiManageBindings* bindings, WifiManageState* state);
} // namespace