initial commit

failure
Marek Ventur 2018-07-15 11:53:48 +01:00
commit 6e4e87e6c4
14 changed files with 937 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

67
app_library/main.py Normal file
View File

@ -0,0 +1,67 @@
"""switches between app libraries, updates and installs apps.
To publish apps use https://badge.emfcamp.org"""
___license___ = "MIT"
___name___ = "App Library"
___dependencies___ = ["wifi", "dialogs"]
___bootstrapped___ = True
import pyb
import ugfx
import os
#import http_client
import wifi
import dialogs
#from app import App, get_local_apps, get_public_apps, get_public_app_categories, empty_local_app_cache
#import filesystem
TEMP_FILE = ".temp_download"
ugfx.init()
### VIEWS ###
def clear():
ugfx.clear(ugfx.html_color(0x7c1143))
def store():
None
def update():
None
def remove():
None
def settings():
None
def main_menu():
while True:
clear()
print()
menu_items = [
{"title": "Install Apps", "function": store},
{"title": "Update", "function": update},
{"title": "Manage Apps", "function": remove},
{"title": "Settings", "function": settings}
]
option = dialogs.prompt_option(menu_items, none_text="Exit", text="What do you want to do?", title="TiLDA App Library")
if option:
option["function"]()
else:
return
main_menu()
#if App("home").loadable:
# main_menu()
#else:
# for app_name in ["changename", "snake", "alistair~selectwifi", "sponsors", "home"]:
# install(App(app_name))
# pyb.hard_reset()

29
boot.py Normal file
View File

@ -0,0 +1,29 @@
import pyb, os, micropython
micropython.alloc_emergency_exception_buf(100)
root = os.listdir()
def app(a):
if (a in root) and ("main.py" in os.listdir(a)):
return a + "/main.py"
def file(file, remove):
try:
with open(file, 'r') as f:
a = f.read().strip()
if remove:
os.remove(file)
return app(a)
except Exception as e:
print(e)
def any_home():
return app(next(a for a in root if a.startswith("home")))
start = None
if "main.py" in root:
start = "main.py"
start = file("once.txt", True) or file("default_app.txt", False) or any_home() or "bootstrap.py"
pyb.main(start)

BIN
home_default/bg.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

20
home_default/main.py Normal file
View File

@ -0,0 +1,20 @@
"""Default homescreen
This is the default homescreen for the Tilda Mk4.
It gets automatically installed when a badge is
newly activated or reset.
"""
___name___ = "Homescreen (Default)"
___license___ = "GPL"
___categories___ = ["homescreen"]
___launchable___ = False
___bootstrapped___ = True
print("there")
import ugfx, homescreen
homescreen.init(color = 0xe4ffdb)
ugfx.display_image(0, 0, "home_default/bg.gif")
ugfx.text(20, 20, homescreen.name(), ugfx.BLACK)

9
launcher/main.py Normal file
View File

@ -0,0 +1,9 @@
"""Launcher for apps currently installed"""
___name___ = "Launcher"
___license___ = "GPL"
___categories___ = ["System"]
___launchable___ = False
___bootstrapped___ = True
print("launcher")

179
lib/apps.py Normal file
View File

