Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
# Conflicts:
#	power_control.py
  • Loading branch information
skjdghsdjgsdj committed Oct 8, 2024
2 parents adf56da + 3b82662 commit 2b3366b
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 28 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ If you update CircuitPython on the Feather, you will likely need to build a corr

The build script supports several arguments:
- `--no-compile`: instead of building files with `mpy-cross`, just copy the source `.py` files. This is useful for debugging so errors don't always show as line 1 of a file, but execution is slower. You should only use `--no-compile` when debugging. `code.py` doesn't get compiled regardless.
- `--modules`: only builds or copies the given files. For example, use `--modules code` to just copy `code.py`, or `--modules code sdcard` to just copy `code.py` and build/copy `sdcard.py`.
- `--modules example1 example2`: only builds or copies the given files. For example, use `--modules code` to just copy `code.py`, or `--modules code sdcard` to just copy `code.py` and build/copy `sdcard.py`.
- `--clean`: deletes everything from `lib/` on the `CIRCUITPY` drive and repopulates it with the required Adafruit libraries. This is useful if using `--no-compile` after using compiled files, or vice versa, to ensure the `.py` or `.mpy` files are being used correctly without duplicates. It can take a minute or two to finish.
- `--no-reboot`: don't attempt to reboot the Feather after copying files.
- `--output`: use the specified path instead of the `CIRCUITPY` drive; useful for building zip releases
- `--output /path/to/output/`: use the specified path instead of the `CIRCUITPY` drive
- `--build-release-zip filename.zip`: create a zip file with the given filename containing all compiled files, `code.py`, and `settings.toml.example`; overrides other options

To set up a brand new BabyPod, all you should need to do is:
1. Erase the flash then re-flash CircuitPython.
Expand Down
5 changes: 5 additions & 0 deletions api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ def connect() -> adafruit_requests.Session:

return ConnectionManager.requests

@staticmethod
def disconnect() -> None:
wifi.radio.enabled = False
ConnectionManager.requests = None

timeout = os.getenv("CIRCUITPY_WIFI_TIMEOUT")
ConnectionManager.timeout = 10 if (timeout is None or not timeout) else int(timeout)

Expand Down
51 changes: 48 additions & 3 deletions build-and-deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import argparse
import glob
import subprocess
import sys
import zipfile
from typing import Final
import shutil
import os
Expand Down Expand Up @@ -43,6 +45,13 @@
help = f"Output to this directory instead of {CIRCUITPY_PATH}"
)

parser.add_argument(
"--build-release-zip",
action = "store",
default = None,
help = "Builds a zip suitable for deployment to GitHub to this zip filename; overrides other arguments"
)

def get_base_path():
return os.path.abspath(os.path.dirname(__file__))

Expand Down Expand Up @@ -71,6 +80,7 @@ def clean(output_path: str):
print("done")

def build_and_deploy(source_module: str, output_path: str, compile_to_mpy: bool = True) -> str:

