Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • flow3r/flow3r-firmware
  • Vespasian/flow3r-firmware
  • alxndr42/flow3r-firmware
  • pl/flow3r-firmware
  • Kari/flow3r-firmware
  • raimue/flow3r-firmware
  • grandchild/flow3r-firmware
  • mu5tach3/flow3r-firmware
  • Nervengift/flow3r-firmware
  • arachnist/flow3r-firmware
  • TheNewCivilian/flow3r-firmware
  • alibi/flow3r-firmware
  • manuel_v/flow3r-firmware
  • xeniter/flow3r-firmware
  • maxbachmann/flow3r-firmware
  • yGifoom/flow3r-firmware
  • istobic/flow3r-firmware
  • EiNSTeiN_/flow3r-firmware
  • gnudalf/flow3r-firmware
  • 999eagle/flow3r-firmware
  • toerb/flow3r-firmware
  • pandark/flow3r-firmware
  • teal/flow3r-firmware
  • x42/flow3r-firmware
  • alufers/flow3r-firmware
  • dos/flow3r-firmware
  • yrlf/flow3r-firmware
  • LuKaRo/flow3r-firmware
  • ThomasElRubio/flow3r-firmware
  • ai/flow3r-firmware
  • T_X/flow3r-firmware
  • highTower/flow3r-firmware
  • beanieboi/flow3r-firmware
  • Woazboat/flow3r-firmware
  • gooniesbro/flow3r-firmware
  • marvino/flow3r-firmware
  • kressnerd/flow3r-firmware
  • quazgar/flow3r-firmware
  • aoid/flow3r-firmware
  • jkj/flow3r-firmware
  • naomi/flow3r-firmware