@ -0,0 +1,179 @@
"""Model and Helpers for TiLDA apps and the App Library API"""
___license___ = "MIT"
___dependencies___ = ["http"]
import os
import ure
import http_client
import filesystem
import gc
ATTRIBUTE_MATCHER = ure.compile("^\s*###\s*([^:]*?)\s*:\s*(.*)\s*$") # Yeah, regex!
CATEGORY_ALL = "all"
CATEGORY_NOT_SET = "uncategorised"
class App:
"""Models an app and provides some helper functions"""
def __init__(self, folder_name, api_information = None):
self.folder_name = self.name = folder_name.lower()
self.user = EMF_USER
if USER_NAME_SEPARATOR in folder_name:
[self.user, self.name] = folder_name.split(USER_NAME_SEPARATOR, 1)
self.user = self.user.lower()
self.name = self.name.lower()
self._attributes = None # Load lazily
self.api_information = api_information
@property
def folder_path(self):
return "apps/" + self.folder_name
@property
def main_path(self):
return self.folder_path + "/main.py"
@property
def loadable(self):
return filesystem.is_file(self.main_path) and os.stat(self.main_path)[6] > 0
@property
def description(self):
"""either returns a local attribute or uses api_information"""
if self.api_information and "description" in self.api_information:
return self.api_information["description"]
return self.get_attribute("description") or ""
@property
def files(self):
"""returns a list of file dicts or returns False if the information is not available"""
if self.api_information and "files" in self.api_information:
return self.api_information["files"]
return False
@property
def category(self):
return self.get_attribute("Category", CATEGORY_NOT_SET).lower()
@property
def title(self):
return self.get_attribute("appname") or self.name
@property
def user_and_title(self):
if self.user == EMF_USER:
return self.name
else:
return "%s by %s" % (self.title, self.user)
def matches_category(self, category):
"""returns True if provided category matches the category of this app"""
category = category.lower()
return category == CATEGORY_ALL or category == self.category
@property
def attributes(self):
"""Returns all attribues of this app
The result is cached for the lifetime of this object
"""
if self._attributes == None:
self._attributes = {}
if self.loadable:
with open(self.main_path) as file:
for line in file:
match = ATTRIBUTE_MATCHER.match(line)
if match:
self._attributes[match.group(1).strip().lower()] = match.group(2).strip()
else:
break
return self._attributes
def get_attribute(self, attribute, default=None):
"""Returns the value of an attribute, or a specific default value if attribute is not found"""
attribute = attribute.lower() # attributes are case insensitive
if attribute in self.attributes:
return self.attributes[attribute]
else:
return default
def fetch_api_information(self):
"""Queries the API for information about this app, returns False if app is not publicly listed"""
with http_client.get("http://api.badge.emfcamp.org/api/app/%s/%s" % (self.user, self.name)) as response:
if response.status == 404:
return False
self.api_information = response.raise_for_status().json()
return self.api_information
def __str__(self):
return self.user_and_title
def __repr__(self):
return "<App %s>" % (self.folder_name)
def app_by_name_and_user(name, user):
"""Returns an user object"""
if user.lower() == EMF_USER:
return App(name)
else:
return App(user + USER_NAME_SEPARATOR + name)
def app_by_api_response(response):
if response["user"].lower() == EMF_USER:
return App(response["name"], response)
else:
return App(response["user"] + USER_NAME_SEPARATOR + response["name"], response)
def get_local_apps(category=CATEGORY_ALL):
"""Returns a list of apps that can be found in the apps folder"""
apps = [App(folder_name) for folder_name in os.listdir("apps") if filesystem.is_dir("apps/" + folder_name)]
return [app for app in apps if app.matches_category(category)]
_public_apps_cache = None
def fetch_public_app_api_information(uncached=False):
"""Returns a dict category => list of apps
Uses cached version unless the uncached parameter is set
"""
global _public_apps_cache
if not _public_apps_cache or uncached:
response = {}
for category, apps in http_client.get("http://api.badge.emfcamp.org/api/apps").raise_for_status().json().items():
response[category] = [app_by_api_response(app) for app in apps]
_public_apps_cache = response
return _public_apps_cache
def get_public_app_categories(uncached=False):
"""Returns a list of all categories used on the app library"""
return list(fetch_public_app_api_information(uncached).keys())
def get_public_apps(category=CATEGORY_ALL, uncached=False):
"""Returns a list of all public apps in one category"""
category = category.lower()
api_information = fetch_public_app_api_information(uncached)
return api_information[category] if category in api_information else []
_category_cache = None
def get_local_app_categories(uncached=False):
"""Returns a list of all app categories the user's apps are currently using
Uses cached version unless the uncached parameter is set
"""
global _category_cache
if not _category_cache or uncached:
_category_cache = ["all"]
for app in get_local_apps():
if app.category not in _category_cache:
_category_cache.append(app.category)
return _category_cache
def empty_local_app_cache():
"""If you're tight on memory you can clean up the local cache"""
global _public_apps_cache, _category_cache
_public_apps_cache = None
_category_cache = None
gc.collect()