if source_module == "code":
print("Copying code.py...", end = "", flush = True)
dst = f"{output_path}/code.py"
Expand All @@ -82,16 +92,23 @@ def build_and_deploy(source_module: str, output_path: str, compile_to_mpy: bool
raise ValueError(f"File doesn't exist: {full_src}")

if compile_to_mpy:
temp_mpy = tempfile.NamedTemporaryFile(suffix = ".mpy")
temp_mpy = tempfile.NamedTemporaryFile(suffix = ".mpy", delete = False, delete_on_close = False)
print(f"Compiling {source_module}.py...", end = "", flush = True)
result = subprocess.run(["mpy-cross", full_src, "-O9", "-o", temp_mpy.name])
if result.returncode != 0:
raise ValueError(f"mpy-cross failed with status {result.returncode}")

if not os.path.isfile(temp_mpy.name):
raise ValueError(f"mpy-cross didn't actually output a file to {temp_mpy.name}")

dst = f"{output_path}/lib/{source_module}.mpy"

output_directory = pathlib.Path(dst).parent
output_directory.mkdir(parents = True, exist_ok = True)

print("deploying...", end = "", flush = True)
shutil.move(temp_mpy.name, dst)
shutil.copy(temp_mpy.name, dst)
os.remove(temp_mpy.name)
print("done")
else:
dst = f"{output_path}/lib/{source_module}.py"
Expand All @@ -103,11 +120,33 @@ def build_and_deploy(source_module: str, output_path: str, compile_to_mpy: bool

args = parser.parse_args()

if args.build_release_zip:
if args.output:
print("Warning: --output has no effect when specifying --build-release-zip; a temp path will be used", file = sys.stderr)
if args.modules:
print("Warning: --modules has no effect when specifying --build-release-zip; all modules will be built", file = sys.stderr)
if args.no_reboot:
print("Warning: --no-reboot has no effect when specifying --build-release-zip; deployment won't go to device", file = sys.stderr)
if args.clean:
print("Warning: --clean has no effect when specifying --build-release-zip; deployment won't go to device", file = sys.stderr)
if args.no_compile:
print("Warning: --no-compile has no effect when specifying --build-release-zip; releases are always compiled", file = sys.stderr)

args.output = tempfile.gettempdir()
args.modules = None
args.no_reboot = True
args.clean = False
args.no_compile = False

output_path = args.output or CIRCUITPY_PATH

if args.clean:
clean(output_path)

zip_file = None
if args.build_release_zip:
zip_file = zipfile.ZipFile(file = args.build_release_zip, mode = "w")

if args.modules:
for module in args.modules:
build_and_deploy(module, output_path, compile_to_mpy = not args.no_compile)
Expand All @@ -116,7 +155,13 @@ def build_and_deploy(source_module: str, output_path: str, compile_to_mpy: bool
for py_file in py_files:
py_file = pathlib.Path(py_file).with_suffix("").name
if py_file != "build-and-deploy" and not py_file.startswith("._"):
build_and_deploy(py_file, output_path, compile_to_mpy = not args.no_compile)
output = build_and_deploy(py_file, output_path, compile_to_mpy = not args.no_compile)
if zip_file is not None:
zip_file.write(filename = output, arcname = os.path.relpath(output, output_path))

if zip_file is not None:
zip_file.write("settings.toml.example", "settings.toml.example")
zip_file.close()

if not args.no_reboot:
print("Rebooting")
Expand Down
58 changes: 39 additions & 19 deletions flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ def __init__(self, devices: Devices):
)
])

idle_shutdown = NVRAMValues.IDLE_SHUTDOWN.get()
if self.devices.power_control and idle_shutdown:
self.devices.rotary_encoder.on_wait_tick_listeners.append(WaitTickListener(
on_tick = self.idle_shutdown,
seconds = NVRAMValues.IDLE_SHUTDOWN.get()
))

if self.devices.rtc is None or self.devices.sdcard is None:
self.offline_state = None
self.offline_queue = None
Expand Down Expand Up @@ -157,6 +164,11 @@ def idle_warning(self, _: float) -> None:
print("Idle; warning not suppressed and is discharging")
self.devices.piezo.tone("idle_warning")

def idle_shutdown(self, _: float) -> None:
if not self.suppress_idle_warning:
print("Idle; soft shutdown")
self.devices.power_control.shutdown(silent = True)

def on_user_input(self) -> None:
if not self.suppress_dim_timeout:
self.devices.lcd.backlight.set_color(BacklightColors.DEFAULT)
Expand Down Expand Up @@ -190,15 +202,13 @@ def auto_connect(self) -> None:
self.render_splash("Connecting...")
# noinspection PyBroadException
try:
print("Getting requests instance from ConnectionManager")
self.requests = ConnectionManager.connect()
except Exception as e:
import traceback
traceback.print_exception(e)
if self.devices.rtc and self.devices.sdcard:
self.render_splash("Going offline")
self.devices.piezo.tone("info")
time.sleep(1)
NVRAMValues.OFFLINE.write(True)
self.offline()
else:
raise e # can't go offline automatically because there's no hardware support
elif not self.devices.rtc:
Expand Down Expand Up @@ -529,29 +539,39 @@ def settings(self) -> None:
if has_offline_hardware:
if NVRAMValues.OFFLINE and not responses[1]: # was offline, now back online
self.back_online()
elif not NVRAMValues.OFFLINE and responses[1]: # was online, now offline
self.offline()

NVRAMValues.OFFLINE.write(responses[1])
def offline(self):
self.render_splash("Going offline")
self.devices.piezo.tone("info")
time.sleep(1)
ConnectionManager.disconnect()
NVRAMValues.OFFLINE.write(True)

def back_online(self) -> None:
NVRAMValues.OFFLINE.write(False)
self.auto_connect()
files = self.offline_queue.get_json_files()
if len(files) == 0:
return # nothing to do

if len(files) > 0:
print(f"Replaying offline-serialized {len(files)} requests")

print(f"Replaying offline-serialized {len(files)} requests")

self.devices.lcd.clear()
self.devices.lcd.clear()

progress_bar = ProgressBar(devices = self.devices, count = len(files), message = "Syncing changes...")
progress_bar.render_and_wait()
progress_bar = ProgressBar(devices = self.devices, count = len(files), message = "Syncing changes...")
progress_bar.render_and_wait()

index = 0
for filename in files:
progress_bar.set_index(index)
self.offline_queue.replay(filename)
index += 1
index = 0
for filename in files:
progress_bar.set_index(index)
try:
self.offline_queue.replay(filename)
except Exception as e:
NVRAMValues.OFFLINE.write(True)
raise e
index += 1

self.render_success_splash("Change synced!" if len(files) == 1 else f"{len(files)} changes synced!")
self.render_success_splash("Change synced!" if len(files) == 1 else f"{len(files)} changes synced!")

def diaper(self) -> None:
self.render_header_text("How was diaper?")
Expand Down
4 changes: 3 additions & 1 deletion lcd.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class LCD:
RIGHT = 4
LEFT = 5
CENTER = 6
BLOCK = 7

COLUMNS = 20
LINES = 4
Expand Down Expand Up @@ -190,7 +191,8 @@ def get_instance(i2c: I2C):
LCD.CHARGING: [0x4, 0xe, 0x1b, 0x0, 0xe, 0xa, 0xe, 0xe],
LCD.RIGHT: [0x10, 0x18, 0x1c, 0x1e, 0x1c, 0x18, 0x10, 0x0],
LCD.LEFT: [0x2, 0x6, 0xe, 0x1e, 0xe, 0x6, 0x2, 0x0],
LCD.CENTER: [0x0, 0xe, 0x11, 0x15, 0x11, 0xe, 0x0, 0x0]
LCD.CENTER: [0x0, 0xe, 0x11, 0x15, 0x11, 0xe, 0x0, 0x0],
LCD.BLOCK: [0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f]
}

class SparkfunSerLCD(LCD):
Expand Down
5 changes: 4 additions & 1 deletion nvram.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,7 @@ class NVRAMValues:
# True if flags are configured and don't need to be reconfigured. No effect for the Adafruit LCD.
HAS_CONFIGURED_SPARKFUN_LCD = NVRAMBooleanValue(9, False, "HAS_CONFIGURED_SPARKFUN_LCD")
# How frequently in seconds to check for a MOTD
MOTD_CHECK_INTERVAL = NVRAMIntegerValue(10, 60 * 60 * 3, "MOTD_CHECK_INTERVAL")
MOTD_CHECK_INTERVAL = NVRAMIntegerValue(10, 60 * 60 * 6, "MOTD_CHECK_INTERVAL")
# Soft shutdown after this many seconds of being idle and not in a timer; only has an effect if soft shutdown
# is enabled
IDLE_SHUTDOWN = NVRAMIntegerValue(11, 60 * 5, "IDLE_SHUTDOWN")
4 changes: 2 additions & 2 deletions ui_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ def render_progress(self) -> None:
block_count = math.ceil(((self.index + 1) / self.count) * self.max_block_count) + 1

if self.last_block_count == -1:
self.devices.lcd.write("-" * block_count, (0, 2))
self.devices.lcd.write(self.devices.lcd[LCD.BLOCK] * block_count, (0, 2))
self.last_block_count = block_count
elif block_count != self.last_block_count:
extra_blocks = block_count - self.last_block_count
self.devices.lcd.write("-" * extra_blocks, (self.last_block_count - 1, 2))
self.devices.lcd.write(self.devices.lcd[LCD.BLOCK] * extra_blocks, (self.last_block_count - 1, 2))

def render_and_wait(self) -> None:
super().render_and_wait()
Expand Down

0 comments on commit 2b3366b

Please sign in to comment.