From 0652a7ee99452c99392e4664077b71114ac0473d Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 19:33:32 +0200 Subject: [PATCH 01/61] add wpa2 config to hostapd.conf dry coded, needs testing on Pi --- python/PiFinder/main.py | 3 ++ python/PiFinder/sys_utils.py | 52 +++++++++++++++++++++++++++++++ python/PiFinder/sys_utils_fake.py | 4 +++ python/tests/test_sys_utils.py | 10 ++++++ 4 files changed, 69 insertions(+) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index bacf93f01..70fa65fd1 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -272,6 +272,9 @@ def main( os_detail, platform, arch = utils.get_os_info() logger.info("PiFinder running on %s, %s, %s", os_detail, platform, arch) + sys_utils = utils.get_sys_utils() + sys_utils.Network.secure_accesspoint() + # init UI Modes command_queues = { "camera": camera_command_queue, diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 7eddbc496..741dbd1a2 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -1,5 +1,7 @@ import glob import re +import random +import string from typing import Dict, Any import sh @@ -26,6 +28,48 @@ def __init__(self): self.populate_wifi_networks() + @staticmethod + def secure_accesspoint() -> None: + """ Add WPA2 encryption, if not already enabled. + + Tasks: + 1) if SSID in current config ends with CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) + 2) Create a random password (20 chars in 5 groups of random chars, separeted by '-', see below) + + where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. + """ + + passphrase_detected = False + ssid_changed = False + with open("/tmp/hostapd.conf", "w") as new_conf: + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("ssid=") and line.endswith("CHANGEME"): + ap_rnd = Network._generate_random_chars(5) + line = f"ssid=PiFinder-{ap_rnd}\n" + ssid_changed = True + logger.warning(f"Network: Chaning SSID to {ap_rnd}") + elif line.startswith("wpa_passphrase="): + passphrase_detected = True + new_conf.write(line) + # consumed all lines, so: + if not passphrase_detected: + logger.warning("Network: Enabling WPA2 with PSK") + # Add encrpytion directives + pwd = Network._generated_random_chars(20, "-", 5) + new_conf.write("wpa=2") + new_conf.write("wpa_key_mgmt=WPA-PSK") + new_conf.write(f"wpa_passphrase={pwd}") + new_conf.write("rsn_pairwise=CCMP") + # Backup and move new file into place, restart service. + logger.warning("Network: Changing configuration for hostapd") + sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") + sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + # If we changed config, restart hostapd + if not passphrase_detected or ssid_changed: + logger.warning("Network: Restarting hostapd") + sh.sudo("systemctl", "restart", "hostapd") + def populate_wifi_networks(self) -> None: wpa_supplicant_path = "/etc/wpa_supplicant/wpa_supplicant.conf" self._wifi_networks = [] @@ -38,6 +82,14 @@ def populate_wifi_networks(self) -> None: self._wifi_networks = Network._parse_wpa_supplicant(contents) + @staticmethod + def _generate_random_chars(length: int, ch: str = None, group: int = -1) -> str: + """ Generate a string using random characters from the set of 0-9,a-z and A-Z""" + rndstr = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(length)) + if str is not None and group > 0: + rndstr = str.join([rndstr[i:i+group] for i in range(0, len(rndstr), group)]) + return rndstr + @staticmethod def _parse_wpa_supplicant(contents: list[str]) -> list: """ diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index efe6f1405..335b9fcc3 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -14,6 +14,10 @@ class Network: def __init__(self): pass + @staticmethod + def secure_accesspoint() -> None: + pass + def populate_wifi_networks(self): """ Parses wpa_supplicant.conf to get current config diff --git a/python/tests/test_sys_utils.py b/python/tests/test_sys_utils.py index 6115dc881..9d7abfb24 100644 --- a/python/tests/test_sys_utils.py +++ b/python/tests/test_sys_utils.py @@ -67,6 +67,16 @@ def test_wpa_supplicant_parsing(): result = sys_utils.Network._parse_wpa_supplicant(wpa_list) assert result[1]["psk"] == "1234@===!!!" + @pytest.mark.unit + def test_generate_five(): + five = sys_utils.Network._generate_random_chars(5) + assert len(five) == 5, "length wrong" + + twenty = sys_utils.Network._generate_random_chars(20, "-", 5) + assert len(twenty) == 23, "length wrong" + assert twenty[5] == "-", "no '-' after first group" + assert twenty[12] == "-", "no '-' after second group" + assert twenty[17] == "-", "no '-' aftger thrid group" except ImportError: pass From e180a56e0bd04ca495e6293aaac452fa31ee0863 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 19:54:05 +0200 Subject: [PATCH 02/61] fixed type checking --- python/PiFinder/sys_utils.py | 15 ++++++++------- python/PiFinder/sys_utils_fake.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 741dbd1a2..b79cd1542 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -34,7 +34,8 @@ def secure_accesspoint() -> None: Tasks: 1) if SSID in current config ends with CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) - 2) Create a random password (20 chars in 5 groups of random chars, separeted by '-', see below) + 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password + (20 chars in 5 groups of random chars, separeted by '-', see below) where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. """ @@ -53,10 +54,10 @@ def secure_accesspoint() -> None: passphrase_detected = True new_conf.write(line) # consumed all lines, so: - if not passphrase_detected: + if not passphrase_detected and ssid_changed: logger.warning("Network: Enabling WPA2 with PSK") # Add encrpytion directives - pwd = Network._generated_random_chars(20, "-", 5) + pwd = Network._generate_random_chars(20, "-", 5) new_conf.write("wpa=2") new_conf.write("wpa_key_mgmt=WPA-PSK") new_conf.write(f"wpa_passphrase={pwd}") @@ -83,11 +84,11 @@ def populate_wifi_networks(self) -> None: self._wifi_networks = Network._parse_wpa_supplicant(contents) @staticmethod - def _generate_random_chars(length: int, ch: str = None, group: int = -1) -> str: + def _generate_random_chars(length: int, ch: str = "", group: int = -1) -> str: """ Generate a string using random characters from the set of 0-9,a-z and A-Z""" - rndstr = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(length)) - if str is not None and group > 0: - rndstr = str.join([rndstr[i:i+group] for i in range(0, len(rndstr), group)]) + rndstr = ''.join([random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(length)]) + if ch != "" and group > 0: + rndstr = ch.join([rndstr[i:i+group] for i in range(0, len(rndstr), group)]) return rndstr @staticmethod diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 335b9fcc3..a648a2c14 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -15,7 +15,7 @@ def __init__(self): pass @staticmethod - def secure_accesspoint() -> None: + def secure_accesspoint() -> None: pass def populate_wifi_networks(self): From c68b94cbf05ee34dbfffe16f99412a184e59c04c Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 20:13:08 +0200 Subject: [PATCH 03/61] Only work, if SSID with CHANGEME detected. --- python/PiFinder/sys_utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index b79cd1542..5c7c22021 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -40,6 +40,16 @@ def secure_accesspoint() -> None: where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. """ + action_needed = False + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("ssid=") and line.endswith("CHANGEME"): + action_needed = True + break + + if not action_needed: + return + passphrase_detected = False ssid_changed = False with open("/tmp/hostapd.conf", "w") as new_conf: From 442e20751ece94ad1d79f3542dd3d7c867344066 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 20:23:54 +0200 Subject: [PATCH 04/61] Log if change is needed. --- python/PiFinder/sys_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 5c7c22021..3f63510da 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -48,7 +48,10 @@ def secure_accesspoint() -> None: break if not action_needed: + logger.info("SYSUTILS: No change in hostapd.conf needed.") return + + logger.info("SYSUTILS: Change in hostapd.conf needed.") passphrase_detected = False ssid_changed = False From ba126fdd350ed226e8775d17156a8d2ee32428dc Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 20:29:02 +0200 Subject: [PATCH 05/61] Test Testing for CHANGEME --- python/PiFinder/sys_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 3f63510da..eb51de60b 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -43,8 +43,9 @@ def secure_accesspoint() -> None: action_needed = False with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: - if line.startswith("ssid=") and line.endswith("CHANGEME"): - action_needed = True + if line.startswith("ssid="): + logger.info(f"SSID detected: {line} {line.endswith('CHANGEME')}") + action_needed = False break if not action_needed: From 934f38fa224fe6dd78315b3890183a2c1183c060 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 20:32:12 +0200 Subject: [PATCH 06/61] Getting past action_needed test --- python/PiFinder/sys_utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index eb51de60b..bdedc54b8 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -43,10 +43,9 @@ def secure_accesspoint() -> None: action_needed = False with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: - if line.startswith("ssid="): + if line.startswith("ssid=") and "CHANGEME" in line: logger.info(f"SSID detected: {line} {line.endswith('CHANGEME')}") - action_needed = False - break + action_needed = True if not action_needed: logger.info("SYSUTILS: No change in hostapd.conf needed.") From 1b54a76119189748e2cf68caa21bf80a54a36eb2 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 20:50:24 +0200 Subject: [PATCH 07/61] Should be first working version --- python/PiFinder/sys_utils.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index bdedc54b8..682c03786 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -33,8 +33,8 @@ def secure_accesspoint() -> None: """ Add WPA2 encryption, if not already enabled. Tasks: - 1) if SSID in current config ends with CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) - 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password + 1) if SSID in current config ends with CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) + 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password (20 chars in 5 groups of random chars, separeted by '-', see below) where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. @@ -44,25 +44,23 @@ def secure_accesspoint() -> None: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: if line.startswith("ssid=") and "CHANGEME" in line: - logger.info(f"SSID detected: {line} {line.endswith('CHANGEME')}") action_needed = True if not action_needed: - logger.info("SYSUTILS: No change in hostapd.conf needed.") return - logger.info("SYSUTILS: Change in hostapd.conf needed.") + logger.info("SYSUTILS: Securing WIFI.") passphrase_detected = False ssid_changed = False with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: - if line.startswith("ssid=") and line.endswith("CHANGEME"): + if line.startswith("ssid=") and "CHANGEME" in line: ap_rnd = Network._generate_random_chars(5) line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True - logger.warning(f"Network: Chaning SSID to {ap_rnd}") + logger.warning(f"Network: Changing SSID to {ap_rnd}") elif line.startswith("wpa_passphrase="): passphrase_detected = True new_conf.write(line) @@ -71,10 +69,10 @@ def secure_accesspoint() -> None: logger.warning("Network: Enabling WPA2 with PSK") # Add encrpytion directives pwd = Network._generate_random_chars(20, "-", 5) - new_conf.write("wpa=2") - new_conf.write("wpa_key_mgmt=WPA-PSK") - new_conf.write(f"wpa_passphrase={pwd}") - new_conf.write("rsn_pairwise=CCMP") + new_conf.write("wpa=2\n") + new_conf.write("wpa_key_mgmt=WPA-PSK\n") + new_conf.write(f"wpa_passphrase={pwd}\n") + new_conf.write("rsn_pairwise=CCMP\n") # Backup and move new file into place, restart service. logger.warning("Network: Changing configuration for hostapd") sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") From 3349bb6c3caf663cbeb6f1806a5ec755a9f0d9f2 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 21:03:01 +0200 Subject: [PATCH 08/61] Better logging of changes performed --- python/PiFinder/sys_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 682c03786..ffb96bf2b 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -33,7 +33,7 @@ def secure_accesspoint() -> None: """ Add WPA2 encryption, if not already enabled. Tasks: - 1) if SSID in current config ends with CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) + 1) if SSID in current config contains CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password (20 chars in 5 groups of random chars, separeted by '-', see below) @@ -49,7 +49,7 @@ def secure_accesspoint() -> None: if not action_needed: return - logger.info("SYSUTILS: Securing WIFI.") + logger.info("SYSUTILS: Changing WIFI.") passphrase_detected = False ssid_changed = False @@ -60,7 +60,7 @@ def secure_accesspoint() -> None: ap_rnd = Network._generate_random_chars(5) line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True - logger.warning(f"Network: Changing SSID to {ap_rnd}") + logger.warning(f"Network: Changing SSID to 'PiFinder-{ap_rnd}'") elif line.startswith("wpa_passphrase="): passphrase_detected = True new_conf.write(line) From 28b4718b0802bccd4661fbd3fee317d465a0f4d6 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 21:14:54 +0200 Subject: [PATCH 09/61] Add "WiFi Password" menu entry, display static text --- python/PiFinder/ui/menu_structure.py | 5 ++++ python/PiFinder/ui/wifi_password.py | 38 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 python/PiFinder/ui/wifi_password.py diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 83b3844c0..3254cbfd5 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -11,6 +11,7 @@ from PiFinder.ui.preview import UIPreview from PiFinder.ui.equipment import UIEquipment from PiFinder.ui.location_list import UILocationList +from PiFinder.ui.wifi_password import UIWiFiPassword import PiFinder.ui.callbacks as callbacks from multiprocessing import Queue @@ -834,6 +835,10 @@ }, ], }, + { + "name": "WiFi Password" + "class": UIWiFiPassword + }, { "name": "PiFinder Type", "class": UITextMenu, diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py new file mode 100644 index 000000000..f1024dc33 --- /dev/null +++ b/python/PiFinder/ui/wifi_password.py @@ -0,0 +1,38 @@ + +import logging +from PiFinder import state_utils +from PiFinder.ui.base import UIModule +from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu + +logger = logging.getLogger("WiFiPassword") + + +class UIGPSStatus(UIModule): + """ + UI for seeing GPS status + """ + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + def update(self, force=False): + state_utils.sleep_for_framerate(self.shared_state) + self.clear_screen() + draw_pos = self.display_class.titlebar_height + 2 + + # Status message + self.draw.text( + (0, draw_pos), + "SSID:", + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "1234567890123457890123456789012", + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + + return self.screen_update() From e795edd54f3e33d78e9cbe0b0d14bb8eca7c2843 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 6 Apr 2025 21:23:27 +0200 Subject: [PATCH 10/61] UIWiFiPassword: Fixed display --- python/PiFinder/ui/menu_structure.py | 2 +- python/PiFinder/ui/wifi_password.py | 40 ++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 3254cbfd5..1c033bd56 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -836,7 +836,7 @@ ], }, { - "name": "WiFi Password" + "name": "WiFi Password", "class": UIWiFiPassword }, { diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index f1024dc33..5a1750bb9 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -7,7 +7,7 @@ logger = logging.getLogger("WiFiPassword") -class UIGPSStatus(UIModule): +class UIWiFiPassword(UIModule): """ UI for seeing GPS status """ @@ -20,11 +20,47 @@ def update(self, force=False): self.clear_screen() draw_pos = self.display_class.titlebar_height + 2 - # Status message + # SSID self.draw.text( (0, draw_pos), "SSID:", font=self.fonts.bold.font, + fill=self.colors.get(128), + ) + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "1234567890123457890123456789012", + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + # Password + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "Password:", + font=self.fonts.bold.font, + fill=self.colors.get(128), + ) + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "1234567890123457890123456789012", + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "1234567890123457890123456789012", + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + self.draw.text( + (0, draw_pos), + "1234567890123457890123456789012", + font=self.fonts.bold.font, fill=self.colors.get(255), ) draw_pos += 16 From a0d9d68f1d308bad76a6e420b26276184aef8809 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 8 Apr 2025 09:00:39 +0200 Subject: [PATCH 11/61] Working password display (display the password literally) --- python/PiFinder/sys_utils.py | 7 ++ python/PiFinder/sys_utils_fake.py | 4 ++ python/PiFinder/ui/wifi_password.py | 100 ++++++++++++++++++---------- 3 files changed, 77 insertions(+), 34 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index ffb96bf2b..2c30a616e 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -196,6 +196,13 @@ def get_ap_name(self): if line.startswith("ssid="): return line[5:-1] return "UNKN" + + def get_ap_pwd(self): + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("wpa_passphrase="): + return line[15:-1] + return "" def set_ap_name(self, ap_name): if ap_name == self.get_ap_name(): diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index a648a2c14..98367a903 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -39,6 +39,10 @@ def add_wifi_network(self, ssid, key_mgmt, psk=None): """ pass + def get_ap_pwd(self): + # Return an example password, e.g. to test the password display. + return "UNKN8-01234-abcde-testpw-²³{[]}-!""§$%&/()=" + def get_ap_name(self): return "UNKN" diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 5a1750bb9..b4d1b9471 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -3,6 +3,8 @@ from PiFinder import state_utils from PiFinder.ui.base import UIModule from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu +from PiFinder.utils import get_sys_utils +# from PiFinder.sys_utils import Network logger = logging.getLogger("WiFiPassword") @@ -20,55 +22,85 @@ def update(self, force=False): self.clear_screen() draw_pos = self.display_class.titlebar_height + 2 - # SSID + net: Network = get_sys_utils().Network() + mode = net.wifi_mode() + if mode == "Client" or mode == "UNKN": + # Mode + self.draw.text( + (0, draw_pos), + f"Note: {mode} mode!", + font=self.fonts.base.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + elif mode == "Access Point": + pass + else: + raise Exception(f"unexpected wifi mode: {mode}") + + ap_name = net.get_ap_name() + ap_pwd = net.get_ap_pwd() self.draw.text( (0, draw_pos), "SSID:", - font=self.fonts.bold.font, + font=self.fonts.base.font, fill=self.colors.get(128), ) - draw_pos += 16 self.draw.text( - (0, draw_pos), - "1234567890123457890123456789012", + (30, draw_pos-2), + ap_name, font=self.fonts.bold.font, fill=self.colors.get(255), ) # Password - draw_pos += 16 + draw_pos += 10 self.draw.text( (0, draw_pos), "Password:", - font=self.fonts.bold.font, + font=self.fonts.base.font, fill=self.colors.get(128), ) draw_pos += 16 - self.draw.text( - (0, draw_pos), - "1234567890123457890123456789012", - font=self.fonts.bold.font, - fill=self.colors.get(255), - ) - draw_pos += 16 - self.draw.text( - (0, draw_pos), - "1234567890123457890123456789012", - font=self.fonts.bold.font, - fill=self.colors.get(255), - ) - draw_pos += 16 - self.draw.text( - (0, draw_pos), - "1234567890123457890123456789012", - font=self.fonts.bold.font, - fill=self.colors.get(255), - ) - draw_pos += 16 - self.draw.text( - (0, draw_pos), - "1234567890123457890123456789012", - font=self.fonts.bold.font, - fill=self.colors.get(255), - ) + dx = 8 # size of character + dy = 16 # line height + brk = 16 # max number of characters in line + x = 0 # draw position + i = 0 # character count in line + for ch in ap_pwd: + if ch.isdigit(): + self.draw.text( + (x, draw_pos), + ch, + font=self.fonts.bold.font, + fill=self.colors.get(128), + ) + elif ch.islower(): + self.draw.text( + (x, draw_pos), + ch, + font=self.fonts.bold.font, + fill=self.colors.get(225), + ) + elif ch.isupper(): + self.draw.text( + (x, draw_pos), + ch, + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + else: + self.draw.text( + (x, draw_pos), + ch, + font=self.fonts.bold.font, + fill=self.colors.get(100), + ) + x += dx + i += 1 + if i >= brk: + i = 0 + x = 0 + draw_pos += dy + return self.screen_update() From 904feca3b45485403c7d9e3f65c1765cfcdeff08 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 8 Apr 2025 09:21:32 +0200 Subject: [PATCH 12/61] Prepare for displaying QR --- python/PiFinder/ui/wifi_password.py | 55 ++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index b4d1b9471..c73ccfee9 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -8,38 +8,69 @@ logger = logging.getLogger("WiFiPassword") +# Constants for Display Modes +DM_QR = 0 # Display QR code for scanning with smartphone or tablet. +DM_PLAIN_PWD = 1 # Display plain password class UIWiFiPassword(UIModule): """ - UI for seeing GPS status + UI for displaying the Access Point name and password. """ + __help_name__ = "wifi_password" + __title__ = "WIFI" + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) + net: Network = get_sys_utils().Network() + self.ap_mode = net.wifi_mode() + self.ap_name = net.get_ap_name() + self.ap_pwd = net.get_ap_pwd() + + self.qr_image = None + + self.display_mode = DM_PLAIN_PWD + + def cycle_display_mode(self): + """ + Cycle through available display modes + for a module. Invoked when the square + key is pressed + """ + self.display_mode = ( + self.display_mode + 1 if self.display_mode < 1 else 0 + ) + self.update() + + def update(self, force=False): state_utils.sleep_for_framerate(self.shared_state) self.clear_screen() draw_pos = self.display_class.titlebar_height + 2 - net: Network = get_sys_utils().Network() - mode = net.wifi_mode() - if mode == "Client" or mode == "UNKN": + if self.display_mode == DM_PLAIN_PWD: + self._display_plain_pwd(draw_pos) + else: + pass + + return self.screen_update() + + def _display_plain_pwd(self, draw_pos): + if self.ap_mode == "Client" or self.ap_mode == "UNKN": # Mode self.draw.text( (0, draw_pos), - f"Note: {mode} mode!", + f"Note: {self.ap_mode} mode!", font=self.fonts.base.font, fill=self.colors.get(255), ) draw_pos += 16 - elif mode == "Access Point": + elif self.ap_mode == "Access Point": pass else: - raise Exception(f"unexpected wifi mode: {mode}") + raise Exception(f"unexpected wifi mode: {self.ap_mode}") - ap_name = net.get_ap_name() - ap_pwd = net.get_ap_pwd() self.draw.text( (0, draw_pos), "SSID:", @@ -48,7 +79,7 @@ def update(self, force=False): ) self.draw.text( (30, draw_pos-2), - ap_name, + self.ap_name, font=self.fonts.bold.font, fill=self.colors.get(255), ) @@ -66,7 +97,7 @@ def update(self, force=False): brk = 16 # max number of characters in line x = 0 # draw position i = 0 # character count in line - for ch in ap_pwd: + for ch in self.ap_pwd: if ch.isdigit(): self.draw.text( (x, draw_pos), @@ -102,5 +133,3 @@ def update(self, force=False): i = 0 x = 0 draw_pos += dy - - return self.screen_update() From dc4571c162e928a9e374c5b3913265d6cad03b23 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 8 Apr 2025 16:35:06 +0200 Subject: [PATCH 13/61] WiFi QR code is generated. --- python/PiFinder/ui/wifi_password.py | 78 ++++++++++++++++++++++------- python/requirements.txt | 1 + 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index c73ccfee9..e12b87f67 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -1,5 +1,7 @@ import logging +import qrcode + from PiFinder import state_utils from PiFinder.ui.base import UIModule from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu @@ -11,6 +13,7 @@ # Constants for Display Modes DM_QR = 0 # Display QR code for scanning with smartphone or tablet. DM_PLAIN_PWD = 1 # Display plain password +DM_LAST = DM_PLAIN_PWD class UIWiFiPassword(UIModule): """ @@ -23,15 +26,19 @@ class UIWiFiPassword(UIModule): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - net: Network = get_sys_utils().Network() - self.ap_mode = net.wifi_mode() - self.ap_name = net.get_ap_name() - self.ap_pwd = net.get_ap_pwd() + self.network: Network = get_sys_utils().Network() + self._update_info() self.qr_image = None self.display_mode = DM_PLAIN_PWD + def _update_info(self): + self.ap_mode = self.network.wifi_mode() + self.ap_name = self.network.get_ap_name() + self.ap_pwd = self.network.get_ap_pwd() + self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA") + def cycle_display_mode(self): """ Cycle through available display modes @@ -39,11 +46,35 @@ def cycle_display_mode(self): key is pressed """ self.display_mode = ( - self.display_mode + 1 if self.display_mode < 1 else 0 + self.display_mode + 1 if self.display_mode < DM_LAST else 0 ) + self._update_info() self.update() + def _generate_wifi_qrcode( + ssid: str, + password: str, + security_type: str, + qr_code_file: str = "~/qrcode.png"): + + wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" + + qr = qrcode.QRCode( + version=1, # 21x21 matrix + error_correction=qrcode.constants.ERROR_CORRECT_H, + box_size=2, # Size of a box of the + border=0, + ) + qr.add_data(wifi_data) + qr.make(fit=True) + + qr_code_image = qr.make_image( + fill_color="red", back_color="black" + ) + + return qr_code_image + def update(self, force=False): state_utils.sleep_for_framerate(self.shared_state) self.clear_screen() @@ -52,11 +83,17 @@ def update(self, force=False): if self.display_mode == DM_PLAIN_PWD: self._display_plain_pwd(draw_pos) else: - pass + self._display_wifi_qr(draw_pos) return self.screen_update() - def _display_plain_pwd(self, draw_pos): + def _display_wifi_qr(self, draw_pos: int) -> None: + draw_pos = self.display_class.titlebar_height + 2 + self._show_ssid(draw_pos) + self.screen.paste(self.wifi_qr, (0, draw_pos)) + pass + + def _display_plain_pwd(self, draw_pos: int) -> None: if self.ap_mode == "Client" or self.ap_mode == "UNKN": # Mode self.draw.text( @@ -71,18 +108,7 @@ def _display_plain_pwd(self, draw_pos): else: raise Exception(f"unexpected wifi mode: {self.ap_mode}") - self.draw.text( - (0, draw_pos), - "SSID:", - font=self.fonts.base.font, - fill=self.colors.get(128), - ) - self.draw.text( - (30, draw_pos-2), - self.ap_name, - font=self.fonts.bold.font, - fill=self.colors.get(255), - ) + self._show_ssid(draw_pos) # Password draw_pos += 10 self.draw.text( @@ -133,3 +159,17 @@ def _display_plain_pwd(self, draw_pos): i = 0 x = 0 draw_pos += dy + + def _show_ssid(self, draw_pos): + self.draw.text( + (0, draw_pos), + "SSID:", + font=self.fonts.base.font, + fill=self.colors.get(128), + ) + self.draw.text( + (30, draw_pos-2), + self.ap_name, + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) diff --git a/python/requirements.txt b/python/requirements.txt index 8607c6b63..aa2dc743e 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -24,3 +24,4 @@ timezonefinder==6.1.9 tqdm==4.65.0 protobuf==4.25.2 aiofiles==24.1.0 +qrcode==8.1 \ No newline at end of file From 3ebc973d224add2f0e6eccc3a6af9502dd539cd9 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 07:42:03 +0200 Subject: [PATCH 14/61] Scaled WiFi code in black/white --- python/PiFinder/ui/wifi_password.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index e12b87f67..6c4e183ef 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -1,6 +1,7 @@ import logging import qrcode +import math from PiFinder import state_utils from PiFinder.ui.base import UIModule @@ -38,6 +39,7 @@ def _update_info(self): self.ap_name = self.network.get_ap_name() self.ap_pwd = self.network.get_ap_pwd() self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA") + self.wifi_qr_scaled = False def cycle_display_mode(self): """ @@ -62,15 +64,16 @@ def _generate_wifi_qrcode( qr = qrcode.QRCode( version=1, # 21x21 matrix - error_correction=qrcode.constants.ERROR_CORRECT_H, - box_size=2, # Size of a box of the - border=0, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, # Size of a box of the + border=1, ) qr.add_data(wifi_data) qr.make(fit=True) qr_code_image = qr.make_image( - fill_color="red", back_color="black" + # fill_color="red", back_color="black" + fill_color="black", back_color="white" ) return qr_code_image @@ -90,6 +93,15 @@ def update(self, force=False): def _display_wifi_qr(self, draw_pos: int) -> None: draw_pos = self.display_class.titlebar_height + 2 self._show_ssid(draw_pos) + + if not self.wifi_qr_scaled: + (width, height) = self.wifi_qr.size + (target_width, target_height) = self.screen.size + target_height -= draw_pos + scale = min(target_width/width, target_height/height) + self.wifi_qr = self.wifi_qr.resize((math.floor(width*scale), math.floor(height*scale)), 1) # Do antialiasing using LANCZOS (Can't find the constant) + self.wifi_qr_scaled = True + self.screen.paste(self.wifi_qr, (0, draw_pos)) pass From fcfdadfd5c03f55c34b7872216a7bd2dcc2e5321 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 08:41:24 +0200 Subject: [PATCH 15/61] More sensible test password --- python/PiFinder/sys_utils_fake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 98367a903..0038f0842 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -41,7 +41,7 @@ def add_wifi_network(self, ssid, key_mgmt, psk=None): def get_ap_pwd(self): # Return an example password, e.g. to test the password display. - return "UNKN8-01234-abcde-testpw-²³{[]}-!""§$%&/()=" + return "UNKN8-01234-abcde-testpw" def get_ap_name(self): return "UNKN" From f319282e1561fde5187098ad5781c806c4acfb93 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 08:43:09 +0200 Subject: [PATCH 16/61] Fix _generate_wifi_code to use right parameters at right places. --- python/PiFinder/ui/wifi_password.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 6c4e183ef..fa9c27994 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -55,12 +55,13 @@ def cycle_display_mode(self): def _generate_wifi_qrcode( + self, ssid: str, password: str, - security_type: str, - qr_code_file: str = "~/qrcode.png"): + security_type: str): wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" + # logger.debug(f"WIFI Data: '{wifi_data}'") qr = qrcode.QRCode( version=1, # 21x21 matrix @@ -73,8 +74,10 @@ def _generate_wifi_qrcode( qr_code_image = qr.make_image( # fill_color="red", back_color="black" - fill_color="black", back_color="white" + fill_color="red", back_color="black" ) + # logger.warning(f"Generating WiFi QR Code: {qr_code_image.size[0]}, {qr_code_image.size[1]}") + # qr_code_image.save("WiFi_QR_code.png", "PNG") return qr_code_image @@ -101,6 +104,8 @@ def _display_wifi_qr(self, draw_pos: int) -> None: scale = min(target_width/width, target_height/height) self.wifi_qr = self.wifi_qr.resize((math.floor(width*scale), math.floor(height*scale)), 1) # Do antialiasing using LANCZOS (Can't find the constant) self.wifi_qr_scaled = True + # logger.warning(f"WiFi QR Code scaled size: {math.floor(width*scale)}^²") + # self.wifi_qr.save("WiFi_QR_Code_scaled.png", "PNG") self.screen.paste(self.wifi_qr, (0, draw_pos)) pass From d0f1895d725cb5f14a75ee095d8a866e7e211970 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 09:03:19 +0200 Subject: [PATCH 17/61] More space for QR, if long SSID. --- python/PiFinder/ui/wifi_password.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index fa9c27994..4d05b6fcf 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -95,7 +95,7 @@ def update(self, force=False): def _display_wifi_qr(self, draw_pos: int) -> None: draw_pos = self.display_class.titlebar_height + 2 - self._show_ssid(draw_pos) + draw_pos = self._show_ssid(draw_pos, True) if not self.wifi_qr_scaled: (width, height) = self.wifi_qr.size @@ -125,9 +125,8 @@ def _display_plain_pwd(self, draw_pos: int) -> None: else: raise Exception(f"unexpected wifi mode: {self.ap_mode}") - self._show_ssid(draw_pos) + draw_pos = self._show_ssid(draw_pos) # Password - draw_pos += 10 self.draw.text( (0, draw_pos), "Password:", @@ -177,16 +176,28 @@ def _display_plain_pwd(self, draw_pos: int) -> None: x = 0 draw_pos += dy - def _show_ssid(self, draw_pos): + def _show_ssid(self, draw_pos, truncate = False): self.draw.text( (0, draw_pos), "SSID:", font=self.fonts.base.font, fill=self.colors.get(128), ) + + # logger.debug(f"_show_ssid: {draw_pos}, {truncate}") + x_pos = 30 + + # If SSID is too long, display on separate line. + if not truncate: + if len(self.ap_name) > 14: + draw_pos += 10 + x_pos = 0 + self.draw.text( - (30, draw_pos-2), + (x_pos, draw_pos), self.ap_name, font=self.fonts.bold.font, fill=self.colors.get(255), ) + draw_pos += 16 + return draw_pos From 1959f7b64c691cd6ab9609f29159816b2037df45 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 09:18:20 +0200 Subject: [PATCH 18/61] renamed display_mode to wifi_display_mode to pass type checking --- python/PiFinder/ui/wifi_password.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 4d05b6fcf..b25254634 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -27,12 +27,12 @@ class UIWiFiPassword(UIModule): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self.network: Network = get_sys_utils().Network() + self.network = get_sys_utils().Network() self._update_info() self.qr_image = None - self.display_mode = DM_PLAIN_PWD + self.wifi_display_mode:int = DM_PLAIN_PWD def _update_info(self): self.ap_mode = self.network.wifi_mode() @@ -47,8 +47,8 @@ def cycle_display_mode(self): for a module. Invoked when the square key is pressed """ - self.display_mode = ( - self.display_mode + 1 if self.display_mode < DM_LAST else 0 + self.wifi_display_mode = ( + self.wifi_display_mode + 1 if self.wifi_display_mode < DM_LAST else 0 ) self._update_info() self.update() @@ -86,7 +86,7 @@ def update(self, force=False): self.clear_screen() draw_pos = self.display_class.titlebar_height + 2 - if self.display_mode == DM_PLAIN_PWD: + if self.wifi_display_mode == DM_PLAIN_PWD: self._display_plain_pwd(draw_pos) else: self._display_wifi_qr(draw_pos) From 0ccea4ab1b71fee440752048bd8e9676e479761f Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 09:18:56 +0200 Subject: [PATCH 19/61] nox/ruff code formatting. --- python/PiFinder/ui/wifi_password.py | 58 ++++++++++++++--------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index b25254634..a3ac7893f 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -1,21 +1,20 @@ - import logging import qrcode import math from PiFinder import state_utils from PiFinder.ui.base import UIModule -from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu from PiFinder.utils import get_sys_utils # from PiFinder.sys_utils import Network logger = logging.getLogger("WiFiPassword") # Constants for Display Modes -DM_QR = 0 # Display QR code for scanning with smartphone or tablet. -DM_PLAIN_PWD = 1 # Display plain password +DM_QR = 0 # Display QR code for scanning with smartphone or tablet. +DM_PLAIN_PWD = 1 # Display plain password DM_LAST = DM_PLAIN_PWD + class UIWiFiPassword(UIModule): """ UI for displaying the Access Point name and password. @@ -32,7 +31,7 @@ def __init__(self, *args, **kwargs) -> None: self.qr_image = None - self.wifi_display_mode:int = DM_PLAIN_PWD + self.wifi_display_mode: int = DM_PLAIN_PWD def _update_info(self): self.ap_mode = self.network.wifi_mode() @@ -53,20 +52,14 @@ def cycle_display_mode(self): self._update_info() self.update() - - def _generate_wifi_qrcode( - self, - ssid: str, - password: str, - security_type: str): - + def _generate_wifi_qrcode(self, ssid: str, password: str, security_type: str): wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" # logger.debug(f"WIFI Data: '{wifi_data}'") qr = qrcode.QRCode( - version=1, # 21x21 matrix + version=1, # 21x21 matrix error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, # Size of a box of the + box_size=10, # Size of a box of the border=1, ) qr.add_data(wifi_data) @@ -74,7 +67,8 @@ def _generate_wifi_qrcode( qr_code_image = qr.make_image( # fill_color="red", back_color="black" - fill_color="red", back_color="black" + fill_color="red", + back_color="black", ) # logger.warning(f"Generating WiFi QR Code: {qr_code_image.size[0]}, {qr_code_image.size[1]}") # qr_code_image.save("WiFi_QR_code.png", "PNG") @@ -86,11 +80,11 @@ def update(self, force=False): self.clear_screen() draw_pos = self.display_class.titlebar_height + 2 - if self.wifi_display_mode == DM_PLAIN_PWD: + if self.wifi_display_mode == DM_PLAIN_PWD: self._display_plain_pwd(draw_pos) - else: + else: self._display_wifi_qr(draw_pos) - + return self.screen_update() def _display_wifi_qr(self, draw_pos: int) -> None: @@ -101,14 +95,16 @@ def _display_wifi_qr(self, draw_pos: int) -> None: (width, height) = self.wifi_qr.size (target_width, target_height) = self.screen.size target_height -= draw_pos - scale = min(target_width/width, target_height/height) - self.wifi_qr = self.wifi_qr.resize((math.floor(width*scale), math.floor(height*scale)), 1) # Do antialiasing using LANCZOS (Can't find the constant) + scale = min(target_width / width, target_height / height) + self.wifi_qr = self.wifi_qr.resize( + (math.floor(width * scale), math.floor(height * scale)), 1 + ) # Do antialiasing using LANCZOS (Can't find the constant) self.wifi_qr_scaled = True # logger.warning(f"WiFi QR Code scaled size: {math.floor(width*scale)}^²") # self.wifi_qr.save("WiFi_QR_Code_scaled.png", "PNG") - + self.screen.paste(self.wifi_qr, (0, draw_pos)) - pass + pass def _display_plain_pwd(self, draw_pos: int) -> None: if self.ap_mode == "Client" or self.ap_mode == "UNKN": @@ -135,12 +131,12 @@ def _display_plain_pwd(self, draw_pos: int) -> None: ) draw_pos += 16 dx = 8 # size of character - dy = 16 # line height - brk = 16 # max number of characters in line - x = 0 # draw position - i = 0 # character count in line - for ch in self.ap_pwd: - if ch.isdigit(): + dy = 16 # line height + brk = 16 # max number of characters in line + x = 0 # draw position + i = 0 # character count in line + for ch in self.ap_pwd: + if ch.isdigit(): self.draw.text( (x, draw_pos), ch, @@ -161,7 +157,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: font=self.fonts.bold.font, fill=self.colors.get(255), ) - else: + else: self.draw.text( (x, draw_pos), ch, @@ -176,7 +172,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: x = 0 draw_pos += dy - def _show_ssid(self, draw_pos, truncate = False): + def _show_ssid(self, draw_pos, truncate=False): self.draw.text( (0, draw_pos), "SSID:", @@ -188,7 +184,7 @@ def _show_ssid(self, draw_pos, truncate = False): x_pos = 30 # If SSID is too long, display on separate line. - if not truncate: + if not truncate: if len(self.ap_name) > 14: draw_pos += 10 x_pos = 0 From 57a186d6100d2c46d220a84e658b8cde295fce3e Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Wed, 9 Apr 2025 09:29:28 +0200 Subject: [PATCH 20/61] type stubs for qrcode added. --- python/requirements_dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/requirements_dev.txt b/python/requirements_dev.txt index 0f4f318dd..6b859b07d 100644 --- a/python/requirements_dev.txt +++ b/python/requirements_dev.txt @@ -7,3 +7,4 @@ mypy==1.10.0 pytest==8.2.2 pygame==2.6.0 pre-commit==3.7.1 +types-qrcode>=8.1.0 \ No newline at end of file From 8380087904996205c80e160f24ab5dc66a6bd776 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Fri, 11 Apr 2025 21:35:23 +0200 Subject: [PATCH 21/61] added context menu. Switching to wifi mode does not work yet. --- python/PiFinder/ui/menu_structure.py | 1 + python/PiFinder/ui/wifi_password.py | 39 +++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 1c033bd56..ec2063f2e 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -820,6 +820,7 @@ { "name": "WiFi Mode", "class": UITextMenu, + "label": "wifi_mode", "select": "single", "value_callback": callbacks.get_wifi_mode, "items": [ diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index a3ac7893f..920afbd79 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -5,6 +5,7 @@ from PiFinder import state_utils from PiFinder.ui.base import UIModule from PiFinder.utils import get_sys_utils +from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu # from PiFinder.sys_utils import Network logger = logging.getLogger("WiFiPassword") @@ -31,7 +32,43 @@ def __init__(self, *args, **kwargs) -> None: self.qr_image = None - self.wifi_display_mode: int = DM_PLAIN_PWD + self.wifi_display_mode = DM_QR + + self.marking_menu = MarkingMenu( + left=MarkingMenuOption( + label="QR", callback=self.mm_display_qr, enabled=True + ), + right=MarkingMenuOption( + label="Passwd", callback=self.mm_display_pwd, enabled=True + ), + down=MarkingMenuOption( + label="Mode", callback=self.mm_switch_mode, enabled=True + ), + ) + + def mm_display_qr(self, marking_menu, menu_item): + """ + Marking menu option to display the QR code + """ + self.wifi_display_mode = DM_QR + self._update_info() + self.update() + # logger.debug(f"Marking menu: {self.marking_menu}") + return True + + def mm_display_pwd(self, marking_menu, menu_item): + """ + Marking menu option to display the plain password + """ + self.wifi_display_mode = DM_PLAIN_PWD + self._update_info() + self.update() + # logger.debug(f"Marking menu: {self.marking_menu}") + return True + + def mm_switch_mode(self, marking_menu, menu_item): + self.jump_to_label("WiFi Mode") + return False def _update_info(self): self.ap_mode = self.network.wifi_mode() From a210d122f8f315351b58ffac9cf3a723238ec71c Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Fri, 11 Apr 2025 22:55:05 +0200 Subject: [PATCH 22/61] QR display for unencrypted AP + show connect SSID for client mode. --- python/PiFinder/sys_utils.py | 7 ++ python/PiFinder/sys_utils_fake.py | 5 +- python/PiFinder/ui/wifi_password.py | 105 ++++++++++++++++++++++------ 3 files changed, 94 insertions(+), 23 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 2c30a616e..2f7ed9192 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -217,6 +217,13 @@ def set_ap_name(self, ap_name): def get_host_name(self): return socket.gethostname() + + def is_ap_open(self): + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("wpa="): + return False + return True def get_connected_ssid(self) -> str: """ diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 0038f0842..ca2b0bd21 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -49,6 +49,9 @@ def get_ap_name(self): def set_ap_name(self, ap_name): pass + def is_ap_open(self): + return False # Not ( Is AP encrypted? ) + def get_host_name(self): return socket.gethostname() @@ -57,7 +60,7 @@ def get_connected_ssid(self): Returns the SSID of the connected wifi network or None if not connected or in AP mode """ - return "UNKN" + return "UNKN SSID" def set_host_name(self, hostname): if hostname == self.get_host_name(): diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 920afbd79..e553f60b0 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -13,6 +13,7 @@ # Constants for Display Modes DM_QR = 0 # Display QR code for scanning with smartphone or tablet. DM_PLAIN_PWD = 1 # Display plain password +DM_CLIENT = 2 DM_LAST = DM_PLAIN_PWD @@ -32,19 +33,29 @@ def __init__(self, *args, **kwargs) -> None: self.qr_image = None - self.wifi_display_mode = DM_QR - - self.marking_menu = MarkingMenu( - left=MarkingMenuOption( - label="QR", callback=self.mm_display_qr, enabled=True - ), - right=MarkingMenuOption( - label="Passwd", callback=self.mm_display_pwd, enabled=True - ), - down=MarkingMenuOption( - label="Mode", callback=self.mm_switch_mode, enabled=True - ), - ) + if self.ap_mode == "Client": + self.wifi_display_mode = DM_CLIENT + self.marking_menu = MarkingMenu( + left=MarkingMenuOption(), + right=MarkingMenuOption(), + down=MarkingMenuOption(), + ) + else: + # Default to QR code display + self.wifi_display_mode = DM_QR + # self.wifi_display_mode = DM_PLAIN_PWD + + self.marking_menu = MarkingMenu( + left=MarkingMenuOption( + label="QR", callback=self.mm_display_qr, enabled=True + ), + right=MarkingMenuOption( + label="Passwd", callback=self.mm_display_pwd, enabled=True + ), + down=MarkingMenuOption( + label="Exit", callback=self.mm_switch_mode, enabled=True + ), + ) def mm_display_qr(self, marking_menu, menu_item): """ @@ -67,15 +78,23 @@ def mm_display_pwd(self, marking_menu, menu_item): return True def mm_switch_mode(self, marking_menu, menu_item): - self.jump_to_label("WiFi Mode") - return False + # self.jump_to_label("WiFi Mode") + return True def _update_info(self): self.ap_mode = self.network.wifi_mode() self.ap_name = self.network.get_ap_name() + self.ap_open = self.network.is_ap_open() self.ap_pwd = self.network.get_ap_pwd() - self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA") - self.wifi_qr_scaled = False + self.connected_ssid = self.network.get_connected_ssid() + if self.ap_mode == "Client": + self.wifi_qr = None + else: + if self.ap_open: + self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, "0", "nopass") + else: + self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA", False) + self.wifi_qr_scaled = False def cycle_display_mode(self): """ @@ -83,15 +102,19 @@ def cycle_display_mode(self): for a module. Invoked when the square key is pressed """ + if self.ap_mode == "Client": + # Do not cycle in client mode + return + self.wifi_display_mode = ( self.wifi_display_mode + 1 if self.wifi_display_mode < DM_LAST else 0 ) self._update_info() self.update() - def _generate_wifi_qrcode(self, ssid: str, password: str, security_type: str): + def _generate_wifi_qrcode(self, ssid: str, password: str, security_type: str) -> qrcode.image.base.BaseImage: wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" - # logger.debug(f"WIFI Data: '{wifi_data}'") + logger.debug(f"WIFI Data: '{wifi_data}'") qr = qrcode.QRCode( version=1, # 21x21 matrix @@ -119,13 +142,51 @@ def update(self, force=False): if self.wifi_display_mode == DM_PLAIN_PWD: self._display_plain_pwd(draw_pos) - else: + elif self.wifi_display_mode == DM_QR: self._display_wifi_qr(draw_pos) + elif self.wifi_display_mode == DM_CLIENT: + self._display_client_ssid(draw_pos) return self.screen_update() + def _display_client_ssid(self, draw_pos: int) -> None: + self.draw.text( + (0, draw_pos), + "Client Mode!", + font=self.fonts.base.font, + fill=self.colors.get(255), + ) + draw_pos += 20 + self.draw.text( + (0, draw_pos), + "Connected to:", + font=self.fonts.base.font, + fill=self.colors.get(255), + ) + draw_pos += 10 + self.draw.text( + (0, draw_pos), + self.connected_ssid, + font=self.fonts.bold.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + return draw_pos + + def _display_wifi_qr(self, draw_pos: int) -> None: draw_pos = self.display_class.titlebar_height + 2 + if self.ap_mode == "Client": + # Mode + self.draw.text( + (0, draw_pos), + f"Client mode!", + font=self.fonts.base.font, + fill=self.colors.get(255), + ) + draw_pos += 16 + return draw_pos + draw_pos = self._show_ssid(draw_pos, True) if not self.wifi_qr_scaled: @@ -144,7 +205,7 @@ def _display_wifi_qr(self, draw_pos: int) -> None: pass def _display_plain_pwd(self, draw_pos: int) -> None: - if self.ap_mode == "Client" or self.ap_mode == "UNKN": + if self.ap_mode == "Client": # Mode self.draw.text( (0, draw_pos), @@ -153,7 +214,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: fill=self.colors.get(255), ) draw_pos += 16 - elif self.ap_mode == "Access Point": + elif self.ap_mode == "AP" or self.ap_mode == "UNKN": pass else: raise Exception(f"unexpected wifi mode: {self.ap_mode}") From a6d93976534326127758490d9b587296b1c7299f Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sat, 12 Apr 2025 07:21:05 +0200 Subject: [PATCH 23/61] Provide short cut from quick menu to switch wifi mode. --- python/PiFinder/ui/wifi_password.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index e553f60b0..9e2a2aef6 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -38,7 +38,7 @@ def __init__(self, *args, **kwargs) -> None: self.marking_menu = MarkingMenu( left=MarkingMenuOption(), right=MarkingMenuOption(), - down=MarkingMenuOption(), + down=MarkingMenuOption(label=""), ) else: # Default to QR code display @@ -53,7 +53,7 @@ def __init__(self, *args, **kwargs) -> None: label="Passwd", callback=self.mm_display_pwd, enabled=True ), down=MarkingMenuOption( - label="Exit", callback=self.mm_switch_mode, enabled=True + label="mode", menu_jump="wifi_mode" ), ) @@ -77,10 +77,6 @@ def mm_display_pwd(self, marking_menu, menu_item): # logger.debug(f"Marking menu: {self.marking_menu}") return True - def mm_switch_mode(self, marking_menu, menu_item): - # self.jump_to_label("WiFi Mode") - return True - def _update_info(self): self.ap_mode = self.network.wifi_mode() self.ap_name = self.network.get_ap_name() @@ -93,7 +89,7 @@ def _update_info(self): if self.ap_open: self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, "0", "nopass") else: - self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA", False) + self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA") self.wifi_qr_scaled = False def cycle_display_mode(self): From 8af9738e3d06b36879165facb8c8da04f58b5ebd Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sat, 12 Apr 2025 07:22:42 +0200 Subject: [PATCH 24/61] code formatted --- python/PiFinder/sys_utils.py | 35 +++++++++++++++++------------ python/PiFinder/sys_utils_fake.py | 2 +- python/PiFinder/ui/wifi_password.py | 21 ++++++++--------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 2f7ed9192..bf5ce1e8e 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -30,25 +30,25 @@ def __init__(self): @staticmethod def secure_accesspoint() -> None: - """ Add WPA2 encryption, if not already enabled. + """Add WPA2 encryption, if not already enabled. Tasks: 1) if SSID in current config contains CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password (20 chars in 5 groups of random chars, separeted by '-', see below) - + where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. """ action_needed = False with open("/etc/hostapd/hostapd.conf", "r") as conf: - for line in conf: - if line.startswith("ssid=") and "CHANGEME" in line: + for line in conf: + if line.startswith("ssid=") and "CHANGEME" in line: action_needed = True if not action_needed: return - + logger.info("SYSUTILS: Changing WIFI.") passphrase_detected = False @@ -64,17 +64,17 @@ def secure_accesspoint() -> None: elif line.startswith("wpa_passphrase="): passphrase_detected = True new_conf.write(line) - # consumed all lines, so: + # consumed all lines, so: if not passphrase_detected and ssid_changed: logger.warning("Network: Enabling WPA2 with PSK") - # Add encrpytion directives + # Add encrpytion directives pwd = Network._generate_random_chars(20, "-", 5) new_conf.write("wpa=2\n") new_conf.write("wpa_key_mgmt=WPA-PSK\n") new_conf.write(f"wpa_passphrase={pwd}\n") new_conf.write("rsn_pairwise=CCMP\n") # Backup and move new file into place, restart service. - logger.warning("Network: Changing configuration for hostapd") + logger.warning("Network: Changing configuration for hostapd") sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") # If we changed config, restart hostapd @@ -96,12 +96,19 @@ def populate_wifi_networks(self) -> None: @staticmethod def _generate_random_chars(length: int, ch: str = "", group: int = -1) -> str: - """ Generate a string using random characters from the set of 0-9,a-z and A-Z""" - rndstr = ''.join([random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(length)]) + """Generate a string using random characters from the set of 0-9,a-z and A-Z""" + rndstr = "".join( + [ + random.SystemRandom().choice(string.ascii_letters + string.digits) + for _ in range(length) + ] + ) if ch != "" and group > 0: - rndstr = ch.join([rndstr[i:i+group] for i in range(0, len(rndstr), group)]) + rndstr = ch.join( + [rndstr[i : i + group] for i in range(0, len(rndstr), group)] + ) return rndstr - + @staticmethod def _parse_wpa_supplicant(contents: list[str]) -> list: """ @@ -196,7 +203,7 @@ def get_ap_name(self): if line.startswith("ssid="): return line[5:-1] return "UNKN" - + def get_ap_pwd(self): with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: @@ -217,7 +224,7 @@ def set_ap_name(self, ap_name): def get_host_name(self): return socket.gethostname() - + def is_ap_open(self): with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index ca2b0bd21..22af6ae74 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -50,7 +50,7 @@ def set_ap_name(self, ap_name): pass def is_ap_open(self): - return False # Not ( Is AP encrypted? ) + return False # Not ( Is AP encrypted? ) def get_host_name(self): return socket.gethostname() diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 9e2a2aef6..f44eef9f5 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -52,9 +52,7 @@ def __init__(self, *args, **kwargs) -> None: right=MarkingMenuOption( label="Passwd", callback=self.mm_display_pwd, enabled=True ), - down=MarkingMenuOption( - label="mode", menu_jump="wifi_mode" - ), + down=MarkingMenuOption(label="mode", menu_jump="wifi_mode"), ) def mm_display_qr(self, marking_menu, menu_item): @@ -89,7 +87,9 @@ def _update_info(self): if self.ap_open: self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, "0", "nopass") else: - self.wifi_qr = self._generate_wifi_qrcode(self.ap_name, self.ap_pwd, "WPA") + self.wifi_qr = self._generate_wifi_qrcode( + self.ap_name, self.ap_pwd, "WPA" + ) self.wifi_qr_scaled = False def cycle_display_mode(self): @@ -100,15 +100,17 @@ def cycle_display_mode(self): """ if self.ap_mode == "Client": # Do not cycle in client mode - return - + return + self.wifi_display_mode = ( self.wifi_display_mode + 1 if self.wifi_display_mode < DM_LAST else 0 ) self._update_info() self.update() - def _generate_wifi_qrcode(self, ssid: str, password: str, security_type: str) -> qrcode.image.base.BaseImage: + def _generate_wifi_qrcode( + self, ssid: str, password: str, security_type: str + ) -> qrcode.image.base.BaseImage: wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" logger.debug(f"WIFI Data: '{wifi_data}'") @@ -140,7 +142,7 @@ def update(self, force=False): self._display_plain_pwd(draw_pos) elif self.wifi_display_mode == DM_QR: self._display_wifi_qr(draw_pos) - elif self.wifi_display_mode == DM_CLIENT: + elif self.wifi_display_mode == DM_CLIENT: self._display_client_ssid(draw_pos) return self.screen_update() @@ -169,7 +171,6 @@ def _display_client_ssid(self, draw_pos: int) -> None: draw_pos += 16 return draw_pos - def _display_wifi_qr(self, draw_pos: int) -> None: draw_pos = self.display_class.titlebar_height + 2 if self.ap_mode == "Client": @@ -182,7 +183,7 @@ def _display_wifi_qr(self, draw_pos: int) -> None: ) draw_pos += 16 return draw_pos - + draw_pos = self._show_ssid(draw_pos, True) if not self.wifi_qr_scaled: From 23cee938785da8f4fb4e54b1a0e913ee2d3a28f3 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sat, 12 Apr 2025 07:24:43 +0200 Subject: [PATCH 25/61] Fix nox type_hint error messages. --- python/PiFinder/ui/wifi_password.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index f44eef9f5..731ef8a5d 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -147,7 +147,7 @@ def update(self, force=False): return self.screen_update() - def _display_client_ssid(self, draw_pos: int) -> None: + def _display_client_ssid(self, draw_pos: int) -> int: self.draw.text( (0, draw_pos), "Client Mode!", @@ -171,7 +171,7 @@ def _display_client_ssid(self, draw_pos: int) -> None: draw_pos += 16 return draw_pos - def _display_wifi_qr(self, draw_pos: int) -> None: + def _display_wifi_qr(self, draw_pos: int) -> int: draw_pos = self.display_class.titlebar_height + 2 if self.ap_mode == "Client": # Mode @@ -199,7 +199,7 @@ def _display_wifi_qr(self, draw_pos: int) -> None: # self.wifi_qr.save("WiFi_QR_Code_scaled.png", "PNG") self.screen.paste(self.wifi_qr, (0, draw_pos)) - pass + return draw_pos def _display_plain_pwd(self, draw_pos: int) -> None: if self.ap_mode == "Client": From 98deae9b68fd32347211be7232abadebbad0811f Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 14 Apr 2025 09:02:05 +0200 Subject: [PATCH 26/61] Move WiFi Password to "Start" and rename to "Connect WiFi" --- python/PiFinder/ui/menu_structure.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index ec2063f2e..464d7888d 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -60,6 +60,10 @@ }, ], }, + { + "name": "Connect WiFi", + "class": UIWiFiPassword + }, ], }, { @@ -836,10 +840,6 @@ }, ], }, - { - "name": "WiFi Password", - "class": UIWiFiPassword - }, { "name": "PiFinder Type", "class": UITextMenu, From d45542585d8d8110796a3e37013cabb13679835e Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 14 Apr 2025 09:02:46 +0200 Subject: [PATCH 27/61] Add check list for observation sessions --- docs/source/quick_start.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst index abb09c31b..3683e2ee4 100644 --- a/docs/source/quick_start.rst +++ b/docs/source/quick_start.rst @@ -421,6 +421,21 @@ be in your eyepiece! The numbers displayed will shift a bit and become brighter indicating a 100% reliable position. +Checklist: Observation Session +------------------------------------- + +These are the things, you need to do to start an observation session with the PiFinder: + +- Ensure the PiFinder is securely mounted and perpendicular to the ground +- Check that the internal battery is charged or an external power source is connected +- Make sure the lens cap is off +- Verify the PiFinder has a clear view of the sky and is focused. Use "Start" > "Focus" to check the focus +- Put a bright star star at the center of your eyepiece and align PiFinder to it, using "Start" > "Align" +- Confirm you have a GPS lock and local time or select your observation location from your list of saved locations, using "Start" > "Location&Time" +- Optionally connect your smartphone or tablet to the PiFinder (WiFi) network and use the webserver to control it. +- Optionally startup SkySafari and send targets to the PiFinder + + Shutting down the PiFinder --------------------------- From 6a858f8815eaacbc2a87fd178f4e6c52415ea0df Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 14 Apr 2025 09:05:13 +0200 Subject: [PATCH 28/61] Update user_guide for WiFi connection infos and name of AP --- docs/source/user_guide.rst | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst index c7d006dbf..c0ea40120 100644 --- a/docs/source/user_guide.rst +++ b/docs/source/user_guide.rst @@ -503,17 +503,23 @@ wireless access point for other devices to connect to via the Access Point (AP) :ref:`user_guide:Web Interface` or the :ref:`user_guide:status screen` to switch between these two modes and to see which mode is currently active. -Using the PiFinder in Access Point mode creates a network called PiFinderAP with no password to allow -easy connection of phones, tablets and other devices in the field. +Using the PiFinder in Access Point mode creates a network called PiFinder-12345, where 12345 will be 5 random characters, +that are determined at first startup of the PiFinder (this avoids collisions on star parties). +Open the "Start" > "Connect WiFi" menu on the PiFinder, this displays a QR code, that you can use to connect to the +PiFinder's network. This network will be encrypted using WPA2. Using **SQUARE** button, you can switch to +display the AP name and password. You can also use the Quick Menu to switch the display or switch the WiFi Mode. To use the Client mode, you'll need to add information about the WiFi network you'd like the PiFinder to connect to using the Web Interface as described in :ref:`user_guide:connecting to a new wifi network` +In order to configure the WiFi's name, password and encryption, you can use the PiFinder's web interface, +see :ref:`user_guide:Web Interface` for more details. + PiFinder address ----------------- -In most cases, you can use the name ``pifinder.local`` to connect to the PiFinder. On older computers -or those that don't support zeroconf networking, you can use the IP address provides on the :ref:`Global +Once you are connected to the same WiFi, in most cases, you can use the name ``pifinder.local`` to connect to the PiFinder. On older computers +or those that don't support zeroconf networking, you can use the IP address provided on the :ref:`Global Options` screen to connect. You can connect to the PiFinder via: @@ -533,12 +539,13 @@ The PiFinder provides an easy to use web interface which allows you to: * Backup and restore your observing logs, settings and other data * View and download your logged observations -To access the web interface for the first time, make sure the PiFinder is in Access Point mode (see :ref:`user_guide:settings menu`). This is the default for new PiFinders to make first time set up easier. Using a phone, tablet or computer, connect to the PiFinder's wireless network called PiFinderAP. It's an open network with no password required. Once connected, open your web browser and visit: -``http://pifinder.local`` +To access the web interface for the first time, make sure the PiFinder is in Access Point mode (see :ref:`user_guide:settings menu`). +This is the default for new PiFinders to make first time set up easier. Use the "Start" > "Connect WiFi" display, to connect your phone or tablet. +Once connected, open your web browser and visit: ``http://pifinder.local`` .. note:: - If you are connected to the PiFinderAP network and can't load the PiFinder web interface using + If you are connected to the PiFinder-12345 network and can't load the PiFinder web interface using http://pifinder.local try http://10.10.10.1 as some systems may not support the network features required to resolve local computer names @@ -560,12 +567,12 @@ the Tools option in the web interface. Connecting to a new WiFi network --------------------------------- -The default behavior of the PiFinder is to generate it's own WiFi network call ``PiFinderAP`` that you can connect to +The default behavior of the PiFinder is to generate it's own WiFi network call ``PiFinder-12345`` that you can connect to and configure additional networks. To get the PiFinder to connect to an existing WiFi network with Internet access you can follow the steps below: 1) Make sure the PiFinder is in Access Point mode -2) Connect your phone, tablet, or computer to the PiFinder's wifi network called PiFinderAP +2) Connect your phone, tablet, or computer to the PiFinder's wifi network called similar to PiFinder-12345, see "Start" > "Connect WiFi" for connection infos. 3) Visit http://pifinder.local using your web browser 4) Click the 'Network' link in the top bar, or if you have a smaller screen, click the three stacked horizontal lines in the upper-right corner to access the menu and choose 'Network' from there. .. image:: images/user_guide/pf_web_net0.png @@ -580,7 +587,6 @@ can follow the steps below: To add more WiFi networks for the PiFinder to look for, navigate to the Network Setup page of the :ref:`user_guide:web interface` and click the + button near the list of WiFi networks and repeat the steps above. - SkySafari =================== @@ -599,8 +605,11 @@ Shared Data Access In the course of using the PiFinder several data files are created that may be of interest. These are available via a SMB (samba) network share called ``//pifinder.local/shared``. Accessing this will depend on your -OS, but the PiFinder should be visible in a network browser provided. There is no password requirement, -just connect as ``guest`` with no password provided. +OS, but the PiFinder should be visible in a network browser provided. On Windows, open up file explorer and enter +``\\pifinder.local\shared`` in the address bar. On Mac, open Finder and select Go > Connect to Server and enter +``smb://pifinder.local/shared``. On Linux, you can use the file manager or command line to access the share. + +There is no password requirement, just connect as ``guest`` with no password provided. Once connected, you'll see: From 70c95257b5cdec6096cbd4b60bb82a1c1bcef79d Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 15 Apr 2025 08:38:54 +0200 Subject: [PATCH 29/61] Context help for "connect WiFi" --- help/wifi_connect/1.png | Bin 0 -> 6655 bytes help/wifi_connect/1.xcf | Bin 0 -> 9739 bytes help/wifi_connect/2.png | Bin 0 -> 6092 bytes help/wifi_connect/2.xcf | Bin 0 -> 17921 bytes help/wifi_connect/3.png | Bin 0 -> 7369 bytes help/wifi_connect/3.xcf | Bin 0 -> 16246 bytes python/PiFinder/ui/wifi_password.py | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 help/wifi_connect/1.png create mode 100644 help/wifi_connect/1.xcf create mode 100644 help/wifi_connect/2.png create mode 100644 help/wifi_connect/2.xcf create mode 100644 help/wifi_connect/3.png create mode 100644 help/wifi_connect/3.xcf diff --git a/help/wifi_connect/1.png b/help/wifi_connect/1.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cb7d2939138197caa14f51ea0c8c9b029b3e4c GIT binary patch literal 6655 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 literal 0 HcmV?d00001 diff --git a/help/wifi_connect/1.xcf b/help/wifi_connect/1.xcf new file mode 100644 index 0000000000000000000000000000000000000000..5f4cc96a99ceb7abe1cc0d885da3efc3f8664f67 GIT binary patch literal 9739 zcmeHNTW=f36&@~^D_in4bPG6j2%3?rwOdmX+gMI0%WATjvM7ZHrhS6bmhQ!-#uKk3 z{@eWgt4aT+m^ar-xf13D<1g{#j)_<2zL5PGv5rms>?QJ_94o;1*u>9oj=uY{F|x0v z^U=LRsSy7_yv7;4A&98Yz6ic}In3yK(Ok%H-HMg+3k$L6{o=~4*u7F|{k_rAwT*ag zzHrx!8->+T(LIXA9gR&*jvi@0wKAWquE&L7{F$Mf96f#q#OvcLa?sc4k_?2XqA26> z^|eJoiJdKqf+}{pgL+xew;6ts4NwJNWQSOX#)Dk)(StrZ>Z7mu=pi3{%158}(Pu%6 z@w#OIgcw_lb)`e&L52nGpy2tokM?IPu)8HB(%aL8fgx%qQlWQ1qg)X-yM=)e6(wYP z&+|3!dFe0S^YT~TlaL+q1#Oy^rH5!Mt7n3AFRSa6x<88E%FwKguJ(*Rgr91VI~AF> zYPVooNnK*!?t06om`8tk~@n{dk$58976DcrK?%%*F3 zn=f}YzOnc=x=hip!%?9T;!PWE%)4&Fk2=pldRf^2C%nKv-kUl{-(I zNRT;-LYnFk>oCpAQ10aw+3XUV^YPA+ClN@&k`g-B1{VV>FqY`$7TFRZWj5>Mo+bOR zFsrIdtlYzPv;pQMIG17#?VXtx79ZHkQp*m|A znlk9BM@bViTSE9}W!5f7g2Y)r{m6L;#={ejhJ*nI<{rFU(%@rJ>8FE!I_Rf^emdx< zgFZSCIIWKk`sl!q4j6Ux)8dQ(q@K~Ufc|LnnI&lI_Ma+^NZSKpbuKH^f=YI@ud; zoW(2!#{nU*L;enAK0sROxk%ClvIZXEGD}aAwi0=lIAK|HRE2z_8Ae*sG?kE~mQc{r zlt>53hX8wnY!9$qBg9}!XgEfM97GHQ01}|{YrG;wEe*8p3II%Mpc8 zlqYYJra=T$Z@|HDSz(7gjvjS8nnZRZm~9zox<}+1*;hzJB}trBZjk*YO(Cj?$~(kK zDmX1okal88V(nt`Jx*v7%iR^!AkG{nHFYW*IrImWF z*9N;+asW;O@!+GJ?3Fm#y`B@dbZLMd55@TJ=pZ8Q=sFjUL4jk86YkyKTb}vOg{8me zl4lloqzZGfl)ze^61oIp3#kFJHzyEE7|pQPhnq+3BnyysxEx|m`egS45=@D-BL=`p zS#!ZjWM}A0(g^(e^FMti|MBb3zF^l0NlmNTbR>=noCFvdC2i@>z=QVZHzX2B1rnm} zr>2RTN+eS1WztSou$06Yl~cSr?)M)C){zheCr)BHQBzsG#r=Ag4X zUw4lM#PP4207Vw;R@Kc@$a~+4yw~N5CwjzLP%OoNj92aOlHIo=+4)s)GCIz5y&Z0* z+fBQvx!ZH?DAnQE<#YC@;#7vj_c!wI)Wk53c9CQ%iCovof=!VHhY_t^ZJI=o43nud z_$#%ypRRzvcM%6`@RXDqJW75)Mu$W~DQ8+fN_itLuQi>DNEe-OsFUnemwTWJ%M(FG lq`7Ng1xaH#nVKHnA%Df6y8ChN$C>@#ywCU_3e%lR{tK0_8AAX7 literal 0 HcmV?d00001 diff --git a/help/wifi_connect/2.png b/help/wifi_connect/2.png new file mode 100644 index 0000000000000000000000000000000000000000..f6dc56ca4450bddae5b93cd35016df32c8cb0022 GIT binary patch literal 6092 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
    gba8XcE+ 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 literal 0 HcmV?d00001 diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 731ef8a5d..55ad82bc3 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -22,7 +22,7 @@ class UIWiFiPassword(UIModule): UI for displaying the Access Point name and password. """ - __help_name__ = "wifi_password" + __help_name__ = "wifi_connect" __title__ = "WIFI" def __init__(self, *args, **kwargs) -> None: From 044a17337abf9ae7997e5e013cce4c12a87d7fc2 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 15 Apr 2025 08:52:25 +0200 Subject: [PATCH 30/61] Save changes to help screens --- help/object_details/2.xcf | Bin 0 -> 18010 bytes help/wifi_connect/3.xcf | Bin 16246 -> 21190 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 help/object_details/2.xcf diff --git a/help/object_details/2.xcf b/help/object_details/2.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b59101e95e59908d97fae1f616a034c0aa5aa84c GIT binary patch literal 18010 zcmeHP-H#l_5wD%y?XwSmkud~IFkY5}8$0sdc?=j>+gxDmY?4+&%%Tu!Uyk+O`6l<_ z%$OrJY6=uS>G z=4a-^!4eKvS4-uy!`~WyVfdS+$(i|y#d7KB)1`8K>$IGR$L+J3ed+5+G z>Yl%-n)G~W;le~?dTy$8bPVG}TdtCeBNykU7faJ~jq}X~&|I3IoLp!wVq&20tDEXp zViwT;KB3Jv7bnh5EKVeXvyPv5{?hDB>Gfu~Fg-taa`@=T*l?*i*O)(tDV-dCrFQ1X zGsDlH8a$CQo`OtIEKW?7<|byFC(EZVO;3(|o7ZFHh3UnGmzv?r6SEg)n&nf^l<@Dw zC`kW3i)NI~xv9nT2qI=`upK@b8c~B5%-18zhow7n^2vLO#2FCT|RZ} zOXy$iF`)CY<4-;_`s|mD@uPdqti~@xLm<(<{#p2}9L>^&usJz>>126v zdUCQ{x){!!ET3Ooyzt!U=-k4{#JTy`nj?+**-`#_6pK4rK6PTW%e`%7UY)%#!h|Dt z72S!^o({13^c{u!`YurQKj^zwsB_^%cq13)rntsWH6H?npZFN%QLaC)@QGakh1+?T zC?|EC=;6NlU|+q|S3lfW=b3RoeC$F=>%05vdr{}{(qjM>rsJ_5PwF_)5Z7ZII6u}` zPj{{Kp70eP#RKNNNvSo&#StR(Z(au<%WKn#CS{EYP#&dD<^96t&O1-#w|r}O4^k(8cE{x<2_YDIeKvz0r1lGM zy)E8cdi%}Fmap|>Tlpdhu}Pk^AEKPdQ(o-AIY+UuC=P_$^rZ40q}xuYR6~%+t4<(` zv@Vnqp0ensFJ-9eQre(1(wQ2rLCP}V}_-@&ws+tEz2sA9Q;KHf4nNCPs%lcuGWir`kt z{+ZL&>ohEH=x!r0^cb2t=3sH6xi2&C{*AS>87VbNVY*FRUaqpO4l&Cu!>wcSRKpj^V46#XRuKCM zpG9jnx~ePJ>gN=+;bVzvS|+$RD0bhyfyq~45USc1ChQh?S~u-LRnohwZ;=V)-oTv0 zZCtklh5ZUb&|D$Z9V6-$xXHJyrDtQ-;yM#bsgzlSyj=0LmPyDNx`NeSk05`-YTtx@ z8cx05o4hau)y9$+)vAORV97(s#i$R~Hc_88*ja^&P`;eq%Kd=a*pCiT&fQ0N_&ryP z+uo}$4pFC8-$^%IDbW?CY<80+Pf8auleR7Slc*wP?XCm z`rOC8c)oI_a-kq$sDhoW`)UJSdn!+Vij+-$z)EH|6juc{_Cm)~J`}2Rlx0s-DE}G$c zR^#zP7lRwtZSC=@V;zVAfTZZ#6T1DD%iSIajL5KCM_NN@2KF#isg4f;xX^f88+cnQ zKLp2?U33driAx2w0yh|*GY}dvpkgshai~3NIHhpGYh=I=7tmXg*4X#7Rg|CxhtX)k z*Kfn)giq@NOVi-RVS>9Fz*`Ll(uO#)f(-_N`kH{N8BU%|!pNiKBKW$%#CnaG(W(cx zv%yGGx4ED;F?T>kom#Jh0zCM(@PLryM9Vq`R7V2UgTOWc3j&5&_oVMio+;dOr$j68 zzyU(RI}h?|6RYJjd;kZRpoX!3kgf!9k!z|>Zcr1rccMsnq7Di`3bqqP2kCDH8iUs@ zFbIGO?SZqG44<39NY|l%Fb$)Fz{(m-!yt_Iqyq+}9Qud}n9=8@00DqPLjd%IF*!Yz zIxsax2!cwT0TJ#WkS6$|nrs9IX&pEs*3zO+@O^{#5(n1--;c*wN97=GU?3ZC)pZ+i z3qS?oIJiH+pB>wIR70Y>X{{F38!Cb}v;idSrggg>h7G4hpAj&lAhw@n)D)2}}}#(I5u^P{;?u3z=5a?4_Hm!VE<+vBlt;K{hm63J3;b0LrRq?nZ174Nc5y zpu8S&i?XM<|B=m};_NBTp5p9XoV|;$1bFpk@8bXByV#;X3Itx98eZiozRb1~M{21N zMJ`)ywr2|$0Z+sf*%&Y(lU(#?--C(MWkaMxBDTU70Rqa#?y7i!;*bfVdpX&LbpeCO zfi?0WMo5PvjyVS~n<^Lzfi@FBieSOS2q?kq*jfbb6PzTVY;do^;)lFnSPLf+;J=MqJG+ zstq=jG4F14dJu8Xyt*c}b%gJcyu&MwN{&ZG)wZQLVzZ zBusZb*=`897olGAc?j3r!Uh*V?lNtUP;*xWx}g^=nIC9-^mj(5mnyvQJ|@nKyp`vZ z3S!C-OuA^j;Peuw0@Na`I~*HrH!~5BRHPVw#MPdLbU~TVc*#R7xf7{K-&w)}4-PBM zG3373bPnO;O<0_|t(B`I*vGaq$M0fqzh3Mz923qvz!m2GHQLZ&L-;4`k3g*Ws=&^Q zk3O)#yuzs}Bu>i!t)!7d-eePdeEp_!~ml0uQ0lfn>C{rrSht6r<&$znDip z@r*#DlS2605LLKXMi(r)n&?YPDWc@bhe{y4ue~OY7hlD6eU65*GH9j1!b59rLbeGN z`R4gAvM-g_(PLyQz!i0jw!odYomSx-ZREcD-Y?!Qyz}Y1zf?z%1G6n*r$im=?7-#D zdr#v*zawcUO}%*oL)<-W=Pjh|%Hp3o?F><8&zXTOUzmiQ zk-j!W_Bo}fML(x(L&npYZOA%ZDBF-tDo3^<h=oLR`3 zh5UbDA#V>%q;Lgd4Gm5J4wc=DHEbhZOF#-Fmm|`Fz*ilZ#9f0J2x=KzVvw$}gQ63s z4W+H!(;WPJ_#sH0?m-i++)oH`M0qShAYu3=ZFRu?W@@;XK!uPkybr|iL}U1w*8$HMscfTk zkQ%LgOcX>Br?E{Aq!dU3FKvz;kE!l3DeeYX03x7ah9VPqs$Ky$fB@uke4mkj0^}|FHMJrCJ)o5Mg+Rwe zsE`2xG(eUE)J6t6&<6O^sCU~zSg)ZOB*@P5CEzb_03eK6Xtbh&JbT=xMkWkEf%RL3 zD#C+?GmN=&C=|1e05=7IlMs=`sxsJt*~SQjvr!~d| zpBiB|2JBMAp$A1^E&~0ngB^1$(pB6!RMCV0fom}I@v+g76makp2nxOG+v{WHC@ls; zJF5W_;MWW*fCv6%39Q#dB)Ag{Cuo^L6Z{HsR|b1|`s?{T%MA z?@8+XHxTxrB!4sD2~>BX^dI|i!Cf$E-*NhVT&ScZ-<|%gAvp6rbJm{wAxJ(Sk&N2rBc=7M89DfBOFxBJBa literal 0 HcmV?d00001 diff --git a/help/wifi_connect/3.xcf b/help/wifi_connect/3.xcf index 9d7e4f6b029fe39c7a44438b84441992b989d3f3..9ca3a0f3f1aa3636dddad3bab393269b307b3fb6 100644 GIT binary patch literal 21190 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 From 4bf43b530310eb72ee0e8d2a87c6641b70c559a3 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 15 Apr 2025 18:46:05 +0200 Subject: [PATCH 31/61] Split change of SSID and encryption. If wifi is encrypted enable change of wpa_passphrase through web server. Enable change of wifi country through web server --- python/PiFinder/main.py | 24 ++++--- python/PiFinder/server.py | 30 +++++++- python/PiFinder/sys_utils.py | 113 ++++++++++++++++++++++++------ python/PiFinder/sys_utils_fake.py | 14 +++- python/views/network.tpl | 18 ++++- 5 files changed, 161 insertions(+), 38 deletions(-) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index 70fa65fd1..91f7e6068 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -273,7 +273,7 @@ def main( logger.info("PiFinder running on %s, %s, %s", os_detail, platform, arch) sys_utils = utils.get_sys_utils() - sys_utils.Network.secure_accesspoint() + sys_utils.Network.configure_accesspoint() # init UI Modes command_queues = { @@ -489,19 +489,25 @@ def main( try: gps_msg, gps_content = gps_queue.get(block=False) if gps_msg == "fix": - logger.debug("GPS fix msg: %s", gps_content) + # logger.debug("GPS fix msg: %s", gps_content) if gps_content["lat"] + gps_content["lon"] != 0: location = shared_state.location() # Only update GPS fixes, as soon as it's loaded or comes from the WEB it's untouchable - if not location.source == "WEB" and not location.source.startswith("CONFIG:") and ( - location.error_in_m == 0 - or float(gps_content["error_in_m"]) - < float( - location.error_in_m - ) # Only if new error is smaller + if ( + not location.source == "WEB" + and not location.source.startswith("CONFIG:") + and ( + location.error_in_m == 0 + or float(gps_content["error_in_m"]) + < float( + location.error_in_m + ) # Only if new error is smaller + ) ): - logger.info(f"Updating GPS location: new content: {gps_content}, old content: {location}") + logger.info( + f"Updating GPS location: new content: {gps_content}, old content: {location}" + ) location.lat = gps_content["lat"] location.lon = gps_content["lon"] location.altitude = gps_content["altitude"] diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index f0f08c510..3ba68b60e 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -190,13 +190,15 @@ def advanced(): @app.route("/network") @auth_required - def network_page(): + def network_page(err_pwd="", err_country=""): show_new_form = request.query.add_new or 0 return template( "network", net=self.network, show_new_form=show_new_form, + err_pwd=err_pwd, + err_country=err_country, ) @app.route("/gps") @@ -409,10 +411,30 @@ def network_delete(network_id): def network_update(): wifi_mode = request.forms.get("wifi_mode") ap_name = request.forms.get("ap_name") + wifi_country = request.forms.get("wifi_country") host_name = request.forms.get("host_name") + error_triggered = False + err_pwd = "" + if not self.network.is_ap_open(): + ap_passwd = request.forms.get("ap_passwd") + if len(ap_passwd) < 8: + err_pwd = "Password must be at least 8 characters" + error_triggered = True + else: + self.network.set_ap_pwd(ap_passwd) + + err_country="" + if wifi_country not in self.network.COUNTRY_CODES: + err_country = "Invalid country code" + error_triggered = True + + if error_triggered: + return network_page(err_pwd=err_pwd, err_country=err_country) + self.network.set_wifi_mode(wifi_mode) self.network.set_ap_name(ap_name) + self.network.set_ap_wifi_country(wifi_country) self.network.set_host_name(host_name) return template("restart") @@ -843,11 +865,12 @@ def time_lock(time=datetime.now()): server=CherootServer, ) except (PermissionError, OSError): - logger.info("Web Interface on port 8080") + logger.info("Web Interface on port 8181") + debug() run( app, host="0.0.0.0", - port=8080, + port=8181, quiet=True, debug=True, server=CherootServer, @@ -876,3 +899,4 @@ def run_server( ): MultiprocLogging.configurer(log_queue) Server(keyboard_queue, ui_queue, gps_queue, shared_state, verbose) + diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index bf5ce1e8e..5959cc775 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -23,13 +23,18 @@ class Network: def __init__(self): self.wifi_txt = f"{utils.pifinder_dir}/wifi_status.txt" - with open(self.wifi_txt, "r") as wifi_f: - self._wifi_mode = wifi_f.read() + self._wifi_mode = Network.get_wifi_mode() self.populate_wifi_networks() + self.populate_wifi_countries() @staticmethod - def secure_accesspoint() -> None: + def get_wifi_mode(): + with open("/tmp/wifi_mode.txt", "r") as wifi_f: + return wifi_f.read() + + @staticmethod + def configure_accesspoint() -> None: """Add WPA2 encryption, if not already enabled. Tasks: @@ -43,16 +48,14 @@ def secure_accesspoint() -> None: action_needed = False with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: - if line.startswith("ssid=") and "CHANGEME" in line: - action_needed = True - - if not action_needed: - return + if not (line.startswith("ssid=") and ("ENCRYPTME" in line or "CHANGEME" in line)): + return - logger.info("SYSUTILS: Changing WIFI.") + logger.info("SYSUTILS: Configuring WIFI Access Point definition.") passphrase_detected = False ssid_changed = False + encryption_needed = False with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: @@ -60,13 +63,16 @@ def secure_accesspoint() -> None: ap_rnd = Network._generate_random_chars(5) line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True - logger.warning(f"Network: Changing SSID to 'PiFinder-{ap_rnd}'") + logger.warning(f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'") + elif line.startswith("ssid=") and "ENCRYPTME" in line: + encryption_needed = True elif line.startswith("wpa_passphrase="): passphrase_detected = True new_conf.write(line) # consumed all lines, so: - if not passphrase_detected and ssid_changed: - logger.warning("Network: Enabling WPA2 with PSK") + if encryption_needed and not passphrase_detected: + # Do not change password, if passphrase was detected + logger.warning("SYS-Network: Enabling WPA2 with PSK") # Add encrpytion directives pwd = Network._generate_random_chars(20, "-", 5) new_conf.write("wpa=2\n") @@ -77,10 +83,10 @@ def secure_accesspoint() -> None: logger.warning("Network: Changing configuration for hostapd") sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") - # If we changed config, restart hostapd - if not passphrase_detected or ssid_changed: - logger.warning("Network: Restarting hostapd") - sh.sudo("systemctl", "restart", "hostapd") + # If we are enabling encryption or changed SSID, restart hostapd, if in AP mode + if (not (passphrase_detected and encryption_needed) or ssid_changed) and Network.get_wifi_mode() == "AP": + logger.warning("Network: Restarting hostapd") + sh.sudo("systemctl", "restart", "hostapd") def populate_wifi_networks(self) -> None: wpa_supplicant_path = "/etc/wpa_supplicant/wpa_supplicant.conf" @@ -94,6 +100,22 @@ def populate_wifi_networks(self) -> None: self._wifi_networks = Network._parse_wpa_supplicant(contents) + def populate_wifi_countries(self) -> None: + """ + Read country codes from iso3166.tabs + """ + try: + with open("/usr/share/zoneinfo/iso3166.tab", "r") as iso_countries: + lines = iso_countries.readlines() + lines = [line for line in lines if not line.startswith("#") and line != "\n" and line != "\t\n"] + self.COUNTRY_CODES = [line.split("\t")[0] for line in lines] + logger.debug(f"Country Codes: {self.COUNTRY_CODES}") + # print(self.COUNTRY_CODES) + except IOError as e: + logger.error("Error reading /var/share/zoneinfo/iso3166.tab", exc_info=1) + self.COUNTRY_CODES = ['US', 'CA', 'GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'JP', 'CN'] + logger.error(f"Using default country codes: {self.COUNTRY_CODES}") + @staticmethod def _generate_random_chars(length: int, ch: str = "", group: int = -1) -> str: """Generate a string using random characters from the set of 0-9,a-z and A-Z""" @@ -197,13 +219,6 @@ def add_wifi_network(self, ssid, key_mgmt, psk=None): # Restart the supplicant wpa_cli("reconfigure") - def get_ap_name(self): - with open("/etc/hostapd/hostapd.conf", "r") as conf: - for line in conf: - if line.startswith("ssid="): - return line[5:-1] - return "UNKN" - def get_ap_pwd(self): with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: @@ -211,6 +226,24 @@ def get_ap_pwd(self): return line[15:-1] return "" + def set_ap_pwd(self, ap_pwd): + if ap_pwd == self.get_ap_pwd(): + return + with open("/tmp/hostapd.conf", "w") as new_conf: + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("wpa_passphrase="): + line = f"wpa_passphrase={ap_pwd}\n" + new_conf.write(line) + sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + + def get_ap_name(self): + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("ssid="): + return line[5:-1] + return "UNKN" + def set_ap_name(self, ap_name): if ap_name == self.get_ap_name(): return @@ -222,6 +255,34 @@ def set_ap_name(self, ap_name): new_conf.write(line) sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + def get_ap_wifi_country(self): + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("country_code="): + return line[13:-1] + return "US" + + def set_ap_wifi_country(self, country_code): + country_changed = False + with open("/tmp/hostapd.conf", "w") as new_conf: + no_country = True + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + if line.startswith("country_code="): + line = f"country_code={country_code}\n" + no_country = False + country_changed = True + new_conf.write(line) + if no_country: + new_conf.write(f"country_code={country_code}\n") + if country_changed: + try: + sh.sudo("raspi-config", "nonint", "do_wifi_country", country_code) + sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + except: + logger.warning(f"SYS: Failed to set wifi country code to {country_code}") + raise + def get_host_name(self): return socket.gethostname() @@ -407,3 +468,9 @@ def switch_cam_imx296() -> None: def switch_cam_imx462() -> None: logger.info("SYS: Switching cam to imx462") sh.sudo("python", "-m", "PiFinder.switch_camera", "imx462") + + +if __name__ == "__main__": + # This is for testing purposes only + network = Network() + print(network.COUNTRY_CODES) \ No newline at end of file diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 22af6ae74..21c6e22f3 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -12,7 +12,7 @@ class Network: """ def __init__(self): - pass + self.COUNTRY_CODES = ['US', 'BE', 'DE'] @staticmethod def secure_accesspoint() -> None: @@ -43,14 +43,24 @@ def get_ap_pwd(self): # Return an example password, e.g. to test the password display. return "UNKN8-01234-abcde-testpw" + def set_ap_pwd(self, ap_pwd): + pass + def get_ap_name(self): return "UNKN" def set_ap_name(self, ap_name): pass + def get_ap_wifi_country(self): + return "UNKN" + + def set_ap_wifi_country(self, ap_wifi_country): + pass + def is_ap_open(self): - return False # Not ( Is AP encrypted? ) + return True + # return False # Not ( Is AP encrypted? ) def get_host_name(self): return socket.gethostname() diff --git a/python/views/network.tpl b/python/views/network.tpl index f5f9f2a27..167864fc6 100644 --- a/python/views/network.tpl +++ b/python/views/network.tpl @@ -27,7 +27,23 @@
    - + +
    +
    + % if not net.is_ap_open(): +
    +
    + + +

    {{err_pwd}}

    +
    +
    + %end +
    +
    + + +

    {{err_country}}

    From 6cc710ecd0bd63a432bc8440d0a723acc47f6433 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Tue, 15 Apr 2025 18:58:04 +0200 Subject: [PATCH 32/61] Silence type_hints --- python/PiFinder/sys_utils.py | 2 +- python/PiFinder/sys_utils_fake.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 5959cc775..dcc45e8d7 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -112,7 +112,7 @@ def populate_wifi_countries(self) -> None: logger.debug(f"Country Codes: {self.COUNTRY_CODES}") # print(self.COUNTRY_CODES) except IOError as e: - logger.error("Error reading /var/share/zoneinfo/iso3166.tab", exc_info=1) + logger.error("Error reading /usr/share/zoneinfo/iso3166.tab", exc_info=True) self.COUNTRY_CODES = ['US', 'CA', 'GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'JP', 'CN'] logger.error(f"Using default country codes: {self.COUNTRY_CODES}") diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 21c6e22f3..51007c93f 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -15,7 +15,7 @@ def __init__(self): self.COUNTRY_CODES = ['US', 'BE', 'DE'] @staticmethod - def secure_accesspoint() -> None: + def configure_accesspoint() -> None: pass def populate_wifi_networks(self): @@ -59,8 +59,7 @@ def set_ap_wifi_country(self, ap_wifi_country): pass def is_ap_open(self): - return True - # return False # Not ( Is AP encrypted? ) + return False # i.e. encrypted = Not ( Is AP encrypted? ) def get_host_name(self): return socket.gethostname() From 19d090680304c3137962e46e1d5c777d402d1497 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Thu, 17 Apr 2025 08:51:12 +0200 Subject: [PATCH 33/61] Info in how to startup an open Access Point. --- docs/source/build_guide.rst | 32 +++++++++++++++++++++++++++++++- docs/source/user_guide.rst | 17 +++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/source/build_guide.rst b/docs/source/build_guide.rst index 956f82c2b..ee6b239fe 100644 --- a/docs/source/build_guide.rst +++ b/docs/source/build_guide.rst @@ -200,7 +200,37 @@ After you have all the pins soldrerd, it's a good time to insert the SD card and .. image:: images/build_guide/ui_module_18.jpeg -There you go! The PiFinder hat is fully assembled and you can move on to printing your parts or :ref:`final assembly` +There you go! The PiFinder hat is fully assembled and you can move on to configuring the software, to :ref:`printing your parts` or :ref:`user_guide: final assembly` + +Test & Software Setup +========================= + +Now is the right time to provide power to the Pi and check if everything is alright and configure PiFinder's Access Point and WiFi networks you want to connect to. Note that you can do this also after the assembly, but it is easier and safer to do it now, when all ports of the Raspberry Pi are accessible (in case something goes wrong and you're not able to connect to the PiFinder) and it is easier to exchange a LED, if necessary. + +.. note:: + Set the PiFinder on the desk and leave it sitting there, until the menu displays. The motion detector (IMU) needs to calibrate itself and it is not able to do so if you move it around. If you hold it in your hand, then the next test may fail. + +If the PiFinder is powered up, the screen should display the main menu. If it does not, please check the soldering of the components and the connections to the Raspberry Pi. You may have to reflow the solder joints. + +Once the menu displays correctly, move to Tools > Status. This displays the current status of the PiFinder. You should see the following: + +.. image:: images/build_guide/status.png + :target: images/build_guide/status.png + :alt: Status of PiFinder on first power up. + +The IMU displays "static" or "moving" and the GPS displays 0/0 or N/0 where N is a small number. + +WiFi configurations +----------------------- + +Now insert a network cable into the Raspberry Pi and connect it to your router. The PiFinder will use DHCP to configure its network. Then use an internet browser on your regular computer and enter ``pifinder.local`` in the address bar. This opens up the PiFinder web interface. If this does not work, you can also try to enter the IP address of the Raspberry Pi (you can find it in your router). + +.. warning:: + If you do not attach a network cable now, you have to do the configuration later on. If you access the PiFinder in its default configuration, any passwords you enter will be transferred in clear over the air. + +Now using the web interface, configure your PiFinder's Access Point and add any additional WiFis that the PiFinder is going to connect to. This is described at :ref:`user_guide:WiFi`. Note that the default password is ``solveit``. As part of the configuration, your PiFinder will reboot. + +Once you have configured the Access Point and WiFi networks, you can display the network's QR code in "Start" > "Connect WiFi", now scan the code with your phone or tablet to connect to the PiFinder's Access Point. Open up a browser and enter ``pifinder.local`` in the address bar. This opens up the PiFinder web interface. If this works, congrats! You have successfully configured PiFinder's Access Point and WiFis! Configurations Overview ======================== diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst index c0ea40120..ee9c8fc2c 100644 --- a/docs/source/user_guide.rst +++ b/docs/source/user_guide.rst @@ -587,6 +587,23 @@ can follow the steps below: To add more WiFi networks for the PiFinder to look for, navigate to the Network Setup page of the :ref:`user_guide:web interface` and click the + button near the list of WiFi networks and repeat the steps above. + +Reset Access Point +-------------------------------------- + +.. stop:: + Only do this if you're not able to connect to the PiFinder's access point using the QR code from "Start" > "Connect WiFi", or you can't display it. + +If you can't connect to the PiFinder's access point using the QR code from "Start" > "Connect WiFi", or this doesn't display, you can login into the PiFinder using SSH, if you have a network cable connected. If this does not work, you can plug-in a monitor cable and a keyboard and directly login to the Raspberry Pi. The default username and password are ``pifinder`` and ``solveit``. You may have to open up the PiFinder to have access to the mentioned ports. + +1. Login into PiFinder as described above. You should be in the home directory. Check with ``pwd``, it should display ``/home/pifinder``. If not, execute ``cd``. +2. Copy over the default configuration file for hostapd into it's default location: ``sudo cp pi_config_files/hostapd-open.conf /etc/hostapd/hostapd.conf``. +3. Then check, if PiFinder is in "Client" or "Access Point" mode. Execute ``cat wifi_status.txt``. This should display either "AP" or "Client" +4. If it says "client", execute ``sudo switch-ap.sh``. +5. Now reboot the PiFinder with ``sudo reboot``. + +After the reboot, PiFinder presents with an open Access Point with-out encryption. + SkySafari =================== From a4d310c8de6e81e4bfed710acc64562b80d5b028 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 19:18:01 +0200 Subject: [PATCH 34/61] Server: Added enryption checkbox to Network Settings --- python/PiFinder/server.py | 20 ++++++++++++++------ python/views/network.tpl | 12 ++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 519fd8845..3a2829e3a 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -413,18 +413,26 @@ def network_delete(network_id): def network_update(): wifi_mode = request.forms.get("wifi_mode") ap_name = request.forms.get("ap_name") + ap_passwd = request.forms.get("ap_passwd") wifi_country = request.forms.get("wifi_country") host_name = request.forms.get("host_name") error_triggered = False err_pwd = "" - if not self.network.is_ap_open(): - ap_passwd = request.forms.get("ap_passwd") - if len(ap_passwd) < 8: - err_pwd = "Password must be at least 8 characters" - error_triggered = True - else: + if self.network.is_ap_open(): + ap_encrypt = request.forms.get("ap_encrypt") + if ap_encrypt == "1": + try: + self.network.set_ap_pwd(ap_passwd) + except Exception as e: + err_pwd = "Invalid password: " + e.args[0] + error_triggered = True + else: + try: self.network.set_ap_pwd(ap_passwd) + except Exception as e: + err_pwd = "Invalid password: " + e.args[0] + error_triggered = True err_country="" if wifi_country not in self.network.COUNTRY_CODES: diff --git a/python/views/network.tpl b/python/views/network.tpl index 167864fc6..ddaafc3fe 100644 --- a/python/views/network.tpl +++ b/python/views/network.tpl @@ -30,7 +30,16 @@
    - % if not net.is_ap_open(): + % if net.is_ap_open(): +
    +
    + +
    +
    + %end
    @@ -38,7 +47,6 @@

    {{err_pwd}}

    - %end
    From 0b6a3050eb544c2058c454a65072df7331e6c071 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 19:26:33 +0200 Subject: [PATCH 35/61] Change set_ap_pwd() to enable enryption, if necessary. --- python/PiFinder/sys_utils.py | 39 ++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index dcc45e8d7..1d79b0503 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -12,6 +12,7 @@ import logging BACKUP_PATH = "/home/pifinder/PiFinder_data/PiFinder_backup.zip" +NO_PASSWORD_DEFINED = "" logger = logging.getLogger("SysUtils") @@ -38,11 +39,12 @@ def configure_accesspoint() -> None: """Add WPA2 encryption, if not already enabled. Tasks: - 1) if SSID in current config contains CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) - 2) If SSID was changed, add encryption to hostapd.conf, generate a 20 character random password - (20 chars in 5 groups of random chars, separeted by '-', see below) + 0) If passphrase is already in hostapd.conf, do not change it (this ignores the case where the ap_name contains ENCRYPTME) + 1) if SSID in current config contains CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) and use that. + 2) If SSID in current config contains ENCRYPTME, add encryption to hostapd.conf, generate a 20 character random password + (20 chars in 5 groups of random chars, separeted by '-', see below) - where 'random char' means from a randomly selected character out of the set of 0-9, a-z and A-Z. + where 'random char' means a randomly selected character out of the set of 0-9, a-z and A-Z. """ action_needed = False @@ -224,11 +226,36 @@ def get_ap_pwd(self): for line in conf: if line.startswith("wpa_passphrase="): return line[15:-1] - return "" + return NO_PASSWORD_DEFINED def set_ap_pwd(self, ap_pwd): - if ap_pwd == self.get_ap_pwd(): + """ Set Access Point password. + + If the password is the same as the current password, nothing is done. + + If the password is NO_PASSWORD_DEFINED we consider the Access Point as open and enable encryption as a result of calling this method. + It is the responsiblity of the caller to ensure that this method is only called when the AP needs to be encrypted. + + This method throws an ValueError of the password is < 8 or > 63 characters long. + """ + current_pwd = self.get_ap_pwd() + if ap_pwd == current_pwd: return + + # Enable encryption, if needed + if current_pwd == NO_PASSWORD_DEFINED: + ap_name = self.get_ap_name() + self.set_ap_name(ap_name + "ENCRYPTME") + Network.configure_accesspoint() + self.set_ap_name(ap_name) + + # Check password length + if len(ap_pwd) < 8: + raise ValueError("Password must be at least 8 characters long") + if len(ap_pwd) > 63: + raise ValueError("Password must be at most 63 characters long") + + # Change password with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: From ff5b45f8599b05e133dc8dfe83a9e19bf5b5d9b1 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 20:22:12 +0200 Subject: [PATCH 36/61] Ignore that IMU is absent. --- python/PiFinder/imu_pi.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/PiFinder/imu_pi.py b/python/PiFinder/imu_pi.py index 8736ed004..c28f54d89 100644 --- a/python/PiFinder/imu_pi.py +++ b/python/PiFinder/imu_pi.py @@ -24,7 +24,14 @@ class Imu: def __init__(self): i2c = board.I2C() - self.sensor = adafruit_bno055.BNO055_I2C(i2c) + self.sensor = None + self.calibration = 0 + try: + self.sensor = adafruit_bno055.BNO055_I2C(i2c) + except ValueError as e: + logger.error("IMU: No IMU found", exc_info=True) + return + self.sensor.mode = adafruit_bno055.IMUPLUS_MODE # self.sensor.mode = adafruit_bno055.NDOF_MODE cfg = config.Config() @@ -52,7 +59,6 @@ def __init__(self): ) self.quat_history = [(0, 0, 0, 0)] * QUEUE_LEN self._flip_count = 0 - self.calibration = 0 self.avg_quat = (0, 0, 0, 0) self.__moving = False From babaee25a63ccb60dbd295c8aabfe417e1e0a30d Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 20:55:35 +0200 Subject: [PATCH 37/61] Revert last commit --- python/PiFinder/sys_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 1d79b0503..4c438690a 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -31,7 +31,7 @@ def __init__(self): @staticmethod def get_wifi_mode(): - with open("/tmp/wifi_mode.txt", "r") as wifi_f: + with open("/home/pifinder/PiFinder/wifi_status.txt", "r") as wifi_f: return wifi_f.read() @staticmethod From ea157e56c6e3ee5a855242d76ce7d71eebcd146f Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:07:15 +0200 Subject: [PATCH 38/61] Revert imu commit --- python/PiFinder/imu_pi.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/python/PiFinder/imu_pi.py b/python/PiFinder/imu_pi.py index c28f54d89..8736ed004 100644 --- a/python/PiFinder/imu_pi.py +++ b/python/PiFinder/imu_pi.py @@ -24,14 +24,7 @@ class Imu: def __init__(self): i2c = board.I2C() - self.sensor = None - self.calibration = 0 - try: - self.sensor = adafruit_bno055.BNO055_I2C(i2c) - except ValueError as e: - logger.error("IMU: No IMU found", exc_info=True) - return - + self.sensor = adafruit_bno055.BNO055_I2C(i2c) self.sensor.mode = adafruit_bno055.IMUPLUS_MODE # self.sensor.mode = adafruit_bno055.NDOF_MODE cfg = config.Config() @@ -59,6 +52,7 @@ def __init__(self): ) self.quat_history = [(0, 0, 0, 0)] * QUEUE_LEN self._flip_count = 0 + self.calibration = 0 self.avg_quat = (0, 0, 0, 0) self.__moving = False From bbbd7641c0b3d0e66b6dc8f959b1c83a5d6d8e73 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:20:27 +0200 Subject: [PATCH 39/61] Log that configure_accesspoint is reached --- python/PiFinder/sys_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 4c438690a..560135655 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -51,6 +51,7 @@ def configure_accesspoint() -> None: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: if not (line.startswith("ssid=") and ("ENCRYPTME" in line or "CHANGEME" in line)): + logger.debug("SYS-Network: No action needed in configure_accesspoint.") return logger.info("SYSUTILS: Configuring WIFI Access Point definition.") From ba4ed4a39ff6dfc248a268a15a4baf47b3876ba5 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:28:41 +0200 Subject: [PATCH 40/61] Use root logger. --- python/PiFinder/sys_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 560135655..767cec528 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -14,7 +14,7 @@ BACKUP_PATH = "/home/pifinder/PiFinder_data/PiFinder_backup.zip" NO_PASSWORD_DEFINED = "" -logger = logging.getLogger("SysUtils") +logger = logging.getLogger() class Network: From 9a82dd3c5f1ff76dc2f7897d74670709fcc3ed8f Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:32:34 +0200 Subject: [PATCH 41/61] Which version of sys_utils is loaded? --- python/PiFinder/main.py | 1 + python/PiFinder/sys_utils.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index 28e50c123..fd5538e01 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -273,6 +273,7 @@ def main( logger.info("PiFinder running on %s, %s, %s", os_detail, platform, arch) sys_utils = utils.get_sys_utils() + print(sys_utils.get_version()) sys_utils.Network.configure_accesspoint() # init UI Modes diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 767cec528..6bc6738ab 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -51,7 +51,7 @@ def configure_accesspoint() -> None: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: if not (line.startswith("ssid=") and ("ENCRYPTME" in line or "CHANGEME" in line)): - logger.debug("SYS-Network: No action needed in configure_accesspoint.") + logger.info("SYS-Network: No action needed in configure_accesspoint.") return logger.info("SYSUTILS: Configuring WIFI Access Point definition.") @@ -497,6 +497,8 @@ def switch_cam_imx462() -> None: logger.info("SYS: Switching cam to imx462") sh.sudo("python", "-m", "PiFinder.switch_camera", "imx462") +def get_version() -> str: + return "sysutils" if __name__ == "__main__": # This is for testing purposes only From d97867e8db50d0921235f8f57afd29e6d3f077f6 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:48:07 +0200 Subject: [PATCH 42/61] really configure the AP --- python/PiFinder/sys_utils.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 6bc6738ab..47be2277a 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -46,14 +46,15 @@ def configure_accesspoint() -> None: where 'random char' means a randomly selected character out of the set of 0-9, a-z and A-Z. """ - action_needed = False with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: - if not (line.startswith("ssid=") and ("ENCRYPTME" in line or "CHANGEME" in line)): - logger.info("SYS-Network: No action needed in configure_accesspoint.") - return - + if line.startswith("ssid="): + if ("ENCRYPTME" in line or "CHANGEME" in line)): + action_needed = True + if not action_needed: + return + logger.info("SYSUTILS: Configuring WIFI Access Point definition.") passphrase_detected = False @@ -497,9 +498,6 @@ def switch_cam_imx462() -> None: logger.info("SYS: Switching cam to imx462") sh.sudo("python", "-m", "PiFinder.switch_camera", "imx462") -def get_version() -> str: - return "sysutils" - if __name__ == "__main__": # This is for testing purposes only network = Network() From 2173dddb8a0e00ca71eb5b061b5496afc9945620 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:48:42 +0200 Subject: [PATCH 43/61] Fix typo --- python/PiFinder/sys_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 47be2277a..7091e0b2d 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -50,7 +50,7 @@ def configure_accesspoint() -> None: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: if line.startswith("ssid="): - if ("ENCRYPTME" in line or "CHANGEME" in line)): + if ("ENCRYPTME" in line or "CHANGEME" in line): action_needed = True if not action_needed: return From af792dad47cab1dfa614057032dcd0fdba1c987c Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:49:56 +0200 Subject: [PATCH 44/61] exec configure_accesspoint when unit is invoked. --- python/PiFinder/sys_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 7091e0b2d..3782b9542 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -500,5 +500,4 @@ def switch_cam_imx462() -> None: if __name__ == "__main__": # This is for testing purposes only - network = Network() - print(network.COUNTRY_CODES) \ No newline at end of file + Network.configure_accesspoint() From 62fed38bace679551771e0fa60935af666be9cc6 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:51:58 +0200 Subject: [PATCH 45/61] Fix logic in configure_accesspoint --- python/PiFinder/sys_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 3782b9542..6ff9b598c 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -68,9 +68,9 @@ def configure_accesspoint() -> None: line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True logger.warning(f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'") - elif line.startswith("ssid=") and "ENCRYPTME" in line: + if line.startswith("ssid=") and "ENCRYPTME" in line: encryption_needed = True - elif line.startswith("wpa_passphrase="): + if line.startswith("wpa_passphrase="): passphrase_detected = True new_conf.write(line) # consumed all lines, so: From 2d7cb8f39775889c75f48447a514ea0c9fb1f376 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:54:29 +0200 Subject: [PATCH 46/61] Further fix login --- python/PiFinder/sys_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 6ff9b598c..d262edffd 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -70,7 +70,9 @@ def configure_accesspoint() -> None: logger.warning(f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'") if line.startswith("ssid=") and "ENCRYPTME" in line: encryption_needed = True + logger.info("SYS-Network: Encryption needed.") if line.startswith("wpa_passphrase="): + logger.info("SYS-Network: Passphrase detected.") passphrase_detected = True new_conf.write(line) # consumed all lines, so: From 1940b7033acfec4b2f9f730bf2e8c61e234c68e7 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:55:55 +0200 Subject: [PATCH 47/61] Fix encryption needed detection --- python/PiFinder/sys_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index d262edffd..25a96037a 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -63,14 +63,14 @@ def configure_accesspoint() -> None: with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: + if line.startswith("ssid=") and "ENCRYPTME" in line: + encryption_needed = True + logger.info("SYS-Network: Encryption needed.") if line.startswith("ssid=") and "CHANGEME" in line: ap_rnd = Network._generate_random_chars(5) line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True logger.warning(f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'") - if line.startswith("ssid=") and "ENCRYPTME" in line: - encryption_needed = True - logger.info("SYS-Network: Encryption needed.") if line.startswith("wpa_passphrase="): logger.info("SYS-Network: Passphrase detected.") passphrase_detected = True From a71da2b35b3cf608a73e236afcd38d201c6a4bb0 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 21:59:35 +0200 Subject: [PATCH 48/61] Remove print --- python/PiFinder/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/PiFinder/main.py b/python/PiFinder/main.py index fd5538e01..28e50c123 100644 --- a/python/PiFinder/main.py +++ b/python/PiFinder/main.py @@ -273,7 +273,6 @@ def main( logger.info("PiFinder running on %s, %s, %s", os_detail, platform, arch) sys_utils = utils.get_sys_utils() - print(sys_utils.get_version()) sys_utils.Network.configure_accesspoint() # init UI Modes From 7e8331eb9ae357152d4a1318a39ae931bb3dbacb Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:19:20 +0200 Subject: [PATCH 49/61] Only enable encryption after password check --- python/PiFinder/sys_utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 25a96037a..f297ce424 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -246,6 +246,12 @@ def set_ap_pwd(self, ap_pwd): if ap_pwd == current_pwd: return + # Check password length + if len(ap_pwd) < 8: + raise ValueError("Password must be at least 8 characters long") + if len(ap_pwd) > 63: + raise ValueError("Password must be at most 63 characters long") + # Enable encryption, if needed if current_pwd == NO_PASSWORD_DEFINED: ap_name = self.get_ap_name() @@ -253,12 +259,6 @@ def set_ap_pwd(self, ap_pwd): Network.configure_accesspoint() self.set_ap_name(ap_name) - # Check password length - if len(ap_pwd) < 8: - raise ValueError("Password must be at least 8 characters long") - if len(ap_pwd) > 63: - raise ValueError("Password must be at most 63 characters long") - # Change password with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: From 8766845d18843f6208bfa48def5e14e314a080be Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:23:30 +0200 Subject: [PATCH 50/61] Port 8080 instead of 8181 --- python/PiFinder/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 3a2829e3a..0681a480b 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -988,12 +988,12 @@ def time_lock(time=datetime.now()): server=CherootServer, ) except (PermissionError, OSError): - logger.info("Web Interface on port 8181") + logger.info("Web Interface on port 8080") debug() run( app, host="0.0.0.0", - port=8181, + port=8080, quiet=True, debug=True, server=CherootServer, From 9a0c33c8fa6977af631d62099e82d3e8b6a7fd9c Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:24:38 +0200 Subject: [PATCH 51/61] Use SysUtils logger --- python/PiFinder/sys_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index f297ce424..921d3147e 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -14,7 +14,7 @@ BACKUP_PATH = "/home/pifinder/PiFinder_data/PiFinder_backup.zip" NO_PASSWORD_DEFINED = "" -logger = logging.getLogger() +logger = logging.getLogger("SysUtils") class Network: From 12b8121b88824eaafe7ec2067aef5b0820a42266 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:33:25 +0200 Subject: [PATCH 52/61] Add CHANGEME to AP Name --- pi_config_files/hostapd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pi_config_files/hostapd.conf b/pi_config_files/hostapd.conf index afdb52179..e33dd6dcb 100644 --- a/pi_config_files/hostapd.conf +++ b/pi_config_files/hostapd.conf @@ -1,6 +1,6 @@ country_code=US interface=wlan0 -ssid=PiFinderAP +ssid=PiFinderAP-CHANGEME hw_mode=g channel=7 macaddr_acl=0 From b081aa2d0714bf00e74aeae8589168fabfd0bf54 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:33:42 +0200 Subject: [PATCH 53/61] Add CHANGEME to AP Name --- pi_config_files/hostapd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pi_config_files/hostapd.conf b/pi_config_files/hostapd.conf index e33dd6dcb..85d42457b 100644 --- a/pi_config_files/hostapd.conf +++ b/pi_config_files/hostapd.conf @@ -1,6 +1,6 @@ country_code=US interface=wlan0 -ssid=PiFinderAP-CHANGEME +ssid=PiFinder-CHANGEME hw_mode=g channel=7 macaddr_acl=0 From c82266f079f4e3c395de0c2b5987601ad260c318 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 22:34:12 +0200 Subject: [PATCH 54/61] Use pifinder_dir for wifi_status.txt --- python/PiFinder/sys_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 921d3147e..89366007f 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -31,7 +31,7 @@ def __init__(self): @staticmethod def get_wifi_mode(): - with open("/home/pifinder/PiFinder/wifi_status.txt", "r") as wifi_f: + with open(f"{utils.pifinder_dir}/wifi_status.txt", "r") as wifi_f: return wifi_f.read() @staticmethod From 1d079aa5bf0108021108af370ebb14a0041f3642 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 23:02:37 +0200 Subject: [PATCH 55/61] When enabling encryption from web interface, do not restart hostapd in set_ap_pwd. --- python/PiFinder/sys_utils.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 89366007f..3ea212882 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -35,7 +35,7 @@ def get_wifi_mode(): return wifi_f.read() @staticmethod - def configure_accesspoint() -> None: + def configure_accesspoint(restart_hostapd = True) -> None: """Add WPA2 encryption, if not already enabled. Tasks: @@ -90,9 +90,15 @@ def configure_accesspoint() -> None: sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") # If we are enabling encryption or changed SSID, restart hostapd, if in AP mode - if (not (passphrase_detected and encryption_needed) or ssid_changed) and Network.get_wifi_mode() == "AP": - logger.warning("Network: Restarting hostapd") - sh.sudo("systemctl", "restart", "hostapd") + if (not (passphrase_detected and encryption_needed) or ssid_changed) and restart_hostapd: + Network.force_restart_hostapd() + + @staticmethod + def force_restart_hostapd() -> None: + if Network.get_wifi_mode() == "AP": + logger.warning("Network: Restarting hostapd") + sh.sudo("systemctl", "restart", "hostapd") + def populate_wifi_networks(self) -> None: wpa_supplicant_path = "/etc/wpa_supplicant/wpa_supplicant.conf" @@ -238,7 +244,8 @@ def set_ap_pwd(self, ap_pwd): If the password is the same as the current password, nothing is done. If the password is NO_PASSWORD_DEFINED we consider the Access Point as open and enable encryption as a result of calling this method. - It is the responsiblity of the caller to ensure that this method is only called when the AP needs to be encrypted. + It is the responsiblity of the caller to ensure that this method is only called when the AP needs to be encrypted or already is encrypted + The method also trusts the caller to do a restart! I.e. hostapd is not restarted, when using this method. This method throws an ValueError of the password is < 8 or > 63 characters long. """ @@ -256,7 +263,7 @@ def set_ap_pwd(self, ap_pwd): if current_pwd == NO_PASSWORD_DEFINED: ap_name = self.get_ap_name() self.set_ap_name(ap_name + "ENCRYPTME") - Network.configure_accesspoint() + Network.configure_accesspoint(False) # Do not force restart of hostapd, self.set_ap_name(ap_name) # Change password From dfdfacce2fdde09dddaaa9ead91763519b44b9d4 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 20 Apr 2025 23:49:53 +0200 Subject: [PATCH 56/61] Extract enable_encryption, fix logic to set password --- python/PiFinder/server.py | 1 + python/PiFinder/sys_utils.py | 46 +++++++++++++++++-------------- python/PiFinder/sys_utils_fake.py | 4 +++ 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 0681a480b..274addeed 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -423,6 +423,7 @@ def network_update(): ap_encrypt = request.forms.get("ap_encrypt") if ap_encrypt == "1": try: + self.network.enable_encryption() self.network.set_ap_pwd(ap_passwd) except Exception as e: err_pwd = "Invalid password: " + e.args[0] diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 3ea212882..6a9e9ed56 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -76,23 +76,38 @@ def configure_accesspoint(restart_hostapd = True) -> None: passphrase_detected = True new_conf.write(line) # consumed all lines, so: - if encryption_needed and not passphrase_detected: - # Do not change password, if passphrase was detected - logger.warning("SYS-Network: Enabling WPA2 with PSK") - # Add encrpytion directives - pwd = Network._generate_random_chars(20, "-", 5) - new_conf.write("wpa=2\n") - new_conf.write("wpa_key_mgmt=WPA-PSK\n") - new_conf.write(f"wpa_passphrase={pwd}\n") - new_conf.write("rsn_pairwise=CCMP\n") # Backup and move new file into place, restart service. logger.warning("Network: Changing configuration for hostapd") sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + + if encryption_needed and not passphrase_detected: # must be outside + Network.enable_encryption() + # If we are enabling encryption or changed SSID, restart hostapd, if in AP mode if (not (passphrase_detected and encryption_needed) or ssid_changed) and restart_hostapd: Network.force_restart_hostapd() + @staticmethod + def enable_encryption() -> None: + """Enable WPA2 encryption in hostapd.conf. + Note: Caller is responsible to restart hostapd if needed. + """ + with open("/tmp/hostapd.conf", "w") as new_conf: + with open("/etc/hostapd/hostapd.conf", "r") as conf: + for line in conf: + new_conf.write(line) + logger.warning("SYS-Network: Enabling WPA2 with PSK") + # Add encrpytion directives + pwd = Network._generate_random_chars(20, "-", 5) + new_conf.write("wpa=2\n") + new_conf.write("wpa_key_mgmt=WPA-PSK\n") + new_conf.write(f"wpa_passphrase={pwd}\n") + new_conf.write("rsn_pairwise=CCMP\n") + logger.warning("Network: Enabling encryption in hostapd") + sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck2") + sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") + @staticmethod def force_restart_hostapd() -> None: if Network.get_wifi_mode() == "AP": @@ -243,10 +258,8 @@ def set_ap_pwd(self, ap_pwd): If the password is the same as the current password, nothing is done. - If the password is NO_PASSWORD_DEFINED we consider the Access Point as open and enable encryption as a result of calling this method. - It is the responsiblity of the caller to ensure that this method is only called when the AP needs to be encrypted or already is encrypted - The method also trusts the caller to do a restart! I.e. hostapd is not restarted, when using this method. - + It is the responsiblity of the caller to ensure that this method is only called when AP enryption is already is enabled! + This method throws an ValueError of the password is < 8 or > 63 characters long. """ current_pwd = self.get_ap_pwd() @@ -259,13 +272,6 @@ def set_ap_pwd(self, ap_pwd): if len(ap_pwd) > 63: raise ValueError("Password must be at most 63 characters long") - # Enable encryption, if needed - if current_pwd == NO_PASSWORD_DEFINED: - ap_name = self.get_ap_name() - self.set_ap_name(ap_name + "ENCRYPTME") - Network.configure_accesspoint(False) # Do not force restart of hostapd, - self.set_ap_name(ap_name) - # Change password with open("/tmp/hostapd.conf", "w") as new_conf: with open("/etc/hostapd/hostapd.conf", "r") as conf: diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index 51007c93f..dd5ab9c73 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -18,6 +18,10 @@ def __init__(self): def configure_accesspoint() -> None: pass + @staticmethod + def enable_encryption() -> None: + pass + def populate_wifi_networks(self): """ Parses wpa_supplicant.conf to get current config From 3025fb963d967191325dc7ace35c0383a103cfa7 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Sun, 27 Apr 2025 21:06:47 +0200 Subject: [PATCH 57/61] Solver: Relax fov condition for solving for testing purposes. --- python/PiFinder/solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/solver.py b/python/PiFinder/solver.py index 84129ef14..f664485c9 100644 --- a/python/PiFinder/solver.py +++ b/python/PiFinder/solver.py @@ -163,7 +163,7 @@ def solver( centroids, (512, 512), fov_estimate=12.0, - fov_max_error=4.0, + fov_max_error=6.0, match_max_error=0.005, # return_matches=True, target_pixel=shared_state.solve_pixel(), From 40e3d9433d4519a56f0de58d776e329db63519d7 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 5 May 2025 08:21:53 +0200 Subject: [PATCH 58/61] I18N for wifi_password --- python/PiFinder/ui/menu_structure.py | 2 +- python/PiFinder/ui/wifi_password.py | 22 +- python/locale/de/LC_MESSAGES/messages.mo | Bin 10403 -> 10740 bytes python/locale/de/LC_MESSAGES/messages.po | 369 ++++++++++++---------- python/locale/es/LC_MESSAGES/messages.mo | Bin 445 -> 445 bytes python/locale/es/LC_MESSAGES/messages.po | 371 ++++++++++++---------- python/locale/fr/LC_MESSAGES/messages.mo | Bin 6828 -> 6828 bytes python/locale/fr/LC_MESSAGES/messages.po | 383 +++++++++++++---------- 8 files changed, 630 insertions(+), 517 deletions(-) diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 8bf266aaf..35301237e 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -54,7 +54,7 @@ def _(key: str) -> Any: "class": UIGPSStatus, }, { - "name": "Connect WiFi", + "name": _("Connect WiFi"), "class": UIWiFiPassword }, ], diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 55ad82bc3..4e7a75acb 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -38,7 +38,7 @@ def __init__(self, *args, **kwargs) -> None: self.marking_menu = MarkingMenu( left=MarkingMenuOption(), right=MarkingMenuOption(), - down=MarkingMenuOption(label=""), + down=MarkingMenuOption(label=_("mode"), menu_jump="wifi_mode"), # TRANSLATORS: Jump to WiFi mode selection from context menu ) else: # Default to QR code display @@ -47,10 +47,10 @@ def __init__(self, *args, **kwargs) -> None: self.marking_menu = MarkingMenu( left=MarkingMenuOption( - label="QR", callback=self.mm_display_qr, enabled=True + label=_("QR"), callback=self.mm_display_qr, enabled=True # TRANSLATORS: Switch to QR code WiFi display in context menu ), right=MarkingMenuOption( - label="Passwd", callback=self.mm_display_pwd, enabled=True + label=_("Passwd"), callback=self.mm_display_pwd, enabled=True # TRANSLATORS: Switch to WiFi plain password display in context menu ), down=MarkingMenuOption(label="mode", menu_jump="wifi_mode"), ) @@ -112,12 +112,12 @@ def _generate_wifi_qrcode( self, ssid: str, password: str, security_type: str ) -> qrcode.image.base.BaseImage: wifi_data = f"WIFI:S:{ssid};T:{security_type};P:{password};H:false;" - logger.debug(f"WIFI Data: '{wifi_data}'") + # logger.debug(f"WIFI Data: '{wifi_data}'") # Do NOT log password qr = qrcode.QRCode( version=1, # 21x21 matrix error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, # Size of a box of the + box_size=10, # Size of a box of the QR code (scaling it down later gives better results) border=1, ) qr.add_data(wifi_data) @@ -150,14 +150,14 @@ def update(self, force=False): def _display_client_ssid(self, draw_pos: int) -> int: self.draw.text( (0, draw_pos), - "Client Mode!", + _("Client mode!"), font=self.fonts.base.font, fill=self.colors.get(255), ) draw_pos += 20 self.draw.text( (0, draw_pos), - "Connected to:", + _("Connected to:"), font=self.fonts.base.font, fill=self.colors.get(255), ) @@ -177,7 +177,7 @@ def _display_wifi_qr(self, draw_pos: int) -> int: # Mode self.draw.text( (0, draw_pos), - f"Client mode!", + _("Client mode!"), font=self.fonts.base.font, fill=self.colors.get(255), ) @@ -206,7 +206,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: # Mode self.draw.text( (0, draw_pos), - f"Note: {self.ap_mode} mode!", + _("Note: {wifi_mode} mode!").format(wifi_mode=self.ap_mode), font=self.fonts.base.font, fill=self.colors.get(255), ) @@ -220,7 +220,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: # Password self.draw.text( (0, draw_pos), - "Password:", + _("Password:"), font=self.fonts.base.font, fill=self.colors.get(128), ) @@ -270,7 +270,7 @@ def _display_plain_pwd(self, draw_pos: int) -> None: def _show_ssid(self, draw_pos, truncate=False): self.draw.text( (0, draw_pos), - "SSID:", + _("SSID:"), font=self.fonts.base.font, fill=self.colors.get(128), ) diff --git a/python/locale/de/LC_MESSAGES/messages.mo b/python/locale/de/LC_MESSAGES/messages.mo index 20202bfff2cfc84d60ba55e3d54c81033264f455..5587ab5a7bce9ed1ed9cccf4a68fa7c625d06873 100644 GIT binary patch delta 4619 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\n" "Language: es\n" @@ -22,27 +22,27 @@ msgstr "" msgid "No Image" msgstr "" -#: PiFinder/obj_types.py:7 PiFinder/ui/menu_structure.py:362 +#: PiFinder/obj_types.py:7 PiFinder/ui/menu_structure.py:367 msgid "Galaxy" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:8 PiFinder/ui/menu_structure.py:366 +#: PiFinder/obj_types.py:8 PiFinder/ui/menu_structure.py:371 msgid "Open Cluster" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:9 PiFinder/ui/menu_structure.py:374 +#: PiFinder/obj_types.py:9 PiFinder/ui/menu_structure.py:379 msgid "Globular" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:10 PiFinder/ui/menu_structure.py:378 +#: PiFinder/obj_types.py:10 PiFinder/ui/menu_structure.py:383 msgid "Nebula" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:11 PiFinder/ui/menu_structure.py:386 +#: PiFinder/obj_types.py:11 PiFinder/ui/menu_structure.py:391 msgid "Dark Nebula" msgstr "" @@ -57,12 +57,12 @@ msgid "Cluster + Neb" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:14 PiFinder/ui/menu_structure.py:406 +#: PiFinder/obj_types.py:14 PiFinder/ui/menu_structure.py:411 msgid "Asterism" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:15 PiFinder/ui/menu_structure.py:402 +#: PiFinder/obj_types.py:15 PiFinder/ui/menu_structure.py:407 msgid "Knot" msgstr "" @@ -77,7 +77,7 @@ msgid "Double star" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:18 PiFinder/ui/menu_structure.py:390 +#: PiFinder/obj_types.py:18 PiFinder/ui/menu_structure.py:395 msgid "Star" msgstr "" @@ -87,12 +87,12 @@ msgid "Unkn" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:20 PiFinder/ui/menu_structure.py:410 +#: PiFinder/obj_types.py:20 PiFinder/ui/menu_structure.py:415 msgid "Planet" msgstr "" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:21 PiFinder/ui/menu_structure.py:414 +#: PiFinder/obj_types.py:21 PiFinder/ui/menu_structure.py:419 msgid "Comet" msgstr "" @@ -113,11 +113,11 @@ msgstr "" msgid "No Solve Yet" msgstr "" -#: PiFinder/ui/align.py:397 PiFinder/ui/object_details.py:461 +#: PiFinder/ui/align.py:397 PiFinder/ui/object_details.py:462 msgid "Aligning..." msgstr "" -#: PiFinder/ui/align.py:405 PiFinder/ui/object_details.py:469 +#: PiFinder/ui/align.py:405 PiFinder/ui/object_details.py:470 msgid "Aligned!" msgstr "" @@ -129,7 +129,7 @@ msgstr "" msgid "Filters Reset" msgstr "" -#: PiFinder/ui/callbacks.py:63 PiFinder/ui/menu_structure.py:949 +#: PiFinder/ui/callbacks.py:63 PiFinder/ui/menu_structure.py:980 msgid "Test Mode" msgstr "" @@ -158,7 +158,7 @@ msgstr "" msgid "Time: {time}" msgstr "" -#: PiFinder/ui/chart.py:40 PiFinder/ui/menu_structure.py:526 +#: PiFinder/ui/chart.py:40 PiFinder/ui/menu_structure.py:531 msgid "Settings" msgstr "" @@ -266,8 +266,8 @@ msgstr "" msgid "Lock Type:" msgstr "" -#: PiFinder/ui/gpsstatus.py:213 PiFinder/ui/menu_structure.py:426 -#: PiFinder/ui/menu_structure.py:458 +#: PiFinder/ui/gpsstatus.py:213 PiFinder/ui/menu_structure.py:431 +#: PiFinder/ui/menu_structure.py:463 msgid "None" msgstr "" @@ -382,481 +382,485 @@ msgstr "" msgid "Telescope" msgstr "" -#: PiFinder/ui/menu_structure.py:23 +#: PiFinder/ui/menu_structure.py:24 msgid "Language: de" msgstr "" -#: PiFinder/ui/menu_structure.py:24 +#: PiFinder/ui/menu_structure.py:25 msgid "Language: en" msgstr "" -#: PiFinder/ui/menu_structure.py:25 +#: PiFinder/ui/menu_structure.py:26 msgid "Language: es" msgstr "" -#: PiFinder/ui/menu_structure.py:26 +#: PiFinder/ui/menu_structure.py:27 msgid "Language: fr" msgstr "" -#: PiFinder/ui/menu_structure.py:37 +#: PiFinder/ui/menu_structure.py:38 msgid "Start" msgstr "" -#: PiFinder/ui/menu_structure.py:42 +#: PiFinder/ui/menu_structure.py:43 msgid "Focus" msgstr "" -#: PiFinder/ui/menu_structure.py:46 +#: PiFinder/ui/menu_structure.py:47 msgid "Align" msgstr "" -#: PiFinder/ui/menu_structure.py:52 PiFinder/ui/menu_structure.py:932 +#: PiFinder/ui/menu_structure.py:53 PiFinder/ui/menu_structure.py:963 msgid "GPS Status" msgstr "" -#: PiFinder/ui/menu_structure.py:58 +#: PiFinder/ui/menu_structure.py:57 +msgid "Connect WiFi" +msgstr "" + +#: PiFinder/ui/menu_structure.py:63 msgid "Chart" msgstr "" -#: PiFinder/ui/menu_structure.py:64 +#: PiFinder/ui/menu_structure.py:69 msgid "Objects" msgstr "" -#: PiFinder/ui/menu_structure.py:69 +#: PiFinder/ui/menu_structure.py:74 msgid "All Filtered" msgstr "" -#: PiFinder/ui/menu_structure.py:74 +#: PiFinder/ui/menu_structure.py:79 msgid "By Catalog" msgstr "" -#: PiFinder/ui/menu_structure.py:79 PiFinder/ui/menu_structure.py:254 +#: PiFinder/ui/menu_structure.py:84 PiFinder/ui/menu_structure.py:259 msgid "Planets" msgstr "" -#: PiFinder/ui/menu_structure.py:85 PiFinder/ui/menu_structure.py:156 -#: PiFinder/ui/menu_structure.py:258 PiFinder/ui/menu_structure.py:308 +#: PiFinder/ui/menu_structure.py:90 PiFinder/ui/menu_structure.py:161 +#: PiFinder/ui/menu_structure.py:263 PiFinder/ui/menu_structure.py:313 msgid "NGC" msgstr "" -#: PiFinder/ui/menu_structure.py:91 PiFinder/ui/menu_structure.py:150 -#: PiFinder/ui/menu_structure.py:262 PiFinder/ui/menu_structure.py:304 +#: PiFinder/ui/menu_structure.py:96 PiFinder/ui/menu_structure.py:155 +#: PiFinder/ui/menu_structure.py:267 PiFinder/ui/menu_structure.py:309 msgid "Messier" msgstr "" -#: PiFinder/ui/menu_structure.py:97 PiFinder/ui/menu_structure.py:266 +#: PiFinder/ui/menu_structure.py:102 PiFinder/ui/menu_structure.py:271 msgid "DSO..." msgstr "" -#: PiFinder/ui/menu_structure.py:102 PiFinder/ui/menu_structure.py:272 +#: PiFinder/ui/menu_structure.py:107 PiFinder/ui/menu_structure.py:277 msgid "Abell Pn" msgstr "" -#: PiFinder/ui/menu_structure.py:108 PiFinder/ui/menu_structure.py:276 +#: PiFinder/ui/menu_structure.py:113 PiFinder/ui/menu_structure.py:281 msgid "Arp Galaxies" msgstr "" -#: PiFinder/ui/menu_structure.py:114 PiFinder/ui/menu_structure.py:280 +#: PiFinder/ui/menu_structure.py:119 PiFinder/ui/menu_structure.py:285 msgid "Barnard" msgstr "" -#: PiFinder/ui/menu_structure.py:120 PiFinder/ui/menu_structure.py:284 +#: PiFinder/ui/menu_structure.py:125 PiFinder/ui/menu_structure.py:289 msgid "Caldwell" msgstr "" -#: PiFinder/ui/menu_structure.py:126 PiFinder/ui/menu_structure.py:288 +#: PiFinder/ui/menu_structure.py:131 PiFinder/ui/menu_structure.py:293 msgid "Collinder" msgstr "" -#: PiFinder/ui/menu_structure.py:132 PiFinder/ui/menu_structure.py:292 +#: PiFinder/ui/menu_structure.py:137 PiFinder/ui/menu_structure.py:297 msgid "E.G. Globs" msgstr "" -#: PiFinder/ui/menu_structure.py:138 PiFinder/ui/menu_structure.py:296 +#: PiFinder/ui/menu_structure.py:143 PiFinder/ui/menu_structure.py:301 msgid "Herschel 400" msgstr "" -#: PiFinder/ui/menu_structure.py:144 PiFinder/ui/menu_structure.py:300 +#: PiFinder/ui/menu_structure.py:149 PiFinder/ui/menu_structure.py:305 msgid "IC" msgstr "" -#: PiFinder/ui/menu_structure.py:162 PiFinder/ui/menu_structure.py:312 +#: PiFinder/ui/menu_structure.py:167 PiFinder/ui/menu_structure.py:317 msgid "Sharpless" msgstr "" -#: PiFinder/ui/menu_structure.py:168 PiFinder/ui/menu_structure.py:316 +#: PiFinder/ui/menu_structure.py:173 PiFinder/ui/menu_structure.py:321 msgid "TAAS 200" msgstr "" -#: PiFinder/ui/menu_structure.py:176 PiFinder/ui/menu_structure.py:322 +#: PiFinder/ui/menu_structure.py:181 PiFinder/ui/menu_structure.py:327 msgid "Stars..." msgstr "" -#: PiFinder/ui/menu_structure.py:181 PiFinder/ui/menu_structure.py:328 +#: PiFinder/ui/menu_structure.py:186 PiFinder/ui/menu_structure.py:333 msgid "Bright Named" msgstr "" -#: PiFinder/ui/menu_structure.py:187 PiFinder/ui/menu_structure.py:332 +#: PiFinder/ui/menu_structure.py:192 PiFinder/ui/menu_structure.py:337 msgid "SAC Doubles" msgstr "" -#: PiFinder/ui/menu_structure.py:193 PiFinder/ui/menu_structure.py:336 +#: PiFinder/ui/menu_structure.py:198 PiFinder/ui/menu_structure.py:341 msgid "SAC Asterisms" msgstr "" -#: PiFinder/ui/menu_structure.py:199 PiFinder/ui/menu_structure.py:340 +#: PiFinder/ui/menu_structure.py:204 PiFinder/ui/menu_structure.py:345 msgid "SAC Red Stars" msgstr "" -#: PiFinder/ui/menu_structure.py:205 PiFinder/ui/menu_structure.py:344 +#: PiFinder/ui/menu_structure.py:210 PiFinder/ui/menu_structure.py:349 msgid "RASC Doubles" msgstr "" -#: PiFinder/ui/menu_structure.py:211 PiFinder/ui/menu_structure.py:348 +#: PiFinder/ui/menu_structure.py:216 PiFinder/ui/menu_structure.py:353 msgid "TLK 90 Variables" msgstr "" -#: PiFinder/ui/menu_structure.py:221 +#: PiFinder/ui/menu_structure.py:226 msgid "Recent" msgstr "" -#: PiFinder/ui/menu_structure.py:227 +#: PiFinder/ui/menu_structure.py:232 msgid "Name Search" msgstr "" -#: PiFinder/ui/menu_structure.py:233 PiFinder/ui/object_list.py:136 +#: PiFinder/ui/menu_structure.py:238 PiFinder/ui/object_list.py:136 msgid "Filter" msgstr "" -#: PiFinder/ui/menu_structure.py:239 +#: PiFinder/ui/menu_structure.py:244 msgid "Reset All" msgstr "" -#: PiFinder/ui/menu_structure.py:243 PiFinder/ui/menu_structure.py:973 +#: PiFinder/ui/menu_structure.py:248 PiFinder/ui/menu_structure.py:1004 msgid "Confirm" msgstr "" -#: PiFinder/ui/menu_structure.py:244 PiFinder/ui/menu_structure.py:976 +#: PiFinder/ui/menu_structure.py:249 PiFinder/ui/menu_structure.py:1007 #: PiFinder/ui/software.py:204 msgid "Cancel" msgstr "" -#: PiFinder/ui/menu_structure.py:248 +#: PiFinder/ui/menu_structure.py:253 msgid "Catalogs" msgstr "" -#: PiFinder/ui/menu_structure.py:356 +#: PiFinder/ui/menu_structure.py:361 msgid "Type" msgstr "" -#: PiFinder/ui/menu_structure.py:370 +#: PiFinder/ui/menu_structure.py:375 msgid "Cluster/Neb" msgstr "" -#: PiFinder/ui/menu_structure.py:382 +#: PiFinder/ui/menu_structure.py:387 msgid "P. Nebula" msgstr "" -#: PiFinder/ui/menu_structure.py:394 +#: PiFinder/ui/menu_structure.py:399 msgid "Double Str" msgstr "" -#: PiFinder/ui/menu_structure.py:398 +#: PiFinder/ui/menu_structure.py:403 msgid "Triple Str" msgstr "" -#: PiFinder/ui/menu_structure.py:420 +#: PiFinder/ui/menu_structure.py:425 msgid "Altitude" msgstr "" -#: PiFinder/ui/menu_structure.py:452 +#: PiFinder/ui/menu_structure.py:457 msgid "Magnitude" msgstr "" -#: PiFinder/ui/menu_structure.py:504 PiFinder/ui/menu_structure.py:514 +#: PiFinder/ui/menu_structure.py:509 PiFinder/ui/menu_structure.py:519 msgid "Observed" msgstr "" -#: PiFinder/ui/menu_structure.py:510 +#: PiFinder/ui/menu_structure.py:515 msgid "Any" msgstr "" -#: PiFinder/ui/menu_structure.py:518 +#: PiFinder/ui/menu_structure.py:523 msgid "Not Observed" msgstr "" -#: PiFinder/ui/menu_structure.py:531 +#: PiFinder/ui/menu_structure.py:536 msgid "User Pref..." msgstr "" -#: PiFinder/ui/menu_structure.py:536 +#: PiFinder/ui/menu_structure.py:541 msgid "Key Bright" msgstr "" -#: PiFinder/ui/menu_structure.py:576 +#: PiFinder/ui/menu_structure.py:581 msgid "Sleep Time" msgstr "" -#: PiFinder/ui/menu_structure.py:582 PiFinder/ui/menu_structure.py:614 -#: PiFinder/ui/menu_structure.py:638 PiFinder/ui/menu_structure.py:687 -#: PiFinder/ui/menu_structure.py:711 PiFinder/ui/menu_structure.py:735 -#: PiFinder/ui/menu_structure.py:759 PiFinder/ui/preview.py:62 +#: PiFinder/ui/menu_structure.py:587 PiFinder/ui/menu_structure.py:619 +#: PiFinder/ui/menu_structure.py:643 PiFinder/ui/menu_structure.py:717 +#: PiFinder/ui/menu_structure.py:741 PiFinder/ui/menu_structure.py:765 +#: PiFinder/ui/menu_structure.py:789 PiFinder/ui/preview.py:62 #: PiFinder/ui/preview.py:79 msgid "Off" msgstr "" -#: PiFinder/ui/menu_structure.py:608 +#: PiFinder/ui/menu_structure.py:613 msgid "Menu Anim" msgstr "" -#: PiFinder/ui/menu_structure.py:618 PiFinder/ui/menu_structure.py:642 +#: PiFinder/ui/menu_structure.py:623 PiFinder/ui/menu_structure.py:647 msgid "Fast" msgstr "" -#: PiFinder/ui/menu_structure.py:622 PiFinder/ui/menu_structure.py:646 -#: PiFinder/ui/menu_structure.py:695 PiFinder/ui/menu_structure.py:719 -#: PiFinder/ui/menu_structure.py:743 PiFinder/ui/preview.py:67 +#: PiFinder/ui/menu_structure.py:627 PiFinder/ui/menu_structure.py:651 +#: PiFinder/ui/menu_structure.py:725 PiFinder/ui/menu_structure.py:749 +#: PiFinder/ui/menu_structure.py:773 PiFinder/ui/preview.py:67 msgid "Medium" msgstr "" -#: PiFinder/ui/menu_structure.py:626 PiFinder/ui/menu_structure.py:650 +#: PiFinder/ui/menu_structure.py:631 PiFinder/ui/menu_structure.py:655 msgid "Slow" msgstr "" -#: PiFinder/ui/menu_structure.py:632 +#: PiFinder/ui/menu_structure.py:637 msgid "Scroll Speed" msgstr "" -#: PiFinder/ui/menu_structure.py:656 +#: PiFinder/ui/menu_structure.py:661 msgid "Az Arrows" msgstr "" -#: PiFinder/ui/menu_structure.py:663 +#: PiFinder/ui/menu_structure.py:668 msgid "Default" msgstr "" -#: PiFinder/ui/menu_structure.py:667 +#: PiFinder/ui/menu_structure.py:672 msgid "Reverse" msgstr "" -#: PiFinder/ui/menu_structure.py:675 +#: PiFinder/ui/menu_structure.py:678 +msgid "Language" +msgstr "" + +#: PiFinder/ui/menu_structure.py:685 +msgid "English" +msgstr "" + +#: PiFinder/ui/menu_structure.py:689 +msgid "German" +msgstr "" + +#: PiFinder/ui/menu_structure.py:693 +msgid "French" +msgstr "" + +#: PiFinder/ui/menu_structure.py:697 +msgid "Spanish" +msgstr "" + +#: PiFinder/ui/menu_structure.py:705 msgid "Chart..." msgstr "" -#: PiFinder/ui/menu_structure.py:681 +#: PiFinder/ui/menu_structure.py:711 msgid "Reticle" msgstr "" -#: PiFinder/ui/menu_structure.py:691 PiFinder/ui/menu_structure.py:715 -#: PiFinder/ui/menu_structure.py:739 PiFinder/ui/preview.py:72 +#: PiFinder/ui/menu_structure.py:721 PiFinder/ui/menu_structure.py:745 +#: PiFinder/ui/menu_structure.py:769 PiFinder/ui/preview.py:72 msgid "Low" msgstr "" -#: PiFinder/ui/menu_structure.py:699 PiFinder/ui/menu_structure.py:723 -#: PiFinder/ui/menu_structure.py:747 PiFinder/ui/preview.py:64 +#: PiFinder/ui/menu_structure.py:729 PiFinder/ui/menu_structure.py:753 +#: PiFinder/ui/menu_structure.py:777 PiFinder/ui/preview.py:64 msgid "High" msgstr "" -#: PiFinder/ui/menu_structure.py:705 +#: PiFinder/ui/menu_structure.py:735 msgid "Constellation" msgstr "" -#: PiFinder/ui/menu_structure.py:729 +#: PiFinder/ui/menu_structure.py:759 msgid "DSO Display" msgstr "" -#: PiFinder/ui/menu_structure.py:753 +#: PiFinder/ui/menu_structure.py:783 msgid "RA/DEC Disp." msgstr "" -#: PiFinder/ui/menu_structure.py:763 +#: PiFinder/ui/menu_structure.py:793 msgid "HH:MM" msgstr "" -#: PiFinder/ui/menu_structure.py:767 +#: PiFinder/ui/menu_structure.py:797 msgid "Degrees" msgstr "" -#: PiFinder/ui/menu_structure.py:775 +#: PiFinder/ui/menu_structure.py:805 msgid "Camera Exp" msgstr "" -#: PiFinder/ui/menu_structure.py:783 +#: PiFinder/ui/menu_structure.py:813 msgid "0.025s" msgstr "" -#: PiFinder/ui/menu_structure.py:787 +#: PiFinder/ui/menu_structure.py:817 msgid "0.05s" msgstr "" -#: PiFinder/ui/menu_structure.py:791 +#: PiFinder/ui/menu_structure.py:821 msgid "0.1s" msgstr "" -#: PiFinder/ui/menu_structure.py:795 +#: PiFinder/ui/menu_structure.py:825 msgid "0.2s" msgstr "" -#: PiFinder/ui/menu_structure.py:799 +#: PiFinder/ui/menu_structure.py:829 msgid "0.4s" msgstr "" -#: PiFinder/ui/menu_structure.py:803 +#: PiFinder/ui/menu_structure.py:833 msgid "0.8s" msgstr "" -#: PiFinder/ui/menu_structure.py:807 +#: PiFinder/ui/menu_structure.py:837 msgid "1s" msgstr "" -#: PiFinder/ui/menu_structure.py:813 +#: PiFinder/ui/menu_structure.py:843 msgid "WiFi Mode" msgstr "" -#: PiFinder/ui/menu_structure.py:819 +#: PiFinder/ui/menu_structure.py:850 msgid "Client Mode" msgstr "" -#: PiFinder/ui/menu_structure.py:824 +#: PiFinder/ui/menu_structure.py:855 msgid "AP Mode" msgstr "" -#: PiFinder/ui/menu_structure.py:831 +#: PiFinder/ui/menu_structure.py:862 msgid "PiFinder Type" msgstr "" -#: PiFinder/ui/menu_structure.py:838 +#: PiFinder/ui/menu_structure.py:869 msgid "Left" msgstr "" -#: PiFinder/ui/menu_structure.py:842 +#: PiFinder/ui/menu_structure.py:873 msgid "Right" msgstr "" -#: PiFinder/ui/menu_structure.py:846 +#: PiFinder/ui/menu_structure.py:877 msgid "Straight" msgstr "" -#: PiFinder/ui/menu_structure.py:850 +#: PiFinder/ui/menu_structure.py:881 msgid "Flat v3" msgstr "" -#: PiFinder/ui/menu_structure.py:854 +#: PiFinder/ui/menu_structure.py:885 msgid "Flat v2" msgstr "" -#: PiFinder/ui/menu_structure.py:860 +#: PiFinder/ui/menu_structure.py:891 msgid "Mount Type" msgstr "" -#: PiFinder/ui/menu_structure.py:867 +#: PiFinder/ui/menu_structure.py:898 msgid "Alt/Az" msgstr "" -#: PiFinder/ui/menu_structure.py:871 +#: PiFinder/ui/menu_structure.py:902 msgid "Equitorial" msgstr "" -#: PiFinder/ui/menu_structure.py:877 +#: PiFinder/ui/menu_structure.py:908 msgid "Camera Type" msgstr "" -#: PiFinder/ui/menu_structure.py:883 +#: PiFinder/ui/menu_structure.py:914 msgid "v2 - imx477" msgstr "" -#: PiFinder/ui/menu_structure.py:888 +#: PiFinder/ui/menu_structure.py:919 msgid "v3 - imx296" msgstr "" -#: PiFinder/ui/menu_structure.py:893 +#: PiFinder/ui/menu_structure.py:924 msgid "v3 - imx462" msgstr "" -#: PiFinder/ui/menu_structure.py:900 +#: PiFinder/ui/menu_structure.py:931 msgid "GPS Type" msgstr "" -#: PiFinder/ui/menu_structure.py:908 +#: PiFinder/ui/menu_structure.py:939 msgid "UBlox" msgstr "" -#: PiFinder/ui/menu_structure.py:912 +#: PiFinder/ui/menu_structure.py:943 msgid "GPSD (generic)" msgstr "" -#: PiFinder/ui/menu_structure.py:920 +#: PiFinder/ui/menu_structure.py:951 msgid "Tools" msgstr "" -#: PiFinder/ui/menu_structure.py:924 +#: PiFinder/ui/menu_structure.py:955 msgid "Status" msgstr "" -#: PiFinder/ui/menu_structure.py:925 +#: PiFinder/ui/menu_structure.py:956 msgid "Equipment" msgstr "" -#: PiFinder/ui/menu_structure.py:927 +#: PiFinder/ui/menu_structure.py:958 msgid "Place & Time" msgstr "" -#: PiFinder/ui/menu_structure.py:936 +#: PiFinder/ui/menu_structure.py:967 msgid "Set Location" msgstr "" -#: PiFinder/ui/menu_structure.py:940 +#: PiFinder/ui/menu_structure.py:971 msgid "Set Time" msgstr "" -#: PiFinder/ui/menu_structure.py:944 +#: PiFinder/ui/menu_structure.py:975 msgid "Reset" msgstr "" -#: PiFinder/ui/menu_structure.py:947 +#: PiFinder/ui/menu_structure.py:978 msgid "Console" msgstr "" -#: PiFinder/ui/menu_structure.py:948 +#: PiFinder/ui/menu_structure.py:979 msgid "Software Upd" msgstr "" -#: PiFinder/ui/menu_structure.py:951 +#: PiFinder/ui/menu_structure.py:982 msgid "Power" msgstr "" -#: PiFinder/ui/menu_structure.py:957 +#: PiFinder/ui/menu_structure.py:988 msgid "Shutdown" msgstr "" -#: PiFinder/ui/menu_structure.py:967 +#: PiFinder/ui/menu_structure.py:998 msgid "Restart" msgstr "" -#: PiFinder/ui/menu_structure.py:982 +#: PiFinder/ui/menu_structure.py:1013 msgid "Experimental" msgstr "" -#: PiFinder/ui/menu_structure.py:987 -msgid "Language" -msgstr "" - -#: PiFinder/ui/menu_structure.py:994 -msgid "English" -msgstr "" - -#: PiFinder/ui/menu_structure.py:998 -msgid "German" -msgstr "" - -#: PiFinder/ui/menu_structure.py:1002 -msgid "French" -msgstr "" - -#: PiFinder/ui/menu_structure.py:1006 -msgid "Spanish" -msgstr "" - #: PiFinder/ui/object_details.py:61 PiFinder/ui/object_details.py:66 msgid "ALIGN" msgstr "" @@ -878,39 +882,39 @@ msgstr "" msgid "Sz:{size}" msgstr "" -#: PiFinder/ui/object_details.py:207 +#: PiFinder/ui/object_details.py:208 msgid "  Not Logged" msgstr "" -#: PiFinder/ui/object_details.py:209 +#: PiFinder/ui/object_details.py:210 msgid "  {logs} Logs" msgstr "" -#: PiFinder/ui/object_details.py:244 +#: PiFinder/ui/object_details.py:245 msgid "No solve" msgstr "" -#: PiFinder/ui/object_details.py:250 +#: PiFinder/ui/object_details.py:251 msgid "yet{elipsis}" msgstr "" -#: PiFinder/ui/object_details.py:264 +#: PiFinder/ui/object_details.py:265 msgid "Searching" msgstr "" -#: PiFinder/ui/object_details.py:270 +#: PiFinder/ui/object_details.py:271 msgid "for GPS{elipsis}" msgstr "" -#: PiFinder/ui/object_details.py:284 +#: PiFinder/ui/object_details.py:285 msgid "Calculating" msgstr "" -#: PiFinder/ui/object_details.py:471 +#: PiFinder/ui/object_details.py:472 msgid "Too Far" msgstr "" -#: PiFinder/ui/object_details.py:496 +#: PiFinder/ui/object_details.py:497 msgid "LOG" msgstr "" @@ -1089,6 +1093,38 @@ msgstr "" msgid "󰍴 Delete/Previous" msgstr "" +#: PiFinder/ui/wifi_password.py:41 +msgid "mode" +msgstr "" + +#: PiFinder/ui/wifi_password.py:50 +msgid "QR" +msgstr "" + +#: PiFinder/ui/wifi_password.py:53 +msgid "Passwd" +msgstr "" + +#: PiFinder/ui/wifi_password.py:153 PiFinder/ui/wifi_password.py:180 +msgid "Client mode!" +msgstr "" + +#: PiFinder/ui/wifi_password.py:160 +msgid "Connected to:" +msgstr "" + +#: PiFinder/ui/wifi_password.py:209 +msgid "Note: {wifi_mode} mode!" +msgstr "" + +#: PiFinder/ui/wifi_password.py:223 +msgid "Password:" +msgstr "" + +#: PiFinder/ui/wifi_password.py:273 +msgid "SSID:" +msgstr "" + #~ msgid "Language: englisch" #~ msgstr "" @@ -1470,3 +1506,6 @@ msgstr "" #~ msgid "HELP" #~ msgstr "" +#~ msgid "Client Mode!" +#~ msgstr "" + diff --git a/python/locale/fr/LC_MESSAGES/messages.mo b/python/locale/fr/LC_MESSAGES/messages.mo index 4e1446049593f917b406b1f4c7b44ab10e5a8bf9..9b4868f61f12837e1a1f4a9af370eb29916b8d9c 100644 GIT binary patch delta 20 bcmZ2uy2f-vmms^Tf`NsVk>Tcvg3_D-N2~?t delta 20 bcmZ2uy2f-vmms@|f}yFEvH9kSg3_D-N4f>< diff --git a/python/locale/fr/LC_MESSAGES/messages.po b/python/locale/fr/LC_MESSAGES/messages.po index 5d1c63b6e..e4bc61a94 100644 --- a/python/locale/fr/LC_MESSAGES/messages.po +++ b/python/locale/fr/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2025-05-04 15:37+0200\n" +"POT-Creation-Date: 2025-05-05 08:21+0200\n" "PO-Revision-Date: 2025-01-12 18:13+0100\n" "Last-Translator: xxxxxx \n" "Language: fr_FR\n" @@ -21,27 +21,27 @@ msgstr "" msgid "No Image" msgstr "" -#: PiFinder/obj_types.py:7 PiFinder/ui/menu_structure.py:362 +#: PiFinder/obj_types.py:7 PiFinder/ui/menu_structure.py:367 msgid "Galaxy" msgstr "Galaxie" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:8 PiFinder/ui/menu_structure.py:366 +#: PiFinder/obj_types.py:8 PiFinder/ui/menu_structure.py:371 msgid "Open Cluster" msgstr "Amas Ouvert" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:9 PiFinder/ui/menu_structure.py:374 +#: PiFinder/obj_types.py:9 PiFinder/ui/menu_structure.py:379 msgid "Globular" msgstr "Amas Globulaire" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:10 PiFinder/ui/menu_structure.py:378 +#: PiFinder/obj_types.py:10 PiFinder/ui/menu_structure.py:383 msgid "Nebula" msgstr "Nébuleuse" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:11 PiFinder/ui/menu_structure.py:386 +#: PiFinder/obj_types.py:11 PiFinder/ui/menu_structure.py:391 #, fuzzy msgid "Dark Nebula" msgstr "Nébuleuse obscure" @@ -59,12 +59,12 @@ msgid "Cluster + Neb" msgstr "Amas Ouvert" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:14 PiFinder/ui/menu_structure.py:406 +#: PiFinder/obj_types.py:14 PiFinder/ui/menu_structure.py:411 msgid "Asterism" msgstr "Asterisme" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:15 PiFinder/ui/menu_structure.py:402 +#: PiFinder/obj_types.py:15 PiFinder/ui/menu_structure.py:407 msgid "Knot" msgstr "Knot" @@ -81,7 +81,7 @@ msgid "Double star" msgstr "Etoile Double" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:18 PiFinder/ui/menu_structure.py:390 +#: PiFinder/obj_types.py:18 PiFinder/ui/menu_structure.py:395 #, fuzzy msgid "Star" msgstr "Etoile" @@ -92,12 +92,12 @@ msgid "Unkn" msgstr "Inconnu" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:20 PiFinder/ui/menu_structure.py:410 +#: PiFinder/obj_types.py:20 PiFinder/ui/menu_structure.py:415 msgid "Planet" msgstr "Planète" #. TRANSLATORS: Object type -#: PiFinder/obj_types.py:21 PiFinder/ui/menu_structure.py:414 +#: PiFinder/obj_types.py:21 PiFinder/ui/menu_structure.py:419 #, fuzzy msgid "Comet" msgstr "Comètes" @@ -119,12 +119,12 @@ msgstr "Pas d'astrométrie" msgid "No Solve Yet" msgstr "Pas d'astrometrie" -#: PiFinder/ui/align.py:397 PiFinder/ui/object_details.py:461 +#: PiFinder/ui/align.py:397 PiFinder/ui/object_details.py:462 #, fuzzy msgid "Aligning..." msgstr "Alignement..." -#: PiFinder/ui/align.py:405 PiFinder/ui/object_details.py:469 +#: PiFinder/ui/align.py:405 PiFinder/ui/object_details.py:470 #, fuzzy msgid "Aligned!" msgstr "Alignement" @@ -137,7 +137,7 @@ msgstr "Erreur d'alignement" msgid "Filters Reset" msgstr "RàZ filtres" -#: PiFinder/ui/callbacks.py:63 PiFinder/ui/menu_structure.py:949 +#: PiFinder/ui/callbacks.py:63 PiFinder/ui/menu_structure.py:980 #, fuzzy msgid "Test Mode" msgstr "Mode client" @@ -167,7 +167,7 @@ msgstr "Wifi vers Client" msgid "Time: {time}" msgstr "Temps: {time}" -#: PiFinder/ui/chart.py:40 PiFinder/ui/menu_structure.py:526 +#: PiFinder/ui/chart.py:40 PiFinder/ui/menu_structure.py:531 msgid "Settings" msgstr "Réglages" @@ -281,8 +281,8 @@ msgstr "" msgid "Lock Type:" msgstr "Type de Monture" -#: PiFinder/ui/gpsstatus.py:213 PiFinder/ui/menu_structure.py:426 -#: PiFinder/ui/menu_structure.py:458 +#: PiFinder/ui/gpsstatus.py:213 PiFinder/ui/menu_structure.py:431 +#: PiFinder/ui/menu_structure.py:463 msgid "None" msgstr "Aucun" @@ -406,503 +406,507 @@ msgstr "Oculaire" msgid "Telescope" msgstr "Telescope..." -#: PiFinder/ui/menu_structure.py:23 +#: PiFinder/ui/menu_structure.py:24 #, fuzzy msgid "Language: de" msgstr "Langage: Allemand" -#: PiFinder/ui/menu_structure.py:24 +#: PiFinder/ui/menu_structure.py:25 #, fuzzy msgid "Language: en" msgstr "Langage: Anglais" -#: PiFinder/ui/menu_structure.py:25 +#: PiFinder/ui/menu_structure.py:26 #, fuzzy msgid "Language: es" msgstr "Langage: Espagnol" -#: PiFinder/ui/menu_structure.py:26 +#: PiFinder/ui/menu_structure.py:27 #, fuzzy msgid "Language: fr" msgstr "Langage: Français" -#: PiFinder/ui/menu_structure.py:37 +#: PiFinder/ui/menu_structure.py:38 #, fuzzy msgid "Start" msgstr "Etoile" -#: PiFinder/ui/menu_structure.py:42 +#: PiFinder/ui/menu_structure.py:43 msgid "Focus" msgstr "Mise au point" -#: PiFinder/ui/menu_structure.py:46 +#: PiFinder/ui/menu_structure.py:47 #, fuzzy msgid "Align" msgstr "Alignement" -#: PiFinder/ui/menu_structure.py:52 PiFinder/ui/menu_structure.py:932 +#: PiFinder/ui/menu_structure.py:53 PiFinder/ui/menu_structure.py:963 msgid "GPS Status" msgstr "Status du GPS" -#: PiFinder/ui/menu_structure.py:58 +#: PiFinder/ui/menu_structure.py:57 +msgid "Connect WiFi" +msgstr "" + +#: PiFinder/ui/menu_structure.py:63 msgid "Chart" msgstr "Cartes" -#: PiFinder/ui/menu_structure.py:64 +#: PiFinder/ui/menu_structure.py:69 msgid "Objects" msgstr "Objets" -#: PiFinder/ui/menu_structure.py:69 +#: PiFinder/ui/menu_structure.py:74 msgid "All Filtered" msgstr "Tous filtres" -#: PiFinder/ui/menu_structure.py:74 +#: PiFinder/ui/menu_structure.py:79 msgid "By Catalog" msgstr "Par catalogue" -#: PiFinder/ui/menu_structure.py:79 PiFinder/ui/menu_structure.py:254 +#: PiFinder/ui/menu_structure.py:84 PiFinder/ui/menu_structure.py:259 msgid "Planets" msgstr "Planètes" -#: PiFinder/ui/menu_structure.py:85 PiFinder/ui/menu_structure.py:156 -#: PiFinder/ui/menu_structure.py:258 PiFinder/ui/menu_structure.py:308 +#: PiFinder/ui/menu_structure.py:90 PiFinder/ui/menu_structure.py:161 +#: PiFinder/ui/menu_structure.py:263 PiFinder/ui/menu_structure.py:313 msgid "NGC" msgstr "NGC" -#: PiFinder/ui/menu_structure.py:91 PiFinder/ui/menu_structure.py:150 -#: PiFinder/ui/menu_structure.py:262 PiFinder/ui/menu_structure.py:304 +#: PiFinder/ui/menu_structure.py:96 PiFinder/ui/menu_structure.py:155 +#: PiFinder/ui/menu_structure.py:267 PiFinder/ui/menu_structure.py:309 msgid "Messier" msgstr "Messier" -#: PiFinder/ui/menu_structure.py:97 PiFinder/ui/menu_structure.py:266 +#: PiFinder/ui/menu_structure.py:102 PiFinder/ui/menu_structure.py:271 msgid "DSO..." msgstr "DSO..." -#: PiFinder/ui/menu_structure.py:102 PiFinder/ui/menu_structure.py:272 +#: PiFinder/ui/menu_structure.py:107 PiFinder/ui/menu_structure.py:277 msgid "Abell Pn" msgstr "Abell Pn" -#: PiFinder/ui/menu_structure.py:108 PiFinder/ui/menu_structure.py:276 +#: PiFinder/ui/menu_structure.py:113 PiFinder/ui/menu_structure.py:281 msgid "Arp Galaxies" msgstr "Arp Galaxies" -#: PiFinder/ui/menu_structure.py:114 PiFinder/ui/menu_structure.py:280 +#: PiFinder/ui/menu_structure.py:119 PiFinder/ui/menu_structure.py:285 msgid "Barnard" msgstr "Barnard" -#: PiFinder/ui/menu_structure.py:120 PiFinder/ui/menu_structure.py:284 +#: PiFinder/ui/menu_structure.py:125 PiFinder/ui/menu_structure.py:289 msgid "Caldwell" msgstr "Caldwell" -#: PiFinder/ui/menu_structure.py:126 PiFinder/ui/menu_structure.py:288 +#: PiFinder/ui/menu_structure.py:131 PiFinder/ui/menu_structure.py:293 msgid "Collinder" msgstr "Collinder" -#: PiFinder/ui/menu_structure.py:132 PiFinder/ui/menu_structure.py:292 +#: PiFinder/ui/menu_structure.py:137 PiFinder/ui/menu_structure.py:297 msgid "E.G. Globs" msgstr "E.G. Globs" -#: PiFinder/ui/menu_structure.py:138 PiFinder/ui/menu_structure.py:296 +#: PiFinder/ui/menu_structure.py:143 PiFinder/ui/menu_structure.py:301 msgid "Herschel 400" msgstr "Herschel 400" -#: PiFinder/ui/menu_structure.py:144 PiFinder/ui/menu_structure.py:300 +#: PiFinder/ui/menu_structure.py:149 PiFinder/ui/menu_structure.py:305 msgid "IC" msgstr "IC" -#: PiFinder/ui/menu_structure.py:162 PiFinder/ui/menu_structure.py:312 +#: PiFinder/ui/menu_structure.py:167 PiFinder/ui/menu_structure.py:317 msgid "Sharpless" msgstr "Sharpless" -#: PiFinder/ui/menu_structure.py:168 PiFinder/ui/menu_structure.py:316 +#: PiFinder/ui/menu_structure.py:173 PiFinder/ui/menu_structure.py:321 msgid "TAAS 200" msgstr "TAAS 200" -#: PiFinder/ui/menu_structure.py:176 PiFinder/ui/menu_structure.py:322 +#: PiFinder/ui/menu_structure.py:181 PiFinder/ui/menu_structure.py:327 msgid "Stars..." msgstr "Etoiles" -#: PiFinder/ui/menu_structure.py:181 PiFinder/ui/menu_structure.py:328 +#: PiFinder/ui/menu_structure.py:186 PiFinder/ui/menu_structure.py:333 msgid "Bright Named" msgstr "Brillantes" -#: PiFinder/ui/menu_structure.py:187 PiFinder/ui/menu_structure.py:332 +#: PiFinder/ui/menu_structure.py:192 PiFinder/ui/menu_structure.py:337 msgid "SAC Doubles" msgstr "SAC Doubles" -#: PiFinder/ui/menu_structure.py:193 PiFinder/ui/menu_structure.py:336 +#: PiFinder/ui/menu_structure.py:198 PiFinder/ui/menu_structure.py:341 msgid "SAC Asterisms" msgstr "SAC Asterismes" -#: PiFinder/ui/menu_structure.py:199 PiFinder/ui/menu_structure.py:340 +#: PiFinder/ui/menu_structure.py:204 PiFinder/ui/menu_structure.py:345 msgid "SAC Red Stars" msgstr "SAC Etoiles Rouges" -#: PiFinder/ui/menu_structure.py:205 PiFinder/ui/menu_structure.py:344 +#: PiFinder/ui/menu_structure.py:210 PiFinder/ui/menu_structure.py:349 msgid "RASC Doubles" msgstr "RASC Doubles" -#: PiFinder/ui/menu_structure.py:211 PiFinder/ui/menu_structure.py:348 +#: PiFinder/ui/menu_structure.py:216 PiFinder/ui/menu_structure.py:353 msgid "TLK 90 Variables" msgstr "TLK 90 Variables" -#: PiFinder/ui/menu_structure.py:221 +#: PiFinder/ui/menu_structure.py:226 msgid "Recent" msgstr "Récent" -#: PiFinder/ui/menu_structure.py:227 +#: PiFinder/ui/menu_structure.py:232 msgid "Name Search" msgstr "Rech. par Nom" -#: PiFinder/ui/menu_structure.py:233 PiFinder/ui/object_list.py:136 +#: PiFinder/ui/menu_structure.py:238 PiFinder/ui/object_list.py:136 msgid "Filter" msgstr "Filtre" -#: PiFinder/ui/menu_structure.py:239 +#: PiFinder/ui/menu_structure.py:244 msgid "Reset All" msgstr "RàZ tout" -#: PiFinder/ui/menu_structure.py:243 PiFinder/ui/menu_structure.py:973 +#: PiFinder/ui/menu_structure.py:248 PiFinder/ui/menu_structure.py:1004 msgid "Confirm" msgstr "Confirmation" -#: PiFinder/ui/menu_structure.py:244 PiFinder/ui/menu_structure.py:976 +#: PiFinder/ui/menu_structure.py:249 PiFinder/ui/menu_structure.py:1007 #: PiFinder/ui/software.py:204 msgid "Cancel" msgstr "Abandon" -#: PiFinder/ui/menu_structure.py:248 +#: PiFinder/ui/menu_structure.py:253 msgid "Catalogs" msgstr "Catalogues" -#: PiFinder/ui/menu_structure.py:356 +#: PiFinder/ui/menu_structure.py:361 msgid "Type" msgstr "Type" -#: PiFinder/ui/menu_structure.py:370 +#: PiFinder/ui/menu_structure.py:375 #, fuzzy msgid "Cluster/Neb" msgstr "Amas Ouvert" -#: PiFinder/ui/menu_structure.py:382 +#: PiFinder/ui/menu_structure.py:387 msgid "P. Nebula" msgstr "Nébuleuse Planétaire" -#: PiFinder/ui/menu_structure.py:394 +#: PiFinder/ui/menu_structure.py:399 msgid "Double Str" msgstr "Etoile Double" -#: PiFinder/ui/menu_structure.py:398 +#: PiFinder/ui/menu_structure.py:403 #, fuzzy msgid "Triple Str" msgstr "Etoile Double" -#: PiFinder/ui/menu_structure.py:420 +#: PiFinder/ui/menu_structure.py:425 msgid "Altitude" msgstr "Altitude" -#: PiFinder/ui/menu_structure.py:452 +#: PiFinder/ui/menu_structure.py:457 msgid "Magnitude" msgstr "Magnitude" -#: PiFinder/ui/menu_structure.py:504 PiFinder/ui/menu_structure.py:514 +#: PiFinder/ui/menu_structure.py:509 PiFinder/ui/menu_structure.py:519 msgid "Observed" msgstr "Observé" -#: PiFinder/ui/menu_structure.py:510 +#: PiFinder/ui/menu_structure.py:515 msgid "Any" msgstr "Tous" -#: PiFinder/ui/menu_structure.py:518 +#: PiFinder/ui/menu_structure.py:523 msgid "Not Observed" msgstr "Non Observé" -#: PiFinder/ui/menu_structure.py:531 +#: PiFinder/ui/menu_structure.py:536 msgid "User Pref..." msgstr "Pref. Utilisateur" -#: PiFinder/ui/menu_structure.py:536 +#: PiFinder/ui/menu_structure.py:541 msgid "Key Bright" msgstr "Brillance Touche" -#: PiFinder/ui/menu_structure.py:576 +#: PiFinder/ui/menu_structure.py:581 msgid "Sleep Time" msgstr "Mise en Sommeil" -#: PiFinder/ui/menu_structure.py:582 PiFinder/ui/menu_structure.py:614 -#: PiFinder/ui/menu_structure.py:638 PiFinder/ui/menu_structure.py:687 -#: PiFinder/ui/menu_structure.py:711 PiFinder/ui/menu_structure.py:735 -#: PiFinder/ui/menu_structure.py:759 PiFinder/ui/preview.py:62 +#: PiFinder/ui/menu_structure.py:587 PiFinder/ui/menu_structure.py:619 +#: PiFinder/ui/menu_structure.py:643 PiFinder/ui/menu_structure.py:717 +#: PiFinder/ui/menu_structure.py:741 PiFinder/ui/menu_structure.py:765 +#: PiFinder/ui/menu_structure.py:789 PiFinder/ui/preview.py:62 #: PiFinder/ui/preview.py:79 msgid "Off" msgstr "Arret" -#: PiFinder/ui/menu_structure.py:608 +#: PiFinder/ui/menu_structure.py:613 msgid "Menu Anim" msgstr "Anim. Menu" -#: PiFinder/ui/menu_structure.py:618 PiFinder/ui/menu_structure.py:642 +#: PiFinder/ui/menu_structure.py:623 PiFinder/ui/menu_structure.py:647 msgid "Fast" msgstr "Rapide" -#: PiFinder/ui/menu_structure.py:622 PiFinder/ui/menu_structure.py:646 -#: PiFinder/ui/menu_structure.py:695 PiFinder/ui/menu_structure.py:719 -#: PiFinder/ui/menu_structure.py:743 PiFinder/ui/preview.py:67 +#: PiFinder/ui/menu_structure.py:627 PiFinder/ui/menu_structure.py:651 +#: PiFinder/ui/menu_structure.py:725 PiFinder/ui/menu_structure.py:749 +#: PiFinder/ui/menu_structure.py:773 PiFinder/ui/preview.py:67 msgid "Medium" msgstr "Moyen" -#: PiFinder/ui/menu_structure.py:626 PiFinder/ui/menu_structure.py:650 +#: PiFinder/ui/menu_structure.py:631 PiFinder/ui/menu_structure.py:655 msgid "Slow" msgstr "Lent" -#: PiFinder/ui/menu_structure.py:632 +#: PiFinder/ui/menu_structure.py:637 msgid "Scroll Speed" msgstr "Vitesse défilement" -#: PiFinder/ui/menu_structure.py:656 +#: PiFinder/ui/menu_structure.py:661 msgid "Az Arrows" msgstr "Fleches AZ" -#: PiFinder/ui/menu_structure.py:663 +#: PiFinder/ui/menu_structure.py:668 msgid "Default" msgstr "Par défaut" -#: PiFinder/ui/menu_structure.py:667 +#: PiFinder/ui/menu_structure.py:672 msgid "Reverse" msgstr "A l'envers" -#: PiFinder/ui/menu_structure.py:675 +#: PiFinder/ui/menu_structure.py:678 +#, fuzzy +msgid "Language" +msgstr "Langage" + +#: PiFinder/ui/menu_structure.py:685 +#, fuzzy +msgid "English" +msgstr "Anglais" + +#: PiFinder/ui/menu_structure.py:689 +#, fuzzy +msgid "German" +msgstr "Allemand" + +#: PiFinder/ui/menu_structure.py:693 +#, fuzzy +msgid "French" +msgstr "Français" + +#: PiFinder/ui/menu_structure.py:697 +#, fuzzy +msgid "Spanish" +msgstr "Espagnol" + +#: PiFinder/ui/menu_structure.py:705 msgid "Chart..." msgstr "Carte..." -#: PiFinder/ui/menu_structure.py:681 +#: PiFinder/ui/menu_structure.py:711 msgid "Reticle" msgstr "Réticule" -#: PiFinder/ui/menu_structure.py:691 PiFinder/ui/menu_structure.py:715 -#: PiFinder/ui/menu_structure.py:739 PiFinder/ui/preview.py:72 +#: PiFinder/ui/menu_structure.py:721 PiFinder/ui/menu_structure.py:745 +#: PiFinder/ui/menu_structure.py:769 PiFinder/ui/preview.py:72 msgid "Low" msgstr "Bas" -#: PiFinder/ui/menu_structure.py:699 PiFinder/ui/menu_structure.py:723 -#: PiFinder/ui/menu_structure.py:747 PiFinder/ui/preview.py:64 +#: PiFinder/ui/menu_structure.py:729 PiFinder/ui/menu_structure.py:753 +#: PiFinder/ui/menu_structure.py:777 PiFinder/ui/preview.py:64 msgid "High" msgstr "Haut" -#: PiFinder/ui/menu_structure.py:705 +#: PiFinder/ui/menu_structure.py:735 msgid "Constellation" msgstr "Constéllation" -#: PiFinder/ui/menu_structure.py:729 +#: PiFinder/ui/menu_structure.py:759 msgid "DSO Display" msgstr "Affichage DSO" -#: PiFinder/ui/menu_structure.py:753 +#: PiFinder/ui/menu_structure.py:783 msgid "RA/DEC Disp." msgstr "Affichage AD/DEC" -#: PiFinder/ui/menu_structure.py:763 +#: PiFinder/ui/menu_structure.py:793 msgid "HH:MM" msgstr "HH:MM" -#: PiFinder/ui/menu_structure.py:767 +#: PiFinder/ui/menu_structure.py:797 msgid "Degrees" msgstr "Degrés" -#: PiFinder/ui/menu_structure.py:775 +#: PiFinder/ui/menu_structure.py:805 msgid "Camera Exp" msgstr "Expo. Caméra" -#: PiFinder/ui/menu_structure.py:783 +#: PiFinder/ui/menu_structure.py:813 msgid "0.025s" msgstr "0.025s" -#: PiFinder/ui/menu_structure.py:787 +#: PiFinder/ui/menu_structure.py:817 msgid "0.05s" msgstr "0.05s" -#: PiFinder/ui/menu_structure.py:791 +#: PiFinder/ui/menu_structure.py:821 msgid "0.1s" msgstr "0.1s" -#: PiFinder/ui/menu_structure.py:795 +#: PiFinder/ui/menu_structure.py:825 msgid "0.2s" msgstr "0.2s" -#: PiFinder/ui/menu_structure.py:799 +#: PiFinder/ui/menu_structure.py:829 msgid "0.4s" msgstr "0.4s" -#: PiFinder/ui/menu_structure.py:803 +#: PiFinder/ui/menu_structure.py:833 msgid "0.8s" msgstr "0.8s" -#: PiFinder/ui/menu_structure.py:807 +#: PiFinder/ui/menu_structure.py:837 msgid "1s" msgstr "1s" -#: PiFinder/ui/menu_structure.py:813 +#: PiFinder/ui/menu_structure.py:843 msgid "WiFi Mode" msgstr "Mode Wifi" -#: PiFinder/ui/menu_structure.py:819 +#: PiFinder/ui/menu_structure.py:850 #, fuzzy msgid "Client Mode" msgstr "Mode client" -#: PiFinder/ui/menu_structure.py:824 +#: PiFinder/ui/menu_structure.py:855 #, fuzzy msgid "AP Mode" msgstr "Mode Wifi" -#: PiFinder/ui/menu_structure.py:831 +#: PiFinder/ui/menu_structure.py:862 msgid "PiFinder Type" msgstr "Type de PiFinder" -#: PiFinder/ui/menu_structure.py:838 +#: PiFinder/ui/menu_structure.py:869 msgid "Left" msgstr "Gauche" -#: PiFinder/ui/menu_structure.py:842 +#: PiFinder/ui/menu_structure.py:873 msgid "Right" msgstr "Droit" -#: PiFinder/ui/menu_structure.py:846 +#: PiFinder/ui/menu_structure.py:877 msgid "Straight" msgstr "Face" -#: PiFinder/ui/menu_structure.py:850 +#: PiFinder/ui/menu_structure.py:881 msgid "Flat v3" msgstr "Plat v3" -#: PiFinder/ui/menu_structure.py:854 +#: PiFinder/ui/menu_structure.py:885 msgid "Flat v2" msgstr "Plat v2" -#: PiFinder/ui/menu_structure.py:860 +#: PiFinder/ui/menu_structure.py:891 msgid "Mount Type" msgstr "Type de Monture" -#: PiFinder/ui/menu_structure.py:867 +#: PiFinder/ui/menu_structure.py:898 msgid "Alt/Az" msgstr "Alt/Az" -#: PiFinder/ui/menu_structure.py:871 +#: PiFinder/ui/menu_structure.py:902 msgid "Equitorial" msgstr "Equatoriale" -#: PiFinder/ui/menu_structure.py:877 +#: PiFinder/ui/menu_structure.py:908 msgid "Camera Type" msgstr "Type Caméra" -#: PiFinder/ui/menu_structure.py:883 +#: PiFinder/ui/menu_structure.py:914 msgid "v2 - imx477" msgstr "v2 - imx477" -#: PiFinder/ui/menu_structure.py:888 +#: PiFinder/ui/menu_structure.py:919 msgid "v3 - imx296" msgstr "v3 - imx296" -#: PiFinder/ui/menu_structure.py:893 +#: PiFinder/ui/menu_structure.py:924 #, fuzzy msgid "v3 - imx462" msgstr "v3 - imx462" -#: PiFinder/ui/menu_structure.py:900 +#: PiFinder/ui/menu_structure.py:931 #, fuzzy msgid "GPS Type" msgstr "Type de GPS" -#: PiFinder/ui/menu_structure.py:908 +#: PiFinder/ui/menu_structure.py:939 msgid "UBlox" msgstr "UBlox" -#: PiFinder/ui/menu_structure.py:912 +#: PiFinder/ui/menu_structure.py:943 msgid "GPSD (generic)" msgstr "GPSD (generique)" -#: PiFinder/ui/menu_structure.py:920 +#: PiFinder/ui/menu_structure.py:951 msgid "Tools" msgstr "Outils" -#: PiFinder/ui/menu_structure.py:924 +#: PiFinder/ui/menu_structure.py:955 #, fuzzy msgid "Status" msgstr "Redémarrage" -#: PiFinder/ui/menu_structure.py:925 +#: PiFinder/ui/menu_structure.py:956 msgid "Equipment" msgstr "Equipment" -#: PiFinder/ui/menu_structure.py:927 +#: PiFinder/ui/menu_structure.py:958 #, fuzzy msgid "Place & Time" msgstr "Mise en Sommeil" -#: PiFinder/ui/menu_structure.py:936 +#: PiFinder/ui/menu_structure.py:967 #, fuzzy msgid "Set Location" msgstr "Réglages" -#: PiFinder/ui/menu_structure.py:940 +#: PiFinder/ui/menu_structure.py:971 #, fuzzy msgid "Set Time" msgstr "Mise en Sommeil" -#: PiFinder/ui/menu_structure.py:944 +#: PiFinder/ui/menu_structure.py:975 #, fuzzy msgid "Reset" msgstr "Récent" -#: PiFinder/ui/menu_structure.py:947 +#: PiFinder/ui/menu_structure.py:978 msgid "Console" msgstr "Console" -#: PiFinder/ui/menu_structure.py:948 +#: PiFinder/ui/menu_structure.py:979 msgid "Software Upd" msgstr "Mise à jour" -#: PiFinder/ui/menu_structure.py:951 +#: PiFinder/ui/menu_structure.py:982 msgid "Power" msgstr "Allumage" -#: PiFinder/ui/menu_structure.py:957 +#: PiFinder/ui/menu_structure.py:988 msgid "Shutdown" msgstr "Arrêt" -#: PiFinder/ui/menu_structure.py:967 +#: PiFinder/ui/menu_structure.py:998 msgid "Restart" msgstr "Redémarrage" -#: PiFinder/ui/menu_structure.py:982 +#: PiFinder/ui/menu_structure.py:1013 msgid "Experimental" msgstr "Expèrimental" -#: PiFinder/ui/menu_structure.py:987 -#, fuzzy -msgid "Language" -msgstr "Langage" - -#: PiFinder/ui/menu_structure.py:994 -#, fuzzy -msgid "English" -msgstr "Anglais" - -#: PiFinder/ui/menu_structure.py:998 -#, fuzzy -msgid "German" -msgstr "Allemand" - -#: PiFinder/ui/menu_structure.py:1002 -#, fuzzy -msgid "French" -msgstr "Français" - -#: PiFinder/ui/menu_structure.py:1006 -#, fuzzy -msgid "Spanish" -msgstr "Espagnol" - #: PiFinder/ui/object_details.py:61 PiFinder/ui/object_details.py:66 #, fuzzy msgid "ALIGN" @@ -927,42 +931,42 @@ msgstr "" msgid "Sz:{size}" msgstr "" -#: PiFinder/ui/object_details.py:207 +#: PiFinder/ui/object_details.py:208 #, fuzzy msgid "  Not Logged" msgstr "Connecté" -#: PiFinder/ui/object_details.py:209 +#: PiFinder/ui/object_details.py:210 msgid "  {logs} Logs" msgstr "" -#: PiFinder/ui/object_details.py:244 +#: PiFinder/ui/object_details.py:245 #, fuzzy msgid "No solve" msgstr "Pas d'astrometrie" -#: PiFinder/ui/object_details.py:250 +#: PiFinder/ui/object_details.py:251 msgid "yet{elipsis}" msgstr "" -#: PiFinder/ui/object_details.py:264 +#: PiFinder/ui/object_details.py:265 #, fuzzy msgid "Searching" msgstr "Seeing" -#: PiFinder/ui/object_details.py:270 +#: PiFinder/ui/object_details.py:271 msgid "for GPS{elipsis}" msgstr "" -#: PiFinder/ui/object_details.py:284 +#: PiFinder/ui/object_details.py:285 msgid "Calculating" msgstr "Calcul en cours" -#: PiFinder/ui/object_details.py:471 +#: PiFinder/ui/object_details.py:472 msgid "Too Far" msgstr "Trop loin" -#: PiFinder/ui/object_details.py:496 +#: PiFinder/ui/object_details.py:497 #, fuzzy msgid "LOG" msgstr "Bas" @@ -1159,6 +1163,40 @@ msgstr "" msgid "󰍴 Delete/Previous" msgstr "" +#: PiFinder/ui/wifi_password.py:41 +#, fuzzy +msgid "mode" +msgstr "Mode Wifi" + +#: PiFinder/ui/wifi_password.py:50 +msgid "QR" +msgstr "" + +#: PiFinder/ui/wifi_password.py:53 +msgid "Passwd" +msgstr "" + +#: PiFinder/ui/wifi_password.py:153 PiFinder/ui/wifi_password.py:180 +#, fuzzy +msgid "Client mode!" +msgstr "Mode client" + +#: PiFinder/ui/wifi_password.py:160 +msgid "Connected to:" +msgstr "" + +#: PiFinder/ui/wifi_password.py:209 +msgid "Note: {wifi_mode} mode!" +msgstr "" + +#: PiFinder/ui/wifi_password.py:223 +msgid "Password:" +msgstr "" + +#: PiFinder/ui/wifi_password.py:273 +msgid "SSID:" +msgstr "" + #~ msgid "Language: Spanish" #~ msgstr "Langage: Espagnol" @@ -1513,3 +1551,6 @@ msgstr "" #~ msgid "HELP" #~ msgstr "" +#~ msgid "Client Mode!" +#~ msgstr "Mode client" + From 1afaf1ef835e8ca3d4c768fad4e303473c57b0d8 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 5 May 2025 08:24:38 +0200 Subject: [PATCH 59/61] solver: revert 3025fb9 --- python/PiFinder/solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/PiFinder/solver.py b/python/PiFinder/solver.py index f664485c9..84129ef14 100644 --- a/python/PiFinder/solver.py +++ b/python/PiFinder/solver.py @@ -163,7 +163,7 @@ def solver( centroids, (512, 512), fov_estimate=12.0, - fov_max_error=6.0, + fov_max_error=4.0, match_max_error=0.005, # return_matches=True, target_pixel=shared_state.solve_pixel(), From 70c8630fd94446698ca099d18f4f9a481ef5bfed Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 5 May 2025 08:26:59 +0200 Subject: [PATCH 60/61] Make type cheicking work. --- python/PiFinder/ui/wifi_password.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 4e7a75acb..892ee0879 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -1,6 +1,7 @@ import logging import qrcode import math +from typing import Any, TYPE_CHECKING from PiFinder import state_utils from PiFinder.ui.base import UIModule @@ -8,6 +9,11 @@ from PiFinder.ui.marking_menus import MarkingMenuOption, MarkingMenu # from PiFinder.sys_utils import Network +if TYPE_CHECKING: + + def _(a) -> Any: + return a + logger = logging.getLogger("WiFiPassword") # Constants for Display Modes From 3e368bca31c8fa4d86068e5ef1638747ae6156f2 Mon Sep 17 00:00:00 2001 From: Jens Scheidtmann Date: Mon, 5 May 2025 08:28:23 +0200 Subject: [PATCH 61/61] Source code formatting --- python/PiFinder/server.py | 6 +-- python/PiFinder/sys_utils.py | 63 ++++++++++++++++++---------- python/PiFinder/sys_utils_fake.py | 8 ++-- python/PiFinder/ui/menu_structure.py | 5 +-- python/PiFinder/ui/wifi_password.py | 13 ++++-- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/python/PiFinder/server.py b/python/PiFinder/server.py index 6a9485db7..07b042098 100644 --- a/python/PiFinder/server.py +++ b/python/PiFinder/server.py @@ -429,7 +429,7 @@ def network_update(): if self.network.is_ap_open(): ap_encrypt = request.forms.get("ap_encrypt") if ap_encrypt == "1": - try: + try: self.network.enable_encryption() self.network.set_ap_pwd(ap_passwd) except Exception as e: @@ -442,11 +442,11 @@ def network_update(): err_pwd = "Invalid password: " + e.args[0] error_triggered = True - err_country="" + err_country = "" if wifi_country not in self.network.COUNTRY_CODES: err_country = "Invalid country code" error_triggered = True - + if error_triggered: return network_page(err_pwd=err_pwd, err_country=err_country) diff --git a/python/PiFinder/sys_utils.py b/python/PiFinder/sys_utils.py index 6a9e9ed56..6d632f711 100644 --- a/python/PiFinder/sys_utils.py +++ b/python/PiFinder/sys_utils.py @@ -33,16 +33,16 @@ def __init__(self): def get_wifi_mode(): with open(f"{utils.pifinder_dir}/wifi_status.txt", "r") as wifi_f: return wifi_f.read() - + @staticmethod - def configure_accesspoint(restart_hostapd = True) -> None: + def configure_accesspoint(restart_hostapd=True) -> None: """Add WPA2 encryption, if not already enabled. Tasks: 0) If passphrase is already in hostapd.conf, do not change it (this ignores the case where the ap_name contains ENCRYPTME) 1) if SSID in current config contains CHANGEME, create a random SSID of the from PiFinder-XYZAB, XYZAB 5 random chars (see below) and use that. 2) If SSID in current config contains ENCRYPTME, add encryption to hostapd.conf, generate a 20 character random password - (20 chars in 5 groups of random chars, separeted by '-', see below) + (20 chars in 5 groups of random chars, separeted by '-', see below) where 'random char' means a randomly selected character out of the set of 0-9, a-z and A-Z. """ @@ -50,11 +50,11 @@ def configure_accesspoint(restart_hostapd = True) -> None: with open("/etc/hostapd/hostapd.conf", "r") as conf: for line in conf: if line.startswith("ssid="): - if ("ENCRYPTME" in line or "CHANGEME" in line): + if "ENCRYPTME" in line or "CHANGEME" in line: action_needed = True if not action_needed: - return - + return + logger.info("SYSUTILS: Configuring WIFI Access Point definition.") passphrase_detected = False @@ -70,7 +70,9 @@ def configure_accesspoint(restart_hostapd = True) -> None: ap_rnd = Network._generate_random_chars(5) line = f"ssid=PiFinder-{ap_rnd}\n" ssid_changed = True - logger.warning(f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'") + logger.warning( + f"SYS-Network: Changing SSID to 'PiFinder-{ap_rnd}'" + ) if line.startswith("wpa_passphrase="): logger.info("SYS-Network: Passphrase detected.") passphrase_detected = True @@ -81,12 +83,14 @@ def configure_accesspoint(restart_hostapd = True) -> None: sh.sudo("cp", "/etc/hostapd/hostapd.conf", "/etc/hostapd/hostapd.conf.bck") sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") - if encryption_needed and not passphrase_detected: # must be outside + if encryption_needed and not passphrase_detected: # must be outside Network.enable_encryption() # If we are enabling encryption or changed SSID, restart hostapd, if in AP mode - if (not (passphrase_detected and encryption_needed) or ssid_changed) and restart_hostapd: - Network.force_restart_hostapd() + if ( + not (passphrase_detected and encryption_needed) or ssid_changed + ) and restart_hostapd: + Network.force_restart_hostapd() @staticmethod def enable_encryption() -> None: @@ -114,7 +118,6 @@ def force_restart_hostapd() -> None: logger.warning("Network: Restarting hostapd") sh.sudo("systemctl", "restart", "hostapd") - def populate_wifi_networks(self) -> None: wpa_supplicant_path = "/etc/wpa_supplicant/wpa_supplicant.conf" self._wifi_networks = [] @@ -134,13 +137,28 @@ def populate_wifi_countries(self) -> None: try: with open("/usr/share/zoneinfo/iso3166.tab", "r") as iso_countries: lines = iso_countries.readlines() - lines = [line for line in lines if not line.startswith("#") and line != "\n" and line != "\t\n"] + lines = [ + line + for line in lines + if not line.startswith("#") and line != "\n" and line != "\t\n" + ] self.COUNTRY_CODES = [line.split("\t")[0] for line in lines] logger.debug(f"Country Codes: {self.COUNTRY_CODES}") # print(self.COUNTRY_CODES) - except IOError as e: + except IOError: logger.error("Error reading /usr/share/zoneinfo/iso3166.tab", exc_info=True) - self.COUNTRY_CODES = ['US', 'CA', 'GB', 'DE', 'FR', 'IT', 'ES', 'NL', 'JP', 'CN'] + self.COUNTRY_CODES = [ + "US", + "CA", + "GB", + "DE", + "FR", + "IT", + "ES", + "NL", + "JP", + "CN", + ] logger.error(f"Using default country codes: {self.COUNTRY_CODES}") @staticmethod @@ -251,21 +269,21 @@ def get_ap_pwd(self): for line in conf: if line.startswith("wpa_passphrase="): return line[15:-1] - return NO_PASSWORD_DEFINED + return NO_PASSWORD_DEFINED def set_ap_pwd(self, ap_pwd): - """ Set Access Point password. + """Set Access Point password. If the password is the same as the current password, nothing is done. It is the responsiblity of the caller to ensure that this method is only called when AP enryption is already is enabled! - + This method throws an ValueError of the password is < 8 or > 63 characters long. """ current_pwd = self.get_ap_pwd() if ap_pwd == current_pwd: return - + # Check password length if len(ap_pwd) < 8: raise ValueError("Password must be at least 8 characters long") @@ -305,7 +323,7 @@ def get_ap_wifi_country(self): if line.startswith("country_code="): return line[13:-1] return "US" - + def set_ap_wifi_country(self, country_code): country_changed = False with open("/tmp/hostapd.conf", "w") as new_conf: @@ -323,8 +341,10 @@ def set_ap_wifi_country(self, country_code): try: sh.sudo("raspi-config", "nonint", "do_wifi_country", country_code) sh.sudo("cp", "/tmp/hostapd.conf", "/etc/hostapd/hostapd.conf") - except: - logger.warning(f"SYS: Failed to set wifi country code to {country_code}") + except: + logger.warning( + f"SYS: Failed to set wifi country code to {country_code}" + ) raise def get_host_name(self): @@ -513,6 +533,7 @@ def switch_cam_imx462() -> None: logger.info("SYS: Switching cam to imx462") sh.sudo("python", "-m", "PiFinder.switch_camera", "imx462") + if __name__ == "__main__": # This is for testing purposes only Network.configure_accesspoint() diff --git a/python/PiFinder/sys_utils_fake.py b/python/PiFinder/sys_utils_fake.py index dd5ab9c73..b7728f2fb 100644 --- a/python/PiFinder/sys_utils_fake.py +++ b/python/PiFinder/sys_utils_fake.py @@ -12,7 +12,7 @@ class Network: """ def __init__(self): - self.COUNTRY_CODES = ['US', 'BE', 'DE'] + self.COUNTRY_CODES = ["US", "BE", "DE"] @staticmethod def configure_accesspoint() -> None: @@ -20,7 +20,7 @@ def configure_accesspoint() -> None: @staticmethod def enable_encryption() -> None: - pass + pass def populate_wifi_networks(self): """ @@ -58,12 +58,12 @@ def set_ap_name(self, ap_name): def get_ap_wifi_country(self): return "UNKN" - + def set_ap_wifi_country(self, ap_wifi_country): pass def is_ap_open(self): - return False # i.e. encrypted = Not ( Is AP encrypted? ) + return False # i.e. encrypted = Not ( Is AP encrypted? ) def get_host_name(self): return socket.gethostname() diff --git a/python/PiFinder/ui/menu_structure.py b/python/PiFinder/ui/menu_structure.py index 35301237e..6d3c51173 100644 --- a/python/PiFinder/ui/menu_structure.py +++ b/python/PiFinder/ui/menu_structure.py @@ -53,10 +53,7 @@ def _(key: str) -> Any: "name": _("GPS Status"), "class": UIGPSStatus, }, - { - "name": _("Connect WiFi"), - "class": UIWiFiPassword - }, + {"name": _("Connect WiFi"), "class": UIWiFiPassword}, ], }, { diff --git a/python/PiFinder/ui/wifi_password.py b/python/PiFinder/ui/wifi_password.py index 892ee0879..a2a6732dd 100644 --- a/python/PiFinder/ui/wifi_password.py +++ b/python/PiFinder/ui/wifi_password.py @@ -14,6 +14,7 @@ def _(a) -> Any: return a + logger = logging.getLogger("WiFiPassword") # Constants for Display Modes @@ -44,7 +45,9 @@ def __init__(self, *args, **kwargs) -> None: self.marking_menu = MarkingMenu( left=MarkingMenuOption(), right=MarkingMenuOption(), - down=MarkingMenuOption(label=_("mode"), menu_jump="wifi_mode"), # TRANSLATORS: Jump to WiFi mode selection from context menu + down=MarkingMenuOption( + label=_("mode"), menu_jump="wifi_mode" + ), # TRANSLATORS: Jump to WiFi mode selection from context menu ) else: # Default to QR code display @@ -53,10 +56,14 @@ def __init__(self, *args, **kwargs) -> None: self.marking_menu = MarkingMenu( left=MarkingMenuOption( - label=_("QR"), callback=self.mm_display_qr, enabled=True # TRANSLATORS: Switch to QR code WiFi display in context menu + label=_("QR"), + callback=self.mm_display_qr, + enabled=True, # TRANSLATORS: Switch to QR code WiFi display in context menu ), right=MarkingMenuOption( - label=_("Passwd"), callback=self.mm_display_pwd, enabled=True # TRANSLATORS: Switch to WiFi plain password display in context menu + label=_("Passwd"), + callback=self.mm_display_pwd, + enabled=True, # TRANSLATORS: Switch to WiFi plain password display in context menu ), down=MarkingMenuOption(label="mode", menu_jump="wifi_mode"), )