127
lib/buttons.py Normal file
View File

@ -0,0 +1,127 @@
"""Convenience methods for dealing with the TiLDA buttons"""
___license___ = "MIT"
import pyb
CONFIG = {
"JOY_UP": pyb.Pin.PULL_DOWN,
"JOY_DOWN": pyb.Pin.PULL_DOWN,
"JOY_RIGHT": pyb.Pin.PULL_DOWN,
"JOY_LEFT": pyb.Pin.PULL_DOWN,
"JOY_CENTER": pyb.Pin.PULL_DOWN,
"BTN_MENU": pyb.Pin.PULL_UP,
"BTN_A": pyb.Pin.PULL_UP,
"BTN_B": pyb.Pin.PULL_UP
}
_tilda_pins = {}
_tilda_interrupts = {}
_tilda_bounce = {}
def _get_pin(button):
if button not in _tilda_pins:
raise ValueError("Please call button.init() first before using any other button functions")
return _tilda_pins[button]
def init(buttons = CONFIG.keys()):
"""Inits all pins used by the TiLDA badge"""
global _tilda_pins
for button in buttons:
_tilda_pins[button] = pyb.Pin(button, pyb.Pin.IN)
_tilda_pins[button].init(pyb.Pin.IN, CONFIG[button])
def is_pressed(button):
pin = _get_pin(button)
if pin.pull() == pyb.Pin.PULL_DOWN:
return pin.value() > 0
else:
return pin.value() == 0
def is_triggered(button, interval = 30):
"""Use this function if you want buttons as a trigger for something in a loop
It blocks for a while before returning a True and ignores trailing edge highs
for a certain time to filter out bounce on both edges
"""
global _tilda_bounce
if is_pressed(button):
if button in _tilda_bounce:
if pyb.millis() > _tilda_bounce[button]:
del _tilda_bounce[button]
else:
return False # The button might have bounced back to high
# Wait for a while to avoid bounces to low
pyb.delay(interval)
# Wait until button is released again
while is_pressed(button):
pyb.wfi()
_tilda_bounce[button] = pyb.millis() + interval
return True
def has_interrupt(button):
global _tilda_interrupts
_get_pin(button)
if button in _tilda_interrupts:
return True
else:
return False
def enable_interrupt(button, interrupt, on_press = True, on_release = False):
"""Attaches an interrupt to a button
on_press defines whether it should be called when the button is pressed
on_release defines whether it should be called when the button is releaseed
The callback function must accept exactly 1 argument, which is the line that
triggered the interrupt.
"""
global _tilda_interrupts
pin = _get_pin(button)
if button in _tilda_interrupts:
# If someone tries to set an interrupt on a pin that already
# has one that's totally ok, but we need to remove the old one
# first
disable_interrupt(button)
if not (on_press or on_release):
return
mode = None;
if on_press and on_release:
mode = pyb.ExtInt.IRQ_RISING_FALLING
else:
if pin.pull() == pyb.Pin.PULL_DOWN:
mode = pyb.ExtInt.IRQ_RISING if on_press else pyb.ExtInt.IRQ_FALLING
else:
mode = pyb.ExtInt.IRQ_FALLING if on_press else pyb.ExtInt.IRQ_RISING
_tilda_interrupts[button] = {
"interrupt": pyb.ExtInt(pin, mode, pin.pull(), interrupt),
"mode": mode,
"pin": pin
}
def disable_interrupt(button):
global _tilda_interrupts
if button in _tilda_interrupts:
interrupt = _tilda_interrupts[button]
pyb.ExtInt(interrupt["pin"], interrupt["mode"], interrupt["pin"].pull(), None)
del _tilda_interrupts[button]
init([button])
def disable_all_interrupt():
for interrupt in _tilda_interrupts:
disable_interrupt(interrupt)
def enable_menu_reset():
import onboard
enable_interrupt("BTN_MENU", lambda t:onboard.semihard_reset(), on_release = True)
def disable_menu_reset():
disable_interrupt("BTN_MENU")

