diff --git a/README.md b/README.md index b445a7a..400175f 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,38 @@ cp SOURCE_FILE_NAME /pyboard/NEW_FILE_NAME Copying '/Users/Jones/Downloads/MicroPython/ESP-WiFi-Manager/wifi_manager.py' to '/pyboard/wifi_manager.py' ... ``` +Create compressed CSS and JS files as described in the +[simulation static files README](simulation/static) to save disk space on the +device and increase the performance (webpages are loading faster) + +```bash +mkdir /pyboard/static/ +cp simulation/static/css/*.gz /pyboard/static/ +# around 24kB compared to uncompressed 120kB + +# optional, not used so far +mkdir /pyboard/static/ +cp simulation/static/js/*.gz /pyboard/static/ +# around 12kB compared to uncompressed 40kB + +mkdir /pyboard/templates +cp templates/* /pyboard/templates +# around 20kB + +mkdir /pyboard/helpers +cp helpers/*.py /pyboard/helpers +# around 64kB + +mkdir /pyboard/primitives +cp primitives/* /pyboard/primitives +# around 8kB + +cp boot.py /pyboard +cp main.py /pyboard +cp wifi_manager.py /pyboard +# around 40kB +``` + ##### Open REPL in rshell Call `repl` in the rshell. Use CTRL+X to leave the repl or CTRL+D for a soft diff --git a/changelog.md b/changelog.md index 7ff9297..909b13d 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial [`WiFi Manager`](wifi_manager.py) implementation - Micropython [`boot`](boot.py) and [`main`](main.py) files - [`README`](README.md) and [`requirements.txt`](requirements.txt) files +- Compressed version of + [`bootstrap.min.css`](simulation/static/css/bootstrap.min.css) and + [`bootstrap.min.js`](simulation/static/js/bootstrap.min.js) #### Simulation - [`Simulation README`](simulation/README.md) file @@ -52,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [network station and client](simulation/src/wifi_helper/network.py) - Bash script to prepare all folders for a unittest coverage report - Unittests for all modules and fakes +- Render list of selectable networks in Python and provide result via API +- `sendfile` function implemented in same way as on Micropythons PicoWeb [Unreleased]: https://github.com/brainelectronics/Micropython-ESP-WiFi-Manager/compare/0.1.0...develop diff --git a/simulation/README.md b/simulation/README.md index c623237..6ee66d6 100644 --- a/simulation/README.md +++ b/simulation/README.md @@ -28,6 +28,17 @@ On Windows the package `pycryptodome>=3.14.0,<4` shall be used instead of Simulation webpages use [bootstrap 3.4][ref-bootstrap-34]. +## Usage + +Run the simulation of the ESP WiFi Manager **after** activating the virtual +environment of the [Setup section](#setup) + +```bash +sh run.sh +``` + +Open [`http://127.0.0.1:5000/`](http://127.0.0.1:5000/){:target="_blank"} in a browser + ## Unittests ### General @@ -170,17 +181,6 @@ Test [`wifi manager`][ref-wifi-manager-test] implementation. nose2 --config tests/unittest.cfg -v tests.test_wifi_manager.TestWiFiManager ``` -## Usage - -Run the simulation of the ESP WiFi Manager **after** activating the virtual -environment of the [Setup section](#setup) - -```bash -sh run.sh -``` - -Open [`http://127.0.0.1:5000/`](http://127.0.0.1:5000/){:target="_blank"} in a browser - [ref-bootstrap-34]: https://getbootstrap.com/docs/3.4/getting-started/#download diff --git a/simulation/requirements.txt b/simulation/requirements.txt index 219574c..71b02b0 100644 --- a/simulation/requirements.txt +++ b/simulation/requirements.txt @@ -2,5 +2,5 @@ flask>=2.0.2,<3 jinja2>=3.0.2,<4 PyYAML>=5.4.1,<6 nose2>=0.10.0,<1 -pycrypto>=2.6.1,<3 netifaces>=0.11.0,<1 +pycrypto>=2.6.1,<3 diff --git a/simulation/src/generic_helper/generic_helper.py b/simulation/src/generic_helper/generic_helper.py index 20a462f..0be1e6b 100644 --- a/simulation/src/generic_helper/generic_helper.py +++ b/simulation/src/generic_helper/generic_helper.py @@ -14,7 +14,7 @@ import random import sys -from typing import (Optional, Union) +from typing import Optional, Union class GenericHelper(object): diff --git a/simulation/src/led_helper/led_helper.py b/simulation/src/led_helper/led_helper.py index c157147..336eb8e 100755 --- a/simulation/src/led_helper/led_helper.py +++ b/simulation/src/led_helper/led_helper.py @@ -312,7 +312,6 @@ def fade(self, delay_ms: int = 50, pixel_amount: int = -1) -> None: self.fade_pixel_amount = pixel_amount self.fading = True - # def _fade(self, delay_ms: int, pixel_amount: int, lock: lock) -> None: def _fade(self, delay_ms: int, pixel_amount: int, lock: int) -> None: """ Internal Neopixel fading thread content. diff --git a/simulation/src/path_helper/path_helper.py b/simulation/src/path_helper/path_helper.py index 13d21b5..5094c9f 100755 --- a/simulation/src/path_helper/path_helper.py +++ b/simulation/src/path_helper/path_helper.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- -"""Provide unavailable path functions in MicroPython""" +""" +Path Helper + +Provide unavailable path functions for Micropython boards +""" # import os from pathlib import Path diff --git a/simulation/src/time_helper/time_helper.py b/simulation/src/time_helper/time_helper.py index 255deab..47a2645 100644 --- a/simulation/src/time_helper/time_helper.py +++ b/simulation/src/time_helper/time_helper.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- -"""try to sync and set the internal clock (RTC) with NTP server time""" +""" +Time Helper + +Sync and set internal clock (RTC) with NTP server time +""" from machine import RTC # import ntptime diff --git a/simulation/src/wifi_helper/network.py b/simulation/src/wifi_helper/network.py index f880ffa..7128eeb 100644 --- a/simulation/src/wifi_helper/network.py +++ b/simulation/src/wifi_helper/network.py @@ -78,8 +78,6 @@ def _scan_mac() -> List[dict]: scan_result = subprocess.check_output(['/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport', '--scan']) if len(scan_result) == 0: - print('### Check WiFi to be active ###') - print('Returning dummy data') return NetworkHelper._dummy_data() scan_result = [ele.decode('ascii') for ele in scan_result.splitlines()] @@ -188,17 +186,14 @@ def _scan_windows() -> List[dict]: try: scan_result = subprocess.check_output(['netsh', 'wlan', 'show', 'networks', 'mode=bssid']) except Exception as e: - print('Failed to scan due to this error: {}'.format(e)) - print('### Check WiFi to be active ###') - print('Returning dummy data') + # print('Failed to scan due to this error: {}'.format(e)) return NetworkHelper._dummy_data() # netsh call returns report in local language, try to decode it try: scan_result = [ele.decode('cp850').lstrip() for ele in scan_result.splitlines()] except Exception as e: - print('Failed to decode scan data due to this error: {}'.format(e)) - print('Returning dummy data') + # print('Failed to decode scan data due to this error: {}'.format(e)) return NetworkHelper._dummy_data() # [ @@ -366,6 +361,7 @@ def _dummy_data() -> List[dict]: 'hidden': False } ] + # print('Returning dummy data') return nets diff --git a/simulation/src/wifi_helper/wifi_helper.py b/simulation/src/wifi_helper/wifi_helper.py index 03eedc1..0a33aa7 100644 --- a/simulation/src/wifi_helper/wifi_helper.py +++ b/simulation/src/wifi_helper/wifi_helper.py @@ -1,7 +1,11 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- -"""connect to specified network(s) or create an accesspoint""" +""" +WiFi Helper + +Connect to specified network(s) or create an accesspoint +""" # import ubinascii import json @@ -341,7 +345,8 @@ def scan_networks(self) -> None: try: net['authmode'] = self.auth_modes[net['authmode']] except KeyError: - print('{} is unknown authmode'.format(net['authmode'])) + # print('{} is unknown authmode'.format(net['authmode'])) + pass except Exception: pass if 'bssid' in net: diff --git a/simulation/src/wifi_manager/wifi_manager.py b/simulation/src/wifi_manager/wifi_manager.py index 55ad016..a950fa9 100644 --- a/simulation/src/wifi_manager/wifi_manager.py +++ b/simulation/src/wifi_manager/wifi_manager.py @@ -10,7 +10,7 @@ import gc import json from machine import machine -import os +# import os from pathlib import Path import _thread import time @@ -21,7 +21,7 @@ from generic_helper import Message # pip installed packages -from flask import Flask, render_template, abort, request, jsonify +from flask import Flask, render_template, url_for, redirect, request, jsonify, make_response # custom packages from generic_helper import GenericHelper @@ -65,11 +65,12 @@ def __init__(self, logger=None, quiet=False, name=__name__): self._enc_key = (uuid * amount).decode('ascii')[:required_len] self._configured_networks = list() + self._selected_network_bssid = '' # WiFi scan specific defines self._scan_lock = _thread.allocate_lock() self._scan_interval = 5000 # milliseconds - # Queue also works, but not in this case there is no need for a history + # Queue also works, but in this case there is no need for a history self._scan_net_msg = Message() self._scan_net_msg.set([]) # empty list, required by save_wifi_config self._latest_scan = None @@ -120,7 +121,8 @@ def load_and_connect(self) -> bool: # self._configured_networks = list(ssids).copy() # self.logger.debug('All SSIDs: {}'.format(ssids)) self.logger.debug('Config content: {}'.format(loaded_cfg)) - self.logger.debug('Private config content: {}'.format(private_cfg)) + self.logger.debug('Private config content: {}'. + format(private_cfg)) self.logger.debug('Configured networks: {}'. format(self._configured_networks)) @@ -152,8 +154,10 @@ def start_config(self) -> None: def _add_app_routes(self) -> None: """Add all application routes to the webserver.""" self.app.add_url_rule("/", view_func=self.landing_page) - self.app.add_url_rule("/wifi_selection", view_func=self.wifi_selection) - self.app.add_url_rule("/wifi_configs", view_func=self.wifi_configs) + self.app.add_url_rule("/select", view_func=self.wifi_selection) + self.app.add_url_rule("/render_network_inputs", + view_func=self.render_network_inputs) + self.app.add_url_rule("/configure", view_func=self.wifi_configs) self.app.add_url_rule("/save_wifi_config", view_func=self.save_wifi_config, methods=['POST', 'GET']) @@ -162,6 +166,14 @@ def _add_app_routes(self) -> None: methods=['POST', 'GET']) self.app.add_url_rule("/scan_result", view_func=self.scan_result) + self.app.add_url_rule( + "/static/css/bootstrap.min.css", + view_func=self.styles + ) + # regex not supported in Flask + # self.app.add_url_rule(re.compile('^\/(.+\.css)$'), + # view_func=self.styles) + def _encrypt_data(self, data: Union[str, list, dict]) -> bytes: """ Encrypt data with encryption key @@ -270,7 +282,8 @@ def extend_wifi_config_data(self, def _load_wifi_config_data(self, path: str, - encrypted: bool = False) -> Union[dict, List[dict]]: + encrypted: bool = False) -> Union[dict, + List[dict]]: """ Load WiFi configuration data from file. @@ -286,7 +299,8 @@ def _load_wifi_config_data(self, if encrypted: # read file in binary as it contains encrypted data - encrypted_read_data = GenericHelper.load_file(path=path, mode='rb') + encrypted_read_data = GenericHelper.load_file(path=path, + mode='rb') self.logger.debug('Read encrypted data: {}'. format(encrypted_read_data)) @@ -321,7 +335,6 @@ def _scan(self, msg: Message, scan_interval: int, lock: int) -> None: - # lock: lock) -> None: """ Scan for available networks. @@ -334,7 +347,7 @@ def _scan(self, :param scan_interval: The scan interval in milliseconds :type scan_interval: int :param lock: The lock object - :type lock: lock + :type lock: _thread.lock """ pixel.fading = True @@ -369,8 +382,8 @@ def scan_interval(self, value: int) -> None: """ Set the WiFi scan interval in milliseconds. - Values below 1000ms are set to 1000ms. - One scan takes around 3 sec, which leads to maximum 15 scans per minute + Values below 1000 ms are set to 1000 ms. + One scan takes around 3 sec, which leads to maximum 15 scans per min :param value: Interval of WiFi scans in milliseconds :type value: int @@ -423,70 +436,90 @@ def latest_scan(self) -> Union[List[dict], str]: # free = gc.mem_free() # self.logger.debug('Free memory: {}'.format(free)) latest_scan_result = self._scan_net_msg.value() - self.logger.info('Requested latest scan result: {}'.format(latest_scan_result)) + self.logger.info('Requested latest scan result: {}'. + format(latest_scan_result)) return latest_scan_result - # ------------------------------------------------------------------------- - # Webserver functions - - # @app.route('/landing_page') - def landing_page(self): - # return render_template('wifi_select_loader.tpl.html', - return render_template('index.tpl.html') - - # @app.route('/scan_result') - def scan_result(self): - return jsonify(self.latest_scan) - - # @app.route('/wifi_selection') - def wifi_selection(self): - # abort(404) - available_nets = self.latest_scan - # return render_template('wifi_select_loader.tpl.html', - return render_template('wifi_select_loader_bootstrap.tpl.html', - wifi_nets=available_nets) - - # @app.route('/wifi_configs') - def wifi_configs(self): - # abort(404) - configured_nets = self.configured_networks - self.logger.debug('Existing config content: {}'. - format(configured_nets)) - - if isinstance(configured_nets, str): - configured_nets = [configured_nets] - - return render_template('wifi_configs.tpl.html', - wifi_nets=configured_nets) - - # @app.route('/save_wifi_config', methods=['POST', 'GET']) - def save_wifi_config(self): - # abort(404) - if request.method == 'POST': - data = request.form + def _render_network_inputs(self, + available_nets: dict, + selected_bssid: str = '') -> str: + """ + Render HTML list of selectable networks - form_data = dict(request.form) + :param available_nets: All available nets + :type available_nets: dict + :param selected_bssid: Currently selected network on the webpage + :type selected_bssid: str - # print('Posted data in save_wifi_config: {}'.format(data)) - self.logger.info('WiFi user input content: {}'.format(form_data)) - # {'bssid': 'a0f3c1fbfc3c', 'ssid': '', 'password': 'sdsfv'} + :returns: Sub content of WiFi selection page + :rtype: str + """ + content = "" + if len(available_nets): + for ele in available_nets: + selected = '' + if ele['bssid'] == selected_bssid: + selected = "checked" + content += """ + + + """.format(bssid=ele['bssid'], + state=selected, + ssid=ele['ssid'], + quality=ele['quality']) + else: + # as long as no networks are available show a spinner + content = """ +