From f7616fbc4051df5e1243cc762105c21ae9c43f29 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Wed, 4 Jun 2025 14:23:57 +0200 Subject: [PATCH] WiFiConfig: use mpos.config facilities --- .../assets/wificonf.py | 84 +++----- internal_filesystem/lib/mpos/config.py | 202 +++++++++++++++--- 2 files changed, 202 insertions(+), 84 deletions(-) diff --git a/internal_filesystem/builtin/apps/com.micropythonos.wificonf/assets/wificonf.py b/internal_filesystem/builtin/apps/com.micropythonos.wificonf/assets/wificonf.py index 4d1ca5ed..9dce87f3 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.wificonf/assets/wificonf.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.wificonf/assets/wificonf.py @@ -6,8 +6,9 @@ import _thread from mpos.apps import Activity, Intent import mpos.ui +import mpos.config -# Global variables because they're shared between activities: +# Global variables because they're used by multiple Activities: access_points={} last_tried_ssid = "" last_tried_result = "" @@ -61,7 +62,9 @@ class WiFiConfig(Activity): self.start_scan_networks() def onResume(self, screen): - load_config() + global access_points + access_points = mpos.config.SharedPreferences("com.micropythonos.wificonf").get_dict("access_points") + def show_error(self, message): print(f"show_error: Displaying error: {message}") @@ -103,7 +106,7 @@ class WiFiConfig(Activity): self.scan_button_label.set_text(self.scan_button_scanning_text) _thread.stack_size(mpos.apps.good_stack_size()) _thread.start_new_thread(self.scan_networks_thread, ()) - + def refresh_list(self): print("refresh_list: Clearing current list") self.aplist.clean() # this causes an issue with lost taps if an ssid is clicked that has been removed @@ -203,6 +206,7 @@ class PasswordPage(Activity): connect_button=None cancel_button=None + def onCreate(self): self.selected_ssid = self.getIntent().extras.get("selected_ssid") print("PasswordPage: Creating new password page") @@ -218,11 +222,9 @@ class PasswordPage(Activity): self.password_ta.set_one_line(True) self.password_ta.align_to(label, lv.ALIGN.OUT_BOTTOM_MID, 5, 0) self.password_ta.add_event_cb(self.password_ta_cb,lv.EVENT.CLICKED,None) - # try to find saved password: - for apssid,password in access_points.items(): - if self.selected_ssid == apssid: - self.password_ta.set_text(password) - break + pwd = self.findSavedPassword(self.selected_ssid) + if pwd: + self.password_ta.set_text(pwd) self.password_ta.set_placeholder_text("Password") print("PasswordPage: Creating keyboard (hidden by default)") self.keyboard=lv.keyboard(password_page) @@ -291,9 +293,11 @@ class PasswordPage(Activity): print("connect_cb: Connect button clicked") password=self.password_ta.get_text() print(f"connect_cb: Got password: {password}") - access_points[self.selected_ssid]=password + self.setPassword(self.selected_ssid, password) print(f"connect_cb: Updated access_points: {access_points}") - save_config() + editor = mpos.config.SharedPreferences("com.micropythonos.wificonf").edit() + editor.put_dict("access_points", access_points) + editor.commit() self.setResult(True, {"ssid": self.selected_ssid, "password": password}) print("connect_cb: Restoring main_screen") self.finish() @@ -302,47 +306,21 @@ class PasswordPage(Activity): print("cancel_cb: Cancel button clicked") self.finish() + @staticmethod + def setPassword(ssid, password): + global access_points + ap = access_points.get(ssid) + if ap: + ap["password"] = password + return + # if not found, then add it: + access_points[ssid] = { "password": password } - - -# Non-class functions: - - -def load_config(): # Maybe this should call a framework function - print("load_config: Checking for /data directory") - try: - os.stat('data') - print("load_config: /data exists") - except OSError: - print("load_config: Creating /data directory") - os.mkdir('data') - print("load_config: Checking for /data/com.example.wificonf directory") - try: - os.stat('data/com.example.wificonf') - print("load_config: /data/com.example.wificonf exists") - except OSError: - print("load_config: Creating /data/com.example.wificonf directory") - os.mkdir('data/com.example.wificonf') - print("load_config: Loading config from conf.json") - try: - with open('data/com.example.wificonf/conf.json','r') as f: - global access_points - access_points=ujson.load(f) - print(f"load_config: Loaded access_points: {access_points}") - except OSError: - access_points={} - print("load_config: No config file found, using empty access_points") - - -def save_config(): # Maybe this should call a framework function - print("save_config: Saving access_points to conf.json") - try: - with open('data/com.example.wificonf/conf.json','w') as f: - ujson.dump(access_points,f) - print(f"save_config: Saved access_points: {access_points}") - except OSError: - self.show_error("Failed to save config") - print("save_config: Failed to save config") - - - + @staticmethod + def findSavedPassword(ssid): + if not access_points: + return None + ap = access_points.get(ssid) + if ap: + return ap.get("password") + return None diff --git a/internal_filesystem/lib/mpos/config.py b/internal_filesystem/lib/mpos/config.py index a2108f95..4ac6963c 100644 --- a/internal_filesystem/lib/mpos/config.py +++ b/internal_filesystem/lib/mpos/config.py @@ -8,23 +8,19 @@ class SharedPreferences: self.filename = filename self.filepath = f"data/{self.appname}/{self.filename}" self.data = {} - self.load() # Load existing preferences + self.load() def make_folder_structure(self): """Create directory structure if it doesn't exist.""" - #print("Checking if data/ exists") try: os.stat('data') - #print("data/ exists") except OSError: print("Creating data/ directory") os.mkdir('data') - #print(f"Checking if data/{self.appname}/ exists") try: os.stat(f"data/{self.appname}") - #print(f"data/{self.appname}/ exists") except OSError: - #print(f"Creating data/{self.appname} directory") + print(f"Creating data/{self.appname} directory") os.mkdir(f"data/{self.appname}") def load(self): @@ -35,7 +31,7 @@ class SharedPreferences: print(f"load: Loaded preferences: {self.data}") except Exception as e: print(f"load: Got exception {e}, assuming empty or non-existent preferences.") - self.data = {} # Default to empty dict on error + self.data = {} def get_string(self, key, default=None): """Retrieve a string value for the given key, with a default if not found.""" @@ -55,13 +51,21 @@ class SharedPreferences: except (TypeError, ValueError): return default + def get_list(self, key, default=None): + """Retrieve a list for the given key, with a default if not found.""" + return self.data.get(key, default if default is not None else []) + + def get_dict(self, key, default=None): + """Retrieve a dictionary for the given key, with a default if not found.""" + return self.data.get(key, default if default is not None else {}) + def edit(self): """Return an Editor object to modify preferences.""" return Editor(self) def save_config(self): """Save preferences to the JSON file.""" - self.make_folder_structure() # Ensure directories exist + self.make_folder_structure() print(f"save_config: Saving preferences to {self.filepath}") try: with open(self.filepath, 'w') as f: @@ -70,11 +74,48 @@ class SharedPreferences: except Exception as e: print(f"save_config: Got exception {e}") + # Methods for list-based structures + def get_list_item(self, list_key, index, item_key, default=None): + """Retrieve a specific item's value from a list of dictionaries.""" + try: + return self.data.get(list_key, [])[index].get(item_key, default) + except (IndexError, KeyError, TypeError): + return default + + def get_list_item_dict(self, list_key, index, default=None): + """Retrieve an entire dictionary from a list at the specified index.""" + try: + return self.data.get(list_key, [])[index] + except (IndexError, TypeError): + return default if default is not None else {} + + # Generic methods for dictionary-based structures + def get_dict_item_field(self, dict_key, item_key, field, default=None): + """Retrieve a specific field for an item in a dictionary by item_key.""" + try: + return self.data.get(dict_key, {}).get(item_key, {}).get(field, default) + except (KeyError, TypeError): + return default + + def get_dict_item(self, dict_key, item_key, default=None): + """Retrieve the entire configuration for an item in a dictionary by item_key.""" + try: + return self.data.get(dict_key, {}).get(item_key, default if default is not None else {}) + except (KeyError, TypeError): + return default if default is not None else {} + + def get_dict_keys(self, dict_key): + """Retrieve a list of all keys in a dictionary at dict_key.""" + try: + return list(self.data.get(dict_key, {}).keys()) + except (KeyError, TypeError): + return [] + class Editor: def __init__(self, preferences): """Initialize Editor with a reference to SharedPreferences.""" self.preferences = preferences - self.temp_data = preferences.data.copy() # Work on a copy of the data + self.temp_data = preferences.data.copy() def put_string(self, key, value): """Store a string value.""" @@ -91,6 +132,64 @@ class Editor: self.temp_data[key] = bool(value) return self + def put_list(self, key, value): + """Store a list value.""" + if isinstance(value, list): + self.temp_data[key] = value + return self + + def put_dict(self, key, value): + """Store a dictionary value.""" + if isinstance(value, dict): + self.temp_data[key] = value + return self + + def append_to_list(self, list_key, item): + """Append a dictionary to a list in the preferences.""" + if list_key not in self.temp_data: + self.temp_data[list_key] = [] + if isinstance(item, dict): + self.temp_data[list_key].append(item) + return self + + def update_list_item(self, list_key, index, item): + """Update a dictionary at a specific index in a list.""" + try: + if list_key in self.temp_data and isinstance(self.temp_data[list_key], list): + if index < len(self.temp_data[list_key]) and isinstance(item, dict): + self.temp_data[list_key][index] = item + except (IndexError, TypeError): + pass + return self + + def remove_from_list(self, list_key, index): + """Remove an item from a list at the specified index.""" + try: + if list_key in self.temp_data and isinstance(self.temp_data[list_key], list): + if index < len(self.temp_data[list_key]): + self.temp_data[list_key].pop(index) + except (IndexError, TypeError): + pass + return self + + # Generic methods for dictionary-based structures + def put_dict_item(self, dict_key, item_key, config): + """Add or update an item in a dictionary by item_key.""" + if dict_key not in self.temp_data: + self.temp_data[dict_key] = {} + if isinstance(config, dict): + self.temp_data[dict_key][item_key] = config + return self + + def remove_dict_item(self, dict_key, item_key): + """Remove an item from a dictionary by item_key.""" + try: + if dict_key in self.temp_data and isinstance(self.temp_data[dict_key], dict): + self.temp_data[dict_key].pop(item_key, None) + except (KeyError, TypeError): + pass + return self + def apply(self): """Save changes to the file asynchronously (emulated).""" self.preferences.data = self.temp_data.copy() @@ -102,37 +201,78 @@ class Editor: self.preferences.save_config() return True -# Example usage +# Example usage with access_points as a dictionary def main(): - # Initialize SharedPreferences with an appname + # Initialize SharedPreferences prefs = SharedPreferences("com.example.test_shared_prefs") - - print("Getting preferences:") - print(f"theme: {prefs.get_string('theme')}") - print(f"volume: {prefs.get_int('volume')}") - print(f"notifications: {prefs.get_bool('notifications')}") - print("Done getting preferences.") - # Save some settings using Editor + # Save some simple settings and a dictionary-based access_points editor = prefs.edit() - editor.put_string("theme", "dark") - editor.put_int("volume", 75) - editor.put_bool("notifications", True) - editor.apply() # Asynchronous save (emulated) + editor.put_string("someconfig", "somevalue") + editor.put_int("othervalue", 54321) + editor.put_dict("access_points", { + "example_ssid1": {"password": "examplepass1", "detail": "yes please", "numericalconf": 1234}, + "example_ssid2": {"password": "examplepass2", "detail": "no please", "numericalconf": 9875} + }) + editor.apply() # Read back the settings - print("Theme:", prefs.get_string("theme", "light")) - print("Volume:", prefs.get_int("volume", 50)) - print("Notifications:", prefs.get_bool("notifications", False)) + print("Simple settings:") + print("someconfig:", prefs.get_string("someconfig", "default_value")) + print("othervalue:", prefs.get_int("othervalue", 0)) - # Modify a setting + print("\nAccess points (dictionary-based):") + ssids = prefs.get_dict_keys("access_points") + for ssid in ssids: + print(f"Access Point SSID: {ssid}") + print(f" Password: {prefs.get_dict_item_field('access_points', ssid, 'password', 'N/A')}") + print(f" Detail: {prefs.get_dict_item_field('access_points', ssid, 'detail', 'N/A')}") + print(f" Numerical Conf: {prefs.get_dict_item_field('access_points', ssid, 'numericalconf', 0)}") + print(f" Full config: {prefs.get_dict_item('access_points', ssid)}") + + # Add a new access point editor = prefs.edit() - editor.put_string("theme", "light") - success = editor.commit() # Synchronous save - print("Save successful:", success) + editor.put_dict_item("access_points", "example_ssid3", { + "password": "examplepass3", + "detail": "maybe", + "numericalconf": 5555 + }) + editor.commit() - # Read updated setting - print("Updated Theme:", prefs.get_string("theme", "light")) + # Update an existing access point + editor = prefs.edit() + editor.put_dict_item("access_points", "example_ssid1", { + "password": "newpass1", + "detail": "updated please", + "numericalconf": 4321 + }) + editor.commit() + + # Remove an access point + editor = prefs.edit() + editor.remove_dict_item("access_points", "example_ssid2") + editor.commit() + + # Read updated access points + print("\nUpdated access points (dictionary-based):") + ssids = prefs.get_dict_keys("access_points") + for ssid in ssids: + print(f"Access Point SSID: {ssid}: {prefs.get_dict_item('access_points', ssid)}") + + # Demonstrate compatibility with list-based configs + editor = prefs.edit() + editor.put_list("somelist", [ + {"a": "ok", "numericalconf": 1111}, + {"a": "not ok", "numericalconf": 2222} + ]) + editor.apply() + + print("\List-based config:") + somelist = prefs.get_list("somelist") + for i, ap in enumerate(somelist): + print(f"List item {i}:") + print(f" a: {prefs.get_list_item('somelist', i, 'a', 'N/A')}") + print(f" Full dict: {prefs.get_list_item_dict('somelist', i)}") if __name__ == '__main__': main()