88
lib/database.py Normal file
View File

@ -0,0 +1,88 @@
"""A simple key/value store backed by a json file
Keys need to be convertable to str
Values can be anything json can store, including a dict
Usage:
import database
with database.open() as db:
print(db.get("hello", "default"))
db.set("foo", "world")
db.delete("bar")
Or, to make things even easier, there are three static function:
import database
print(database.get("hello", "default"))
database.set("foo", "world")
database.delete("bar")
"""
___license___ = "MIT"
import os, json
class Database:
def __init__(self, filename = "config.json"):
self.filename = filename
self.dirty = False
try:
with open(filename, "rt") as file:
self.data = json.loads(file.read())
except (OSError, ValueError):
print("Database %s doesn't exists or is invalid, creating new" % (filename))
self.data = {}
self.dirty = True
self.flush()
def set(self, key, value):
"""Sets a value for a given key.
'key' gets converted into a string
'value' can be anything that json can store, including a dict
"""
self.data[key] = value
self.dirty = True
def get(self, key, default_value = None):
"""Returns the value for a given key.
If key is not found 'default_value' will be returned
"""
return self.data[key] if key in self.data else default_value
def delete(self, key):
"""Deletes a key/value pair"""
if key in self.data:
del self.data[key]
self.dirty = True
def flush(self):
"""Writes changes to flash"""
if self.dirty:
with open(self.filename, "wt") as file:
file.write(json.dumps(self.data))
file.flush()
os.sync()
self.dirty = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.flush()
def get(key, default_value = None, *args):
with Database(*args) as db:
return db.get(key, default_value)
def set(key, value, *args):
with Database(*args) as db:
return db.set(key, value)
def delete(key, *args):
with Database(*args) as db:
return db.delete(key)

207
lib/dialogs.py Normal file
View File

@ -0,0 +1,207 @@
"""Some basic UGFX powered dialogs"""
___license___ = "MIT"
___dependencies___ = ["buttons"]
import ugfx
import buttons
import pyb
default_style_badge = ugfx.Style()
default_style_badge.set_focus(ugfx.RED)
default_style_badge.set_enabled([ugfx.WHITE, ugfx.html_color(0x3C0246), ugfx.GREY, ugfx.RED])
default_style_badge.set_background(ugfx.html_color(0x3C0246))
default_style_dialog = ugfx.Style()
default_style_dialog.set_enabled([ugfx.BLACK, ugfx.html_color(0xA66FB0), ugfx.html_color(0xdedede), ugfx.RED])
default_style_dialog.set_background(ugfx.html_color(0xFFFFFF))
TILDA_COLOR = ugfx.html_color(0x7c1143);
def notice(text, title="TiLDA", close_text="Close", width = 260, height = 180, font=ugfx.FONT_SMALL, style=None):
prompt_boolean(text, title = title, true_text = close_text, false_text = None, width = width, height = height, font=font, style=style)
def prompt_boolean(text, title="TiLDA", true_text="Yes", false_text="No", width = 260, height = 180, font=ugfx.FONT_SMALL, style=None):
"""A simple one and two-options dialog
if 'false_text' is set to None only one button is displayed.
If both 'true_text' and 'false_text' are given a boolean is returned
"""
global default_style_dialog
if style == None:
style = default_style_dialog
ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD)
window = ugfx.Container((ugfx.width() - width) // 2, (ugfx.height() - height) // 2, width, height, style=style)
window.show()
ugfx.set_default_font(font)
window.text(5, 10, title, TILDA_COLOR)
window.line(0, 30, width, 30, ugfx.BLACK)
if false_text:
true_text = "A: " + true_text
false_text = "B: " + false_text
ugfx.set_default_font(font)
label = ugfx.Label(5, 30, width - 10, height - 80, text = text, parent=window)
ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD)
button_yes = ugfx.Button(5, height - 40, width // 2 - 15 if false_text else width - 15, 30 , true_text, parent=window)
button_no = ugfx.Button(width // 2 + 5, height - 40, width // 2 - 15, 30 , false_text, parent=window) if false_text else None
try:
buttons.init()
button_yes.attach_input(ugfx.BTN_A,0)
if button_no: button_no.attach_input(ugfx.BTN_B,0)
window.show()
while True:
pyb.wfi()
if buttons.is_triggered("BTN_A"): return True
if buttons.is_triggered("BTN_B"): return False
finally:
window.hide()
window.destroy()
button_yes.destroy()
if button_no: button_no.destroy()
label.destroy()
def prompt_text(description, init_text = "", true_text="OK", false_text="Back", width = 300, height = 200, font=ugfx.FONT_MEDIUM_BOLD, style=default_style_badge):
"""Shows a dialog and keyboard that allows the user to input/change a string
Returns None if user aborts with button B
"""
window = ugfx.Container(int((ugfx.width()-width)/2), int((ugfx.height()-height)/2), width, height, style=style)
if false_text:
true_text = "M: " + true_text
false_text = "B: " + false_text
if buttons.has_interrupt("BTN_MENU"):
buttons.disable_interrupt("BTN_MENU")
ugfx.set_default_font(ugfx.FONT_MEDIUM)
kb = ugfx.Keyboard(0, int(height/2), width, int(height/2), parent=window)
edit = ugfx.Textbox(5, int(height/2)-30, int(width*4/5)-10, 25, text = init_text, parent=window)
ugfx.set_default_font(ugfx.FONT_SMALL)
button_yes = ugfx.Button(int(width*4/5), int(height/2)-30, int(width*1/5)-3, 25 , true_text, parent=window)
button_no = ugfx.Button(int(width*4/5), int(height/2)-30-30, int(width/5)-3, 25 , false_text, parent=window) if false_text else None
ugfx.set_default_font(font)
label = ugfx.Label(int(width/10), int(height/10), int(width*4/5), int(height*2/5)-60, description, parent=window)
try:
buttons.init()
button_yes.attach_input(ugfx.BTN_MENU,0)
if button_no: button_no.attach_input(ugfx.BTN_B,0)
window.show()
edit.set_focus()
while True:
pyb.wfi()
ugfx.poll()
#if buttons.is_triggered("BTN_A"): return edit.text()
if buttons.is_triggered("BTN_B"): return None
if buttons.is_triggered("BTN_MENU"): return edit.text()
finally:
window.hide()
window.destroy()
button_yes.destroy()
if button_no: button_no.destroy()
label.destroy()
kb.destroy()
edit.destroy();
return
def prompt_option(options, index=0, text = "Please select one of the following:", title=None, select_text="OK", none_text=None):
"""Shows a dialog prompting for one of multiple options
If none_text is specified the user can use the B or Menu button to skip the selection
if title is specified a blue title will be displayed about the text
"""
ugfx.set_default_font(ugfx.FONT_SMALL)
window = ugfx.Container(5, 5, ugfx.width() - 10, ugfx.height() - 10)
window.show()
list_y = 30
if title:
window.text(5, 10, title, TILDA_COLOR)
window.line(0, 25, ugfx.width() - 10, 25, ugfx.BLACK)
window.text(5, 30, text, ugfx.BLACK)
list_y = 50
else:
window.text(5, 10, text, ugfx.BLACK)
options_list = ugfx.List(5, list_y, ugfx.width() - 25, 180 - list_y, parent = window)
for option in options:
if isinstance(option, dict) and option["title"]:
options_list.add_item(option["title"])
else:
options_list.add_item(str(option))
options_list.selected_index(index)
select_text = "A: " + select_text
if none_text:
none_text = "B: " + none_text
button_select = ugfx.Button(5, ugfx.height() - 50, 140 if none_text else ugfx.width() - 25, 30 , select_text, parent=window)
button_none = ugfx.Button(ugfx.width() - 160, ugfx.height() - 50, 140, 30 , none_text, parent=window) if none_text else None
try:
buttons.init()
while True:
pyb.wfi()
ugfx.poll()
if buttons.is_triggered("BTN_A"): return options[options_list.selected_index()]
if button_none and buttons.is_triggered("BTN_B"): return None
if button_none and buttons.is_triggered("BTN_MENU"): return None
finally:
window.hide()
window.destroy()
options_list.destroy()
button_select.destroy()
if button_none: button_none.destroy()
ugfx.poll()
class WaitingMessage:
"""Shows a dialog with a certain message that can not be dismissed by the user"""
def __init__(self, text = "Please Wait...", title="TiLDA"):
self.window = ugfx.Container(30, 30, ugfx.width() - 60, ugfx.height() - 60)
self.window.show()
self.window.text(5, 10, title, TILDA_COLOR)
self.window.line(0, 30, ugfx.width() - 60, 30, ugfx.BLACK)
self.label = ugfx.Label(5, 40, self.window.width() - 10, ugfx.height() - 40, text = text, parent=self.window)
# Indicator to show something is going on
self.indicator = ugfx.Label(ugfx.width() - 100, 0, 20, 20, text = "...", parent=self.window)
self.timer = pyb.Timer(3)
self.timer.init(freq=3)
self.timer.callback(lambda t: self.indicator.visible(not self.indicator.visible()))
def destroy(self):
self.timer.deinit()
self.label.destroy()
self.indicator.destroy()
self.window.destroy()
@property
def text(self):
return self.label.text()
@text.setter
def text(self, value):
self.label.text(value)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.destroy()

45
lib/homescreen.py Normal file
View File

@ -0,0 +1,45 @@
"""Shared functionality for home screen apps
Apps in the "homescreen" should behave in a similar manner to not confuse users.
In particular, they *should*:
* Call "homescreen.init()" at the beginning. This will initiate ugfx, clear the screen and
initiate button handline.
* Use "pyb.wfi()" as much as possible to avoid draining the battery.
* Not use
They also *may*:
* Display a name, returned by "homescreen.name()"
* Display network strength "homescreen.mobile_strength()" (0-1, might return "None" if no SIM card found)
* Display network strength "homescreen.wifi_strength()" (0-1, might return "None" if not connected)
* Display remaining battery "homescreen.battery()" (0-1)
"""
__license___ = "MIT"
__dependencies___ = ["database", "buttons"]
import database, ugfx, buttons
def init(color = 0xFFFFFF):
ugfx.init()
ugfx.clear(ugfx.html_color(color))
buttons.init()
#buttons.enable_interrupt()
def menu():
ugfx.clear()
def name():
return database.get("homescreen.name", "Marek")
def mobile_strength():
return 0.75
def wifi_strength():
return 0.65
def battery():
return 0.65

3
lib/http.py Normal file
View File

@ -0,0 +1,3 @@
"""HTTP library specially tied to TiLDAs functionality"""
___license___ = "MIT"

153
lib/wifi.py Normal file
View File

@ -0,0 +1,153 @@
"""Handles connecting to a wifi access point based on a valid wifi.json file"""
___license___ = "MIT"
___dependencies___ = ["dialogs"]
import network
import os
import json
import pyb
import dialogs
_nic = None
def nic():
global _nic
if not _nic:
_nic = network.CC3100()
return _nic
def connection_details():
data = None
try:
if "wifi.json" in os.listdir():
with open("wifi.json") as f:
data = json.loads(f.read())
if 'ssid' not in data or not data['ssid']:
data = None
except ValueError as e:
print(e)
return data
def ssid():
return connection_details()["ssid"]
def connect(wait=True, timeout=10, show_wait_message=False, prompt_on_fail=True, dialog_title='TiLDA'):
retry_connect = True
while retry_connect:
if nic().is_connected():
return
details = connection_details()
if not details:
if prompt_on_fail:
choose_wifi(dialog_title=dialog_title)
else:
raise OSError("No valid wifi configuration")
if not wait:
connect_wifi(details, timeout=None, wait=False)
return
else:
try:
if show_wait_message:
with dialogs.WaitingMessage(text="Connecting to '%s'...\n(%ss timeout)" % (details['ssid'], timeout), title=dialog_title):
connect_wifi(details, timeout=timeout, wait=True)
else:
connect_wifi(details, timeout=timeout, wait=True)
except OSError:
if prompt_on_fail:
retry_connect = dialogs.prompt_boolean(
text="Failed to connect to '%s'" % details['ssid'],
title=dialog_title,
true_text="Try again",
false_text="Forget it",
)
if not retry_connect:
os.remove('wifi.json')
os.sync()
# We would rather let you choose a new network here, but
# scanning doesn't work after a connect at the moment
pyb.hard_reset()
else:
raise
def connect_wifi(details, timeout, wait=False):
if 'pw' in details:
nic().connect(details['ssid'], details['pw'], timeout=timeout)
else:
nic().connect(details['ssid'], timeout=timeout)
if wait:
while not nic().is_connected():
nic().update()
pyb.delay(100)
def is_connected():
return nic().is_connected()
def get_security_level(ap):
n = nic()
levels = {}
try:
levels = {
n.SCAN_SEC_OPEN: 0, # I am awful
n.SCAN_SEC_WEP: 'WEP',
n.SCAN_SEC_WPA: 'WPA',
n.SCAN_SEC_WPA2: 'WPA2',
}
except AttributeError:
print("Firmware too old to query wifi security level, please upgrade.")
return None
return levels.get(ap.get('security', None), None)
def choose_wifi(dialog_title='TiLDA'):
filtered_aps = []
with dialogs.WaitingMessage(text='Scanning for networks...', title=dialog_title):
visible_aps = nic().list_aps()
visible_aps.sort(key=lambda x:x['rssi'], reverse=True)
# We'll get one result for each AP, so filter dupes
for ap in visible_aps:
title = ap['ssid']
security = get_security_level(ap)
if security:
title = title + ' (%s)' % security
ap = {
'title': title,
'ssid': ap['ssid'],
'security': security,
}
if ap['ssid'] not in [ a['ssid'] for a in filtered_aps ]:
filtered_aps.append(ap)
del visible_aps
ap = dialogs.prompt_option(
filtered_aps,
text='Choose wifi network',
title=dialog_title
)
if ap:
key = None
if ap['security'] != 0:
# Backward compat
if ap['security'] == None:
ap['security'] = 'wifi'
key = dialogs.prompt_text(
"Enter %s key" % ap['security'],
width = 310,
height = 220
)
with open("wifi.json", "wt") as file:
if key:
conn_details = {"ssid": ap['ssid'], "pw": key}
else:
conn_details = {"ssid": ap['ssid']}
file.write(json.dumps(conn_details))
os.sync()
# We can't connect after scanning for some bizarre reason, so we reset instead
pyb.hard_reset()

9
settings/main.py Normal file
View File

@ -0,0 +1,9 @@
"""Generic setting used by different apps"""
___name___ = "Settings"
___license___ = "GPL"
___categories___ = ["System"]
___launchable___ = True
___bootstrapped___ = True
print("settings")