mirror of
https://github.com/Crocmagnon/plant-badger.git
synced 2024-09-07 22:57:35 +02:00
update firmware
This commit is contained in:
parent
e11f7a08a6
commit
6fc7ed0f2e
11 changed files with 34 additions and 533 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
||||||
layout python3
|
|
|
@ -1,4 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (plant-badge)" project-jdk-type="Python SDK" />
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (plant-badge)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (plant-badge)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
|
@ -13,7 +13,7 @@
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.direnv" />
|
<excludeFolder url="file://$MODULE_DIR$/.direnv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.10 (plant-badge)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.12 (plant-badge)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="MicroPython" level="project" />
|
<orderEntry type="library" name="MicroPython" level="project" />
|
||||||
</component>
|
</component>
|
||||||
|
|
2
.rtx.toml
Normal file
2
.rtx.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[tools]
|
||||||
|
python = {version='latest', virtualenv='.venv'}
|
|
@ -1 +0,0 @@
|
||||||
python@3.10.9
|
|
|
@ -94,20 +94,20 @@ e6614864d3269c34:
|
||||||
ha_plant_min_dli: number.aloe_vera_min_dli
|
ha_plant_min_dli: number.aloe_vera_min_dli
|
||||||
|
|
||||||
e6614864d3938334:
|
e6614864d3938334:
|
||||||
HA_PLANT_ID: plant.senecio_himalaya
|
HA_PLANT_ID: plant.chlorophytum_comosum
|
||||||
HA_PLANT_MOISTURE_SENSOR: sensor.senecio_himalaya_soil_moisture
|
HA_PLANT_MOISTURE_SENSOR: sensor.chlorophytum_comosum_soil_moisture
|
||||||
HA_PLANT_TEMPERATURE_SENSOR: sensor.senecio_himalaya_temperature
|
HA_PLANT_TEMPERATURE_SENSOR: sensor.chlorophytum_comosum_temperature
|
||||||
HA_PLANT_CONDUCTIVITY_SENSOR: sensor.senecio_himalaya_conductivity
|
HA_PLANT_CONDUCTIVITY_SENSOR: sensor.chlorophytum_comosum_conductivity
|
||||||
HA_PLANT_ILLUMINANCE_SENSOR: sensor.senecio_himalaya_illuminance
|
HA_PLANT_ILLUMINANCE_SENSOR: sensor.chlorophytum_comosum_illuminance
|
||||||
HA_PLANT_DLI_SENSOR: sensor.senecio_himalaya_dli
|
HA_PLANT_DLI_SENSOR: sensor.chlorophytum_comosum_dli
|
||||||
|
|
||||||
ha_plant_max_moisture: number.senecio_himalaya_max_soil_moisture
|
ha_plant_max_moisture: number.chlorophytum_comosum_max_soil_moisture
|
||||||
ha_plant_min_moisture: number.senecio_himalaya_min_soil_moisture
|
ha_plant_min_moisture: number.chlorophytum_comosum_min_soil_moisture
|
||||||
ha_plant_max_temperature: number.senecio_himalaya_max_temperature
|
ha_plant_max_temperature: number.chlorophytum_comosum_max_temperature
|
||||||
ha_plant_min_temperature: number.senecio_himalaya_min_temperature
|
ha_plant_min_temperature: number.chlorophytum_comosum_min_temperature
|
||||||
ha_plant_max_illuminance: number.senecio_himalaya_max_illuminance
|
ha_plant_max_illuminance: number.chlorophytum_comosum_max_illuminance
|
||||||
ha_plant_min_illuminance: number.senecio_himalaya_min_illuminance
|
ha_plant_min_illuminance: number.chlorophytum_comosum_min_illuminance
|
||||||
ha_plant_max_conductivity: number.senecio_himalaya_max_conductivity
|
ha_plant_max_conductivity: number.chlorophytum_comosum_max_conductivity
|
||||||
ha_plant_min_conductivity: number.senecio_himalaya_min_conductivity
|
ha_plant_min_conductivity: number.chlorophytum_comosum_min_conductivity
|
||||||
ha_plant_max_dli: number.senecio_himalaya_max_dli
|
ha_plant_max_dli: number.chlorophytum_comosum_max_dli
|
||||||
ha_plant_min_dli: number.senecio_himalaya_min_dli
|
ha_plant_min_dli: number.chlorophytum_comosum_min_dli
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import urequests
|
import urequests
|
||||||
import jpegdec
|
import jpegdec
|
||||||
|
import sys
|
||||||
|
|
||||||
from badger2040 import (
|
from badger2040 import (
|
||||||
WIDTH,
|
WIDTH,
|
||||||
|
@ -224,9 +225,13 @@ def display_header(text):
|
||||||
display.rectangle(0, 0, WIDTH, 20)
|
display.rectangle(0, 0, WIDTH, 20)
|
||||||
|
|
||||||
# Write text in header
|
# Write text in header
|
||||||
|
if len(text) > 15:
|
||||||
|
text = text.split(" ")[0]
|
||||||
|
if len(text) > 15:
|
||||||
|
text = text[:14] + "."
|
||||||
display.set_font("bitmap6")
|
display.set_font("bitmap6")
|
||||||
display.set_pen(WHITE)
|
display.set_pen(WHITE)
|
||||||
display.text(text, 3, 4)
|
display.text(text[:16], 3, 4)
|
||||||
|
|
||||||
# Display time
|
# Display time
|
||||||
hour, minute = get_time()
|
hour, minute = get_time()
|
||||||
|
@ -249,7 +254,7 @@ while True:
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
sys.print_exception(e)
|
||||||
warning(display, str(e))
|
warning(display, str(e))
|
||||||
display.set_timer_minutes_with_jitter(secrets.ERROR_REFRESH_INTERVAL_MINUTES)
|
display.set_timer_minutes_with_jitter(secrets.ERROR_REFRESH_INTERVAL_MINUTES)
|
||||||
display.halt()
|
display.halt()
|
||||||
|
|
|
@ -165,6 +165,10 @@ if exited_to_launcher or not woken_by_button:
|
||||||
display.set_update_speed(badger2040.UPDATE_FAST)
|
display.set_update_speed(badger2040.UPDATE_FAST)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
# Sometimes a button press or hold will keep the system
|
||||||
|
# powered *through* HALT, so latch the power back on.
|
||||||
|
display.keepalive()
|
||||||
|
|
||||||
if display.pressed(badger2040.BUTTON_A):
|
if display.pressed(badger2040.BUTTON_A):
|
||||||
button(badger2040.BUTTON_A)
|
button(badger2040.BUTTON_A)
|
||||||
if display.pressed(badger2040.BUTTON_B):
|
if display.pressed(badger2040.BUTTON_B):
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
import machine
|
|
||||||
import micropython
|
|
||||||
from picographics import PicoGraphics, DISPLAY_INKY_PACK
|
|
||||||
import network
|
|
||||||
from network_manager import NetworkManager
|
|
||||||
import WIFI_CONFIG
|
|
||||||
import uasyncio
|
|
||||||
import time
|
|
||||||
import gc
|
|
||||||
import wakeup
|
|
||||||
|
|
||||||
|
|
||||||
BUTTON_DOWN = 11
|
|
||||||
BUTTON_A = 12
|
|
||||||
BUTTON_B = 13
|
|
||||||
BUTTON_C = 14
|
|
||||||
BUTTON_UP = 15
|
|
||||||
BUTTON_USER = None # User button not available on W
|
|
||||||
|
|
||||||
BUTTON_MASK = 0b11111 << 11
|
|
||||||
|
|
||||||
SYSTEM_VERY_SLOW = 0
|
|
||||||
SYSTEM_SLOW = 1
|
|
||||||
SYSTEM_NORMAL = 2
|
|
||||||
SYSTEM_FAST = 3
|
|
||||||
SYSTEM_TURBO = 4
|
|
||||||
|
|
||||||
UPDATE_NORMAL = 0
|
|
||||||
UPDATE_MEDIUM = 1
|
|
||||||
UPDATE_FAST = 2
|
|
||||||
UPDATE_TURBO = 3
|
|
||||||
|
|
||||||
LED = 22
|
|
||||||
ENABLE_3V3 = 10
|
|
||||||
BUSY = 26
|
|
||||||
|
|
||||||
WIDTH = 296
|
|
||||||
HEIGHT = 128
|
|
||||||
|
|
||||||
SYSTEM_FREQS = [4000000, 12000000, 48000000, 133000000, 250000000]
|
|
||||||
|
|
||||||
BUTTONS = {
|
|
||||||
BUTTON_DOWN: machine.Pin(BUTTON_DOWN, machine.Pin.IN, machine.Pin.PULL_DOWN),
|
|
||||||
BUTTON_A: machine.Pin(BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN),
|
|
||||||
BUTTON_B: machine.Pin(BUTTON_B, machine.Pin.IN, machine.Pin.PULL_DOWN),
|
|
||||||
BUTTON_C: machine.Pin(BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN),
|
|
||||||
BUTTON_UP: machine.Pin(BUTTON_UP, machine.Pin.IN, machine.Pin.PULL_DOWN),
|
|
||||||
}
|
|
||||||
|
|
||||||
WAKEUP_MASK = 0
|
|
||||||
|
|
||||||
|
|
||||||
def is_wireless():
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def woken_by_button():
|
|
||||||
return wakeup.get_gpio_state() & BUTTON_MASK > 0
|
|
||||||
|
|
||||||
|
|
||||||
def pressed_to_wake(button):
|
|
||||||
return wakeup.get_gpio_state() & (1 << button) > 0
|
|
||||||
|
|
||||||
|
|
||||||
def reset_pressed_to_wake():
|
|
||||||
wakeup.reset_gpio_state()
|
|
||||||
|
|
||||||
|
|
||||||
def pressed_to_wake_get_once(button):
|
|
||||||
global WAKEUP_MASK
|
|
||||||
result = (wakeup.get_gpio_state() & ~WAKEUP_MASK & (1 << button)) > 0
|
|
||||||
WAKEUP_MASK |= 1 << button
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def system_speed(speed):
|
|
||||||
try:
|
|
||||||
machine.freq(SYSTEM_FREQS[speed])
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Badger2040:
|
|
||||||
def __init__(self):
|
|
||||||
self.display = PicoGraphics(DISPLAY_INKY_PACK)
|
|
||||||
self._led = machine.PWM(machine.Pin(LED))
|
|
||||||
self._led.freq(1000)
|
|
||||||
self._led.duty_u16(0)
|
|
||||||
self._update_speed = 0
|
|
||||||
|
|
||||||
def __getattr__(self, item):
|
|
||||||
# Glue to redirect calls to PicoGraphics
|
|
||||||
return getattr(self.display, item)
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
t_start = time.ticks_ms()
|
|
||||||
self.display.update()
|
|
||||||
t_elapsed = time.ticks_ms() - t_start
|
|
||||||
|
|
||||||
delay_ms = [4700, 2600, 900, 250][self._update_speed]
|
|
||||||
|
|
||||||
if t_elapsed < delay_ms:
|
|
||||||
time.sleep((delay_ms - t_elapsed) / 1000)
|
|
||||||
|
|
||||||
def set_update_speed(self, speed):
|
|
||||||
self.display.set_update_speed(speed)
|
|
||||||
self._update_speed = speed
|
|
||||||
|
|
||||||
def led(self, brightness):
|
|
||||||
brightness = max(0, min(255, brightness))
|
|
||||||
self._led.duty_u16(int(brightness * 256))
|
|
||||||
|
|
||||||
def invert(self, invert):
|
|
||||||
raise RuntimeError("Display invert not supported in PicoGraphics.")
|
|
||||||
|
|
||||||
def thickness(self, thickness):
|
|
||||||
raise RuntimeError("Thickness not supported in PicoGraphics.")
|
|
||||||
|
|
||||||
def halt(self):
|
|
||||||
time.sleep(0.05)
|
|
||||||
enable = machine.Pin(ENABLE_3V3, machine.Pin.OUT)
|
|
||||||
enable.off()
|
|
||||||
while not self.pressed_any():
|
|
||||||
pass
|
|
||||||
|
|
||||||
def pressed(self, button):
|
|
||||||
return BUTTONS[button].value() == 1 or pressed_to_wake_get_once(button)
|
|
||||||
|
|
||||||
def pressed_any(self):
|
|
||||||
for button in BUTTONS.values():
|
|
||||||
if button.value():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@micropython.native
|
|
||||||
def icon(self, data, index, data_w, icon_size, x, y):
|
|
||||||
s_x = (index * icon_size) % data_w
|
|
||||||
s_y = int((index * icon_size) / data_w)
|
|
||||||
|
|
||||||
for o_y in range(icon_size):
|
|
||||||
for o_x in range(icon_size):
|
|
||||||
o = ((o_y + s_y) * data_w) + (o_x + s_x)
|
|
||||||
bm = 0b10000000 >> (o & 0b111)
|
|
||||||
if data[o >> 3] & bm:
|
|
||||||
self.display.pixel(x + o_x, y + o_y)
|
|
||||||
|
|
||||||
def image(self, data, w, h, x, y):
|
|
||||||
for oy in range(h):
|
|
||||||
row = data[oy]
|
|
||||||
for ox in range(w):
|
|
||||||
if row & 0b1 == 0:
|
|
||||||
self.display.pixel(x + ox, y + oy)
|
|
||||||
row >>= 1
|
|
||||||
|
|
||||||
def status_handler(self, mode, status, ip):
|
|
||||||
print(mode, status, ip)
|
|
||||||
self.display.set_pen(15)
|
|
||||||
self.display.clear()
|
|
||||||
self.display.set_pen(0)
|
|
||||||
if status:
|
|
||||||
self.display.text("Connected!", 10, 10, 300, 0.5)
|
|
||||||
self.display.text(ip, 10, 30, 300, 0.5)
|
|
||||||
else:
|
|
||||||
self.display.text("Connecting...", 10, 10, 300, 0.5)
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def isconnected(self):
|
|
||||||
return network.WLAN(network.STA_IF).isconnected()
|
|
||||||
|
|
||||||
def ip_address(self):
|
|
||||||
return network.WLAN(network.STA_IF).ifconfig()[0]
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
if WIFI_CONFIG.COUNTRY == "":
|
|
||||||
raise RuntimeError("You must populate WIFI_CONFIG.py for networking.")
|
|
||||||
self.display.set_update_speed(2)
|
|
||||||
network_manager = NetworkManager(
|
|
||||||
WIFI_CONFIG.COUNTRY, status_handler=self.status_handler
|
|
||||||
)
|
|
||||||
uasyncio.get_event_loop().run_until_complete(
|
|
||||||
network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK)
|
|
||||||
)
|
|
||||||
gc.collect()
|
|
|
@ -1,211 +0,0 @@
|
||||||
import os
|
|
||||||
import gc
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
import machine
|
|
||||||
import badger2040
|
|
||||||
|
|
||||||
|
|
||||||
def get_battery_level():
|
|
||||||
# Battery measurement
|
|
||||||
vbat_adc = machine.ADC(badger2040.PIN_BATTERY)
|
|
||||||
vref_adc = machine.ADC(badger2040.PIN_1V2_REF)
|
|
||||||
vref_en = machine.Pin(badger2040.PIN_VREF_POWER)
|
|
||||||
vref_en.init(machine.Pin.OUT)
|
|
||||||
vref_en.value(0)
|
|
||||||
|
|
||||||
# Enable the onboard voltage reference
|
|
||||||
vref_en.value(1)
|
|
||||||
|
|
||||||
# Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries
|
|
||||||
vdd = 1.24 * (65535 / vref_adc.read_u16())
|
|
||||||
vbat = (
|
|
||||||
(vbat_adc.read_u16() / 65535) * 3 * vdd
|
|
||||||
) # 3 in this is a gain, not rounding of 3.3V
|
|
||||||
|
|
||||||
# Disable the onboard voltage reference
|
|
||||||
vref_en.value(0)
|
|
||||||
|
|
||||||
# Convert the voltage to a level to display onscreen
|
|
||||||
return vbat
|
|
||||||
|
|
||||||
|
|
||||||
def get_disk_usage():
|
|
||||||
# f_bfree and f_bavail should be the same?
|
|
||||||
# f_files, f_ffree, f_favail and f_flag are unsupported.
|
|
||||||
f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs("/")
|
|
||||||
|
|
||||||
f_total_size = f_frsize * f_blocks
|
|
||||||
f_total_free = f_bsize * f_bfree
|
|
||||||
f_total_used = f_total_size - f_total_free
|
|
||||||
|
|
||||||
f_used = 100 / f_total_size * f_total_used
|
|
||||||
f_free = 100 / f_total_size * f_total_free
|
|
||||||
|
|
||||||
return f_total_size, f_used, f_free
|
|
||||||
|
|
||||||
|
|
||||||
def state_running():
|
|
||||||
state = {"running": "launcher"}
|
|
||||||
state_load("launcher", state)
|
|
||||||
return state["running"]
|
|
||||||
|
|
||||||
|
|
||||||
def state_clear_running():
|
|
||||||
running = state_running()
|
|
||||||
state_modify("launcher", {"running": "launcher"})
|
|
||||||
return running != "launcher"
|
|
||||||
|
|
||||||
|
|
||||||
def state_set_running(app):
|
|
||||||
state_modify("launcher", {"running": app})
|
|
||||||
|
|
||||||
|
|
||||||
def state_launch():
|
|
||||||
app = state_running()
|
|
||||||
if app is not None and app != "launcher":
|
|
||||||
launch(app)
|
|
||||||
|
|
||||||
|
|
||||||
def state_delete(app):
|
|
||||||
try:
|
|
||||||
os.remove("/state/{}.json".format(app))
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def state_save(app, data):
|
|
||||||
try:
|
|
||||||
with open("/state/{}.json".format(app), "w") as f:
|
|
||||||
f.write(json.dumps(data))
|
|
||||||
f.flush()
|
|
||||||
except OSError:
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.stat("/state")
|
|
||||||
except OSError:
|
|
||||||
os.mkdir("/state")
|
|
||||||
state_save(app, data)
|
|
||||||
|
|
||||||
|
|
||||||
def state_modify(app, data):
|
|
||||||
state = {}
|
|
||||||
state_load(app, state)
|
|
||||||
state.update(data)
|
|
||||||
state_save(app, state)
|
|
||||||
|
|
||||||
|
|
||||||
def state_load(app, defaults):
|
|
||||||
try:
|
|
||||||
data = json.loads(open("/state/{}.json".format(app), "r").read())
|
|
||||||
if type(data) is dict:
|
|
||||||
defaults.update(data)
|
|
||||||
return True
|
|
||||||
except (OSError, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
state_save(app, defaults)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def launch(file):
|
|
||||||
state_set_running(file)
|
|
||||||
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
|
||||||
button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)
|
|
||||||
|
|
||||||
def quit_to_launcher(pin):
|
|
||||||
if button_a.value() and button_c.value():
|
|
||||||
machine.reset()
|
|
||||||
|
|
||||||
button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
|
|
||||||
button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
|
|
||||||
|
|
||||||
try:
|
|
||||||
__import__(file)
|
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
# If the app doesn't exist, notify the user
|
|
||||||
warning(None, f"Could not launch: {file}. {e}")
|
|
||||||
time.sleep(4.0)
|
|
||||||
except Exception as e:
|
|
||||||
# If the app throws an error, catch it and display!
|
|
||||||
print(e)
|
|
||||||
state_clear_running()
|
|
||||||
display = badger2040.Badger2040()
|
|
||||||
warning(display, str(e))
|
|
||||||
display.halt()
|
|
||||||
|
|
||||||
# If the app exits or errors, do not relaunch!
|
|
||||||
state_clear_running()
|
|
||||||
machine.reset() # Exit back to launcher
|
|
||||||
|
|
||||||
|
|
||||||
# Draw an overlay box with a given message within it
|
|
||||||
def warning(
|
|
||||||
display,
|
|
||||||
message,
|
|
||||||
width=badger2040.WIDTH - 20,
|
|
||||||
height=badger2040.HEIGHT - 20,
|
|
||||||
line_spacing=20,
|
|
||||||
text_size=0.6,
|
|
||||||
):
|
|
||||||
print(message)
|
|
||||||
|
|
||||||
if display is None:
|
|
||||||
display = badger2040.Badger2040()
|
|
||||||
display.led(128)
|
|
||||||
|
|
||||||
# Draw a light grey background
|
|
||||||
display.set_pen(12)
|
|
||||||
display.rectangle(
|
|
||||||
(badger2040.WIDTH - width) // 2,
|
|
||||||
(badger2040.HEIGHT - height) // 2,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
)
|
|
||||||
|
|
||||||
width -= 20
|
|
||||||
height -= 20
|
|
||||||
|
|
||||||
display.set_pen(15)
|
|
||||||
display.rectangle(
|
|
||||||
(badger2040.WIDTH - width) // 2,
|
|
||||||
(badger2040.HEIGHT - height) // 2,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Take the provided message and split it up into
|
|
||||||
# lines that fit within the specified width
|
|
||||||
words = message.split(" ")
|
|
||||||
|
|
||||||
lines = []
|
|
||||||
current_line = ""
|
|
||||||
for word in words:
|
|
||||||
if display.measure_text(current_line + word + " ", text_size) < width:
|
|
||||||
current_line += word + " "
|
|
||||||
else:
|
|
||||||
lines.append(current_line.strip())
|
|
||||||
current_line = word + " "
|
|
||||||
lines.append(current_line.strip())
|
|
||||||
|
|
||||||
display.set_pen(0)
|
|
||||||
|
|
||||||
# Display each line of text from the message, centre-aligned
|
|
||||||
num_lines = len(lines)
|
|
||||||
for i in range(num_lines):
|
|
||||||
length = display.measure_text(lines[i], text_size)
|
|
||||||
current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2
|
|
||||||
display.text(
|
|
||||||
lines[i],
|
|
||||||
(badger2040.WIDTH - length) // 2,
|
|
||||||
(badger2040.HEIGHT // 2) + current_line,
|
|
||||||
badger2040.WIDTH,
|
|
||||||
text_size,
|
|
||||||
)
|
|
||||||
|
|
||||||
display.update()
|
|
|
@ -1,117 +0,0 @@
|
||||||
import rp2
|
|
||||||
import network
|
|
||||||
import machine
|
|
||||||
import uasyncio
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkManager:
|
|
||||||
_ifname = ("Client", "Access Point")
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
country="GB",
|
|
||||||
client_timeout=30,
|
|
||||||
access_point_timeout=5,
|
|
||||||
status_handler=None,
|
|
||||||
error_handler=None,
|
|
||||||
):
|
|
||||||
rp2.country(country)
|
|
||||||
self._ap_if = network.WLAN(network.AP_IF)
|
|
||||||
self._sta_if = network.WLAN(network.STA_IF)
|
|
||||||
|
|
||||||
self._mode = network.STA_IF
|
|
||||||
self._client_timeout = client_timeout
|
|
||||||
self._access_point_timeout = access_point_timeout
|
|
||||||
self._status_handler = status_handler
|
|
||||||
self._error_handler = error_handler
|
|
||||||
self.UID = ("{:02X}" * 8).format(*machine.unique_id())
|
|
||||||
|
|
||||||
def isconnected(self):
|
|
||||||
return self._sta_if.isconnected() or self._ap_if.isconnected()
|
|
||||||
|
|
||||||
def config(self, var):
|
|
||||||
if self._sta_if.active():
|
|
||||||
return self._sta_if.config(var)
|
|
||||||
else:
|
|
||||||
if var == "password":
|
|
||||||
return self.UID
|
|
||||||
return self._ap_if.config(var)
|
|
||||||
|
|
||||||
def mode(self):
|
|
||||||
if self._sta_if.isconnected():
|
|
||||||
return self._ifname[0]
|
|
||||||
if self._ap_if.isconnected():
|
|
||||||
return self._ifname[1]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def ifaddress(self):
|
|
||||||
if self._sta_if.isconnected():
|
|
||||||
return self._sta_if.ifconfig()[0]
|
|
||||||
if self._ap_if.isconnected():
|
|
||||||
return self._ap_if.ifconfig()[0]
|
|
||||||
return "0.0.0.0"
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
if self._sta_if.isconnected():
|
|
||||||
self._sta_if.disconnect()
|
|
||||||
if self._ap_if.isconnected():
|
|
||||||
self._ap_if.disconnect()
|
|
||||||
|
|
||||||
async def wait(self, mode):
|
|
||||||
while not self.isconnected():
|
|
||||||
self._handle_status(mode, None)
|
|
||||||
await uasyncio.sleep_ms(1000)
|
|
||||||
|
|
||||||
def _handle_status(self, mode, status):
|
|
||||||
if callable(self._status_handler):
|
|
||||||
self._status_handler(self._ifname[mode], status, self.ifaddress())
|
|
||||||
|
|
||||||
def _handle_error(self, mode, msg):
|
|
||||||
if callable(self._error_handler):
|
|
||||||
if self._error_handler(self._ifname[mode], msg):
|
|
||||||
return
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
async def client(self, ssid, psk):
|
|
||||||
if self._sta_if.isconnected():
|
|
||||||
self._handle_status(network.STA_IF, True)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._ap_if.disconnect()
|
|
||||||
self._ap_if.active(False)
|
|
||||||
|
|
||||||
self._sta_if.active(True)
|
|
||||||
self._sta_if.connect(ssid, psk)
|
|
||||||
self._sta_if.config(pm=0xA11140)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await uasyncio.wait_for(self.wait(network.STA_IF), self._client_timeout)
|
|
||||||
self._handle_status(network.STA_IF, True)
|
|
||||||
|
|
||||||
except uasyncio.TimeoutError:
|
|
||||||
self._sta_if.active(False)
|
|
||||||
self._handle_status(network.STA_IF, False)
|
|
||||||
self._handle_error(network.STA_IF, "WIFI Client Failed")
|
|
||||||
|
|
||||||
async def access_point(self):
|
|
||||||
if self._ap_if.isconnected():
|
|
||||||
self._handle_status(network.AP_IF, True)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._sta_if.disconnect()
|
|
||||||
self._sta_if.active(False)
|
|
||||||
|
|
||||||
self._ap_if.ifconfig(("10.10.1.1", "255.255.255.0", "10.10.1.1", "10.10.1.1"))
|
|
||||||
self._ap_if.config(password=self.UID)
|
|
||||||
self._ap_if.active(True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
await uasyncio.wait_for(
|
|
||||||
self.wait(network.AP_IF), self._access_point_timeout
|
|
||||||
)
|
|
||||||
self._handle_status(network.AP_IF, True)
|
|
||||||
|
|
||||||
except uasyncio.TimeoutError:
|
|
||||||
self._sta_if.active(False)
|
|
||||||
self._handle_status(network.AP_IF, False)
|
|
||||||
self._handle_error(network.AP_IF, "WIFI Client Failed")
|
|
Loading…
Reference in a new issue