41 results
Show changes
Commits on Source (18)
......@@ -6,8 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [1.1.1] - 2023-08-17
## [1.2.0] - 2023-08-18
### Added
- Added a WiFi status indicator icon.
- Added a battery status indicator icon.
- Added support for loading apps from `/sd/apps` and `/flash/apps`.
- Added an error screen when apps fail to start.
- Added Python/st3m API to access battery charge status.
- Added the ability to always hide icons (*System**Settings**Show Icons*).
### Fixed
- File descriptor leak on app load. This would lead to the OS crashing when
too many apps are installed.
## [1.1.1] - 2023-08-17
### Fixed
- Crash on WiFi startup
......@@ -47,7 +61,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Initial Release
[unreleased]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/compare/v1.1.0...main
[unreleased]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/compare/v1.2.0...main
[1.2.0]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/compare/v1.1.1...v1.2.0
[1.1.1]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/compare/v1.1.0...v1.1.1
[1.1.0]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/compare/v1.0.0...v1.1.0
[1.0.0]: https://git.flow3r.garden/flow3r/flow3r-firmware/-/tags/v1.0.0
......
......@@ -8,6 +8,7 @@
#include "py/obj.h"
#include "py/runtime.h"
#include "st3m_console.h"
#include "st3m_io.h"
#include "st3m_usb.h"
#include "st3m_version.h"
......@@ -372,6 +373,13 @@ STATIC mp_obj_t mp_i2c_scan(void) {
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_i2c_scan_obj, mp_i2c_scan);
STATIC mp_obj_t mp_battery_charging(void) {
bool res = st3m_io_charger_state_get();
return mp_obj_new_bool(!res);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_battery_charging_obj, mp_battery_charging);
STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_scheduler_snapshot),
MP_ROM_PTR(&mp_scheduler_snapshot_obj) },
......@@ -385,6 +393,8 @@ STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_usb_console_active),
MP_ROM_PTR(&mp_usb_console_active_obj) },
{ MP_ROM_QSTR(MP_QSTR_i2c_scan), MP_ROM_PTR(&mp_i2c_scan_obj) },
{ MP_ROM_QSTR(MP_QSTR_battery_charging),
MP_ROM_PTR(&mp_battery_charging_obj) },
{ MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(eRunning) },
{ MP_ROM_QSTR(MP_QSTR_READY), MP_ROM_INT(eReady) },
......
......@@ -58,3 +58,7 @@ uint8_t st3m_io_badge_link_disable(uint8_t pin_mask);
* a while. Warn user.
*/
uint8_t st3m_io_badge_link_enable(uint8_t pin_mask);
/* Returns true if the battery is currently being charged.
*/
bool st3m_io_charger_state_get();
``captouch`` module
===================
.. note::
Because of a driver quirk, the top pad of the petal 5 is currently not working,
resulting in that petal only reporting half of its supposed range.
.. automodule:: captouch
:members:
:undoc-members:
......
......@@ -597,9 +597,22 @@ Together with the Python code this file forms a so called bundle
Save this as `flow3r.toml` together with the Python code as `__init__.py` in a
folder (name doesn't matter) and put that folder into the ``/flash/sys/apps``
folder on your flow3r (visible as ``sys/apps`` in `Disk Mode`_). Restart the
flow3r and it should pick up your new application.
folder (name doesn't matter) and put that folder into one of the possible
application directories (see below) using `Disk Mode`_. Restart the flow3r and
it should pick up your new application.
+--------+----------------------+---------------------+---------------------------------------+
| Medium | Path in Disk Mode | Path on Badge | Notes |
+========+======================+=====================+=======================================+
| Flash | ``sys/apps`` | ``/flash/sys/apps`` | “Default” apps. |
+--------+----------------------+---------------------+---------------------------------------+
| Flash | ``apps`` | ``/flash/apps`` | Doesn't exist by default. Split |
| | | | from ``sys`` to allow for cleaner |
| | | | updates. |
+--------+----------------------+---------------------+---------------------------------------+
| SD | ``apps`` | ``/sd/apps`` | Doesn't exist by default. Will be |
| | | | retained even across badge reflashes. |
+--------+----------------------+---------------------+---------------------------------------+
Distributing applications
-------------------------
......
from st3m.application import Application, ApplicationContext
from st3m.ui.colours import PUSH_RED, GO_GREEN, BLACK
from st3m.goose import Dict, Any, Tuple
from st3m.goose import Tuple, Any
from st3m.input import InputState
from ctx import Context
import leds
import json
import math
CONFIG_SCHEMA: dict[str, dict[str, Any]] = {
"name": {"types": [str]},
"size": {"types": [int, float], "cast_to": int},
"font": {"types": [int, float], "cast_to": int},
"pronouns": {"types": ["list_of_str"]},
"pronouns_size": {"types": [int, float], "cast_to": int},
"color": {"types": ["hex_color"]},
"mode": {"types": [int, float], "cast_to": int},
}
class Configuration:
def __init__(self) -> None:
......@@ -17,6 +26,7 @@ class Configuration:
self.pronouns_size: int = 25
self.color = "0x40ff22"
self.mode = 0
self.config_errors: list[str] = []
@classmethod
def load(cls, path: str) -> "Configuration":
......@@ -27,39 +37,52 @@ class Configuration:
data = json.loads(jsondata)
except OSError:
data = {}
if "name" in data and type(data["name"]) == str:
res.name = data["name"]
if "size" in data:
if type(data["size"]) == float:
res.size = int(data["size"])
if type(data["size"]) == int:
res.size = data["size"]
if "font" in data and type(data["font"]) == int:
res.font = data["font"]
# type checks don't look inside collections
if (
"pronouns" in data
and type(data["pronouns"]) == list
and set([type(x) for x in data["pronouns"]]) == {str}
):
res.pronouns = data["pronouns"]
if "pronouns_size" in data:
if type(data["pronouns_size"]) == float:
res.pronouns_size = int(data["pronouns_size"])
if type(data["pronouns_size"]) == int:
res.pronouns_size = data["pronouns_size"]
if (
"color" in data
and type(data["color"]) == str
and data["color"][0:2] == "0x"
and len(data["color"]) == 8
):
res.color = data["color"]
if "mode" in data:
if type(data["mode"]) == float:
res.mode = int(data["mode"])
if type(data["mode"]) == int:
res.mode = data["mode"]
except ValueError:
res.config_errors = ["nick.json decode failed!"]
data = {}
# verify the config format and generate an error message
config_type_errors: list[str] = []
for config_key, type_data in CONFIG_SCHEMA.items():
if config_key not in data.keys():
continue
key_type_valid = False
for allowed_type in type_data["types"]:
if isinstance(allowed_type, type):
if isinstance(data[config_key], allowed_type):
key_type_valid = True
break
elif allowed_type == "list_of_str":
if isinstance(data[config_key], list) and (
len(data[config_key]) == 0
or set([type(x) for x in data[config_key]]) == {str}
):
key_type_valid = True
break
elif allowed_type == "hex_color":
if (
isinstance(data[config_key], str)
and data[config_key][0:2] == "0x"
and len(data[config_key]) == 8
):
key_type_valid = True
break
if not key_type_valid:
config_type_errors.append(config_key)
else:
# Cast to relevant type if needed
if type_data.get("cast_to"):
data[config_key] = type_data["cast_to"](data[config_key])
setattr(res, config_key, data[config_key])
if config_type_errors:
res.config_errors += [
"data types wrong",
"in nick.json for:",
"",
] + config_type_errors
return res
def save(self, path: str) -> None:
......@@ -106,6 +129,19 @@ class NickApp(Application):
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
ctx.rgb(*self._config.to_normalized_tuple())
if self._config.config_errors:
draw_y = (-20 * len(self._config.config_errors)) / 2
ctx.move_to(0, draw_y)
ctx.font_size = 20
# 0xff4500, red
ctx.rgb(1, 0.41, 0)
ctx.font = ctx.get_font_name(8)
for config_error in self._config.config_errors:
draw_y += 20
ctx.move_to(0, draw_y)
ctx.text(config_error)
return
ctx.move_to(0, 0)
ctx.save()
if self._config.mode == 0:
......@@ -132,7 +168,8 @@ class NickApp(Application):
# ctx.fill()
def on_exit(self) -> None:
self._config.save(self._filename)
if not self._config.config_errors:
self._config.save(self._filename)
def think(self, ins: InputState, delta_ms: int) -> None:
super().think(ins, delta_ms)
......
......@@ -89,12 +89,12 @@ class CaptouchState(Protocol):
"""
State of individual petals.
Contains 10 elements, with the zeroth element being the pad closest to
the USB port. Then, every other pad in a clockwise direction.
Contains 10 elements, with the zeroth element being the petal closest to
the USB port. Then, every other petal in a clockwise direction.
Pads 0, 2, 4, 6, 8 are Top pads.
Petals 0, 2, 4, 6, 8 are Top petals.
Pads 1, 3, 5, 7, 9 are Bottom pads.
Petals 1, 3, 5, 7, 9 are Bottom petals.
"""
...
......
......@@ -71,3 +71,4 @@ def usb_console_active() -> bool: ...
def hardware_version() -> str: ...
def firmware_version() -> str: ...
def i2c_scan() -> list[int]: ...
def battery_charging() -> bool: ...
......@@ -6,14 +6,16 @@ from st3m.ui.view import (
)
from st3m.ui.menu import MenuItem
from st3m.input import InputState
from st3m.goose import Optional, List, Enum
from st3m.goose import Optional, List, Enum, Dict
from st3m.logging import Log
from ctx import Context
import toml
import os
import os.path
import stat
import sys
import random
log = Log(__name__)
......@@ -52,6 +54,7 @@ class BundleLoadException(BaseException):
res = self.MSG
if msg is not None:
res += ": " + msg
self.msg = res
super().__init__(res)
......@@ -97,7 +100,7 @@ class BundleMetadata:
This data is used to discover bundles and load them as applications.
"""
__slots__ = ["path", "name", "menu", "_t"]
__slots__ = ["path", "name", "menu", "_t", "version"]
def __init__(self, path: str) -> None:
self.path = path.rstrip("/")
......@@ -109,9 +112,12 @@ class BundleMetadata:
try:
t = toml.load(f)
except toml.TomlDecodeError as e:
f.close()
raise BundleMetadataCorrupt(str(e))
except Exception as e:
f.close()
raise BundleMetadataCorrupt(str(e))
f.close()
if "app" not in t or type(t["app"]) != dict:
raise BundleMetadataBroken("missing app section")
......@@ -126,6 +132,11 @@ class BundleMetadata:
if self.menu not in ["Apps", "Music", "Badge", "Hidden"]:
raise BundleMetadataBroken("app.menu must be either Apps, Music or Badge")
version = 0
if t.get("metadata") is not None:
version = t["metadata"].get("version", 0)
self.version = version
self._t = t
@staticmethod
......@@ -193,8 +204,74 @@ class BundleMetadata:
return []
return [MenuItemAppLaunch(self)]
@property
def source(self) -> str:
return os.path.dirname(self.path)
@property
def id(self) -> str:
return os.path.basename(self.path)
def __repr__(self) -> str:
return f"<BundleMetadata: {self.name} at {self.path}>"
return f"<BundleMetadata: {self.id} at {self.path}>"
class LoadErrorView(BaseView):
def __init__(self, e: BundleLoadException) -> None:
super().__init__()
self.e = e
self.header = "oh no"
def on_enter(self, vm: Optional[ViewManager]) -> None:
self.header = random.choice(
[
"oh no",
"aw shucks",
"whoopsie",
"ruh-roh",
"aw crud",
]
)
def think(self, ins: InputState, delta_ms: int) -> None:
pass
def draw(self, ctx: Context) -> None:
ctx.rgb(0.8, 0.1, 0.1)
ctx.rectangle(-120, -120, 240, 240)
ctx.fill()
ctx.gray(1)
ctx.font_size = 20
ctx.font = "Camp Font 1"
ctx.text_align = ctx.MIDDLE
ctx.move_to(0, -70)
ctx.text(self.header)
lines: List[List[str]] = []
msg = self.e.msg
for word in msg.split():
if len(lines) == 0:
lines.append([word])
continue
lastline = lines[-1][:]
lastline.append(word)
if sum(len(l) for l in lastline) + len(lastline) - 1 > 30:
lines.append([word])
else:
lines[-1].append(word)
ctx.gray(0)
ctx.rectangle(-120, -60, 240, 240).fill()
y = -40
ctx.gray(1)
ctx.font_size = 15
ctx.font = "Arimo Regular"
ctx.text_align = ctx.LEFT
for line in lines:
ctx.move_to(-90, y)
ctx.text(" ".join(line))
y += 15
class MenuItemAppLaunch(MenuItem):
......@@ -218,6 +295,8 @@ class MenuItemAppLaunch(MenuItem):
self._instance = self._bundle.load()
except BundleLoadException as e:
log.error(f"Could not load {self.label()}: {e}")
err = LoadErrorView(e)
vm.push(err)
return
assert self._instance is not None
vm.push(self._instance, ViewTransitionSwipeLeft())
......@@ -226,6 +305,80 @@ class MenuItemAppLaunch(MenuItem):
return self._bundle.name
class BundleManager:
"""
The BundleManager maintains information about BundleMetadata at different
locations in the badge filesystem.
It also manages updating/reloading bundles.
"""
def __init__(self) -> None:
self.bundles: Dict[str, BundleMetadata] = {}
@staticmethod
def _source_trumps(a: str, b: str) -> bool:
prios = {
"/flash/sys/apps": 200,
"/sd/apps": 120,
"/flash/apps": 100,
}
prio_a = prios.get(a, 0)
prio_b = prios.get(b, 0)
return prio_a > prio_b
def _discover_at(self, path: str) -> None:
path = path.rstrip("/")
try:
l = os.listdir(path)
except Exception as e:
log.warning(f"Could not discover bundles in {path}: {e}")
l = []
for d in l:
dirpath = path + "/" + d
st = os.stat(dirpath)
if not stat.S_ISDIR(st[0]):
continue
tomlpath = dirpath + "/flow3r.toml"
try:
st = os.stat(tomlpath)
if not stat.S_ISREG(st[0]):
continue
except Exception:
continue
try:
b = BundleMetadata(dirpath)
except BundleLoadException as e:
log.error(f"Failed to bundle from {dirpath}: {e}")
continue
id_ = b.id
if id_ not in self.bundles:
self.bundles[id_] = b
continue
ex = self.bundles[id_]
# Do we have a newer version?
if b.version > ex.version:
self.bundles[id_] = b
continue
# Do we have a higher priority source?
if self._source_trumps(b.source, ex.source):
self.bundles[id_] = b
continue
log.warning(
f"Ignoring {id_} at {b.source} as it already exists at {ex.source}"
)
def update(self) -> None:
self._discover_at("/flash/sys/apps")
self._discover_at("/flash/apps")
self._discover_at("/sd/apps")
def discover_bundles(path: str) -> List[BundleMetadata]:
"""
Discover valid bundles (directories containing flow3r.toml) inside a given
......
import machine
import time
import sys_kernel
class Power:
......@@ -29,3 +30,42 @@ class Power:
def battery_voltage(self) -> float:
self._update()
return self._battery_voltage
@property
def battery_charging(self) -> bool:
"""
True if the battery is currently being charged.
"""
return sys_kernel.battery_charging()
def approximate_battery_percentage(voltage: float) -> float:
"""
Returns approximate battery percentage ([0,100]) based on battery voltage
(in volts).
"""
if voltage > 4.20:
return 100
piecewise = [
(100, 4.20),
(90, 4.06),
(80, 3.98),
(70, 3.92),
(60, 3.87),
(50, 3.82),
(40, 3.79),
(30, 3.77),
(20, 3.74),
(10, 3.68),
(5, 3.45),
(0, 3.00),
]
for (p1, v1), (p2, v2) in zip(piecewise, piecewise[1:]):
if voltage > v1 or voltage < v2:
continue
vr = v1 - v2
pr = p1 - p2
vd = voltage - v2
p = vd / vr
return pr * p + p2
return 0
......@@ -11,7 +11,7 @@ from st3m.ui.menu import (
from st3m.ui.elements import overlays
from st3m.ui.view import View, ViewManager, ViewTransitionBlend
from st3m.ui.elements.menus import SimpleMenu, SunMenu
from st3m.application import discover_bundles, BundleMetadata, MenuItemAppLaunch
from st3m.application import BundleManager, BundleMetadata, MenuItemAppLaunch
from st3m.about import About
from st3m import settings, logging, processors, wifi
......@@ -52,9 +52,11 @@ def run_responder(r: Responder) -> None:
reactor.run()
def _make_bundle_menu(bundles: List[BundleMetadata], kind: str) -> SimpleMenu:
def _make_bundle_menu(mgr: BundleManager, kind: str) -> SimpleMenu:
entries: List[MenuItem] = [MenuItemBack()]
for bundle in bundles:
ids = sorted(mgr.bundles.keys())
for id in ids:
bundle = mgr.bundles[id]
entries += bundle.menu_entries(kind)
return SimpleMenu(entries)
......@@ -93,6 +95,15 @@ def _make_compositor(reactor: Reactor, vm: ViewManager) -> overlays.Compositor:
settings.onoff_debug_touch.subscribe(_onoff_debug_touch_update)
compositor.add_overlay(debug_touch)
# Tie compositor's icon visibility to setting.
def _onoff_show_tray_update() -> None:
compositor.enabled[
overlays.OverlayKind.Indicators
] = settings.onoff_show_tray.value
_onoff_show_tray_update()
settings.onoff_show_tray.subscribe(_onoff_show_tray_update)
# Add icon tray.
compositor.add_overlay(overlays.IconTray())
return compositor
......@@ -127,7 +138,8 @@ def run_main() -> None:
audio.set_volume_dB(-10)
leds.set_rgb(0, 255, 0, 0)
leds.update()
bundles = discover_bundles("/flash/sys/apps")
bundles = BundleManager()
bundles.update()
settings.load_all()
menu_settings = settings.build_menu()
......@@ -152,8 +164,7 @@ def run_main() -> None:
],
)
if override_main_app is not None:
requested = [b for b in bundles if b.name == override_main_app]
print([b.name for b in bundles])
requested = [b for b in bundles.bundles.values() if b.name == override_main_app]
if len(requested) > 1:
raise Exception(f"More than one bundle named {override_main_app}")
if len(requested) == 0:
......
......@@ -297,8 +297,10 @@ onoff_camp_wifi = OnOffTunable("Connect Camp WiFi", "system.camp_wifi_enabled",
onoff_button_swap = OnOffTunable("Swap Buttons", "system.swap_buttons", False)
onoff_debug = OnOffTunable("Debug Overlay", "system.debug", False)
onoff_debug_touch = OnOffTunable("Touch Overlay", "system.debug_touch", False)
onoff_show_tray = OnOffTunable("Show Icons", "system.show_icons", True)
all_settings: List[UnaryTunable] = [
onoff_camp_wifi,
onoff_show_tray,
onoff_button_swap,
onoff_debug,
onoff_debug_touch,
......
......@@ -11,6 +11,7 @@ from st3m.goose import Dict, Enum, List, ABCBase, abstractmethod, Optional
from st3m.utils import tau
from st3m.ui.view import ViewManager
from st3m.input import power
from st3m.power import approximate_battery_percentage
from ctx import Context
import st3m.wifi
......@@ -347,6 +348,8 @@ class Icon(Responder):
that contains it.
"""
WIDTH: int = 25
@abstractmethod
def visible(self) -> bool:
...
......@@ -359,6 +362,8 @@ class USBIcon(Icon):
Might or might not be related to a certain serial bus.
"""
WIDTH: int = 20
def visible(self) -> bool:
return sys_kernel.usb_connected()
......@@ -378,6 +383,8 @@ class USBIcon(Icon):
class WifiIcon(Icon):
WIDTH: int = 15
def __init__(self) -> None:
super().__init__()
self._rssi: float = -120
......@@ -393,17 +400,61 @@ class WifiIcon(Icon):
b = w / 2 - 3.14 / 2
r = self._rssi
ctx.gray(1.0 if r > -775 else 0.2)
ctx.arc(0, 60, 100, a, b, 0).stroke()
ctx.gray(1.0 if r > -75 else 0.2)
ctx.arc(0, 65, 100, a, b, 0).stroke()
ctx.gray(1.0 if r > -85 else 0.2)
ctx.arc(0, 60, 70, a, b, 0).stroke()
ctx.arc(0, 65, 70, a, b, 0).stroke()
ctx.gray(1.0 if r > -95 else 0.2)
ctx.arc(0, 60, 40, a, b, 0).stroke()
ctx.arc(0, 65, 40, a, b, 0).stroke()
def think(self, ins: InputState, delta_ms: int) -> None:
self._rssi = st3m.wifi.rssi()
class BatteryIcon(Icon):
def __init__(self) -> None:
super().__init__()
self._percent = 100.0
self._charging = False
def visible(self) -> bool:
return True
def draw(self, ctx: Context) -> None:
if self._percent > 30:
ctx.rgb(0.17, 0.55, 0.04)
else:
ctx.rgb(0.52, 0.04, 0.17)
height = 160 * self._percent / 100
ctx.rectangle(-80, -50, height, 100)
ctx.fill()
ctx.gray(0.8)
ctx.line_width = 10.0
ctx.rectangle(80, -50, -160, 100)
ctx.stroke()
ctx.rectangle(100, -30, -20, 60)
ctx.fill()
if self._charging:
ctx.gray(1)
ctx.line_width = 20
ctx.move_to(10, -65 - 10)
ctx.line_to(-30, 20 - 10)
ctx.line_to(30, -20 - 10)
ctx.line_to(-10, 65 - 10)
ctx.line_to(-20, 35 - 10)
ctx.stroke()
ctx.move_to(-10, 65 - 10)
ctx.line_to(40, 35 - 10)
ctx.stroke()
def think(self, ins: InputState, delta_ms: int) -> None:
self._percent = approximate_battery_percentage(power.battery_voltage)
self._charging = power.battery_charging
class IconTray(Overlay):
"""
An overlay which renders Icons.
......@@ -413,6 +464,7 @@ class IconTray(Overlay):
def __init__(self) -> None:
self.icons = [
BatteryIcon(),
USBIcon(),
WifiIcon(),
]
......@@ -424,14 +476,16 @@ class IconTray(Overlay):
v.think(ins, delta_ms)
def draw(self, ctx: Context) -> None:
nicons = len(self.visible)
dist = 25
width = (nicons - 1) * dist
x0 = width / -2
if len(self.visible) < 1:
return
width = 0
for icon in self.visible:
width += icon.WIDTH
x0 = width / -2 + self.visible[0].WIDTH / 2
for i, v in enumerate(self.visible):
x = x0 + i * dist
ctx.save()
ctx.translate(x, -100)
ctx.translate(x0, -100)
ctx.scale(0.1, 0.1)
v.draw(ctx)
ctx.restore()
x0 = x0 + v.WIDTH
......@@ -31,3 +31,7 @@ def freertos_sleep(ms):
def i2c_scan():
return [16, 44, 45, 85, 109, 110]
def battery_charging():
return True