Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
micropython/
target/
src/settings.py
#cached templated file
src/templates/*.py

__pycache__
temp/
#OS garbage
Expand Down
11 changes: 4 additions & 7 deletions src/gunpla/base_gundam.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ class BaseGundam:
Base Gunpla.
"""

def __init__(self):
def __init__(self, hardware):
from src.hardware.Hardware import Hardware
self.hardware: Hardware = hardware
with open(self.get_config_file()) as config_contents:
self.config: json = json.loads(config_contents.read())

Expand All @@ -37,9 +39,6 @@ def led_off(self, led_name: str) -> None:
led = self._get_led_from_name(led_name)
led.off()

# TODO: make this not need the safe_execution and do it when we register paths

@safe_execution
def all_on(self) -> None:
"""
Turns all configured LED's on.
Expand All @@ -62,8 +61,6 @@ def _all_leds_on(self) -> str:
leds += f"{led_name}: on\n"
return leds

# TODO: make this not need the safe_execution and do it when we register paths
@safe_execution
def all_off(self) -> None:
"""
Turns all configured LED's off
Expand Down Expand Up @@ -110,7 +107,7 @@ def _get_led_from_name(self, led_name: str) -> LED:
if 'disabled' in entry and entry['disabled']:
print(f"{led_name} is disabled")
return DisabledLED(led_name)
return LED(entry['pin'], led_name)
return self.hardware.create_led(entry['pin'], led_name)

def __get_entry_from_name(self, led_name: str) -> json:
"""
Expand Down
9 changes: 9 additions & 0 deletions src/hardware/Hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@ def networking(self) -> Networking:

def board_led(self) -> BoardLED:
raise NotImplementedError

def create_led(self, pin_number: int, name: str):
"""
Creates an LED instance appropriate for this hardware.
:param pin_number: GPIO pin number
:param name: LED name
:return: LED instance (LED or MockLED)
"""
raise NotImplementedError
6 changes: 6 additions & 0 deletions src/hardware/PicoHardwre.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ def get_pwm(self, pin_obj):
def reset_pin(self, pin_num):
"""Re-initializes the pin to clear PWM settings"""
return self.get_pin(pin_num, mode="OUT")

def create_led(self, pin_number: int, name: str):
"""Creates a real LED with actual GPIO pin"""
from src.pi.LED import LED
pin = self.get_pin(pin_number, mode="OUT")
return LED(pin, name)
6 changes: 6 additions & 0 deletions src/hardware/VirtualHardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,9 @@ def networking(self) -> Networking:

def reset_pin(self, pin_num):
print(f"[SIM] Pin {pin_num} reset to standard GPIO")

def create_led(self, pin_number: int, name: str):
"""Creates a mock LED for simulation"""
from src.pi.LED import MockLED
pin = self.get_pin(pin_number, mode="OUT")
return MockLED(pin, name)
27 changes: 23 additions & 4 deletions src/pi/LED.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ class LED:
A wrapper around Pin(n, Pin.OUT) that provides LED light like functionality to turn on, turn off, dim, etc
"""

def __init__(self, pin_number: int, name: str):
from machine import Pin

self._pin: Pin = Pin(pin_number, Pin.OUT)
def __init__(self, pin, name: str):
"""
:param pin: A Pin object (from machine.Pin or MockPin)
:param name: The LED name
"""
self._pin = pin
self._led_name = name

def enabled(self) -> bool:
Expand Down Expand Up @@ -40,3 +42,20 @@ def pin(self):
:return: The underlying Raspberry Pi Pico Pin of the LED.
"""
return self._pin


class MockLED(LED):
"""
LED implementation for simulation that prints actions to console.
Used when running with VirtualHardware for testing without physical hardware.
"""

def on(self):
"""Turns on the LED with simulation output"""
print(f"[SIM] LED '{self._led_name}' (Pin {self._pin.num}) ON")
self._pin.on()

def off(self):
"""Turns off the LED with simulation output"""
print(f"[SIM] LED '{self._led_name}' (Pin {self._pin.num}) OFF")
self._pin.off()
5 changes: 4 additions & 1 deletion src/server/Wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ def safe_execution(func):
async def wrapper(*args, **kwargs):
try:
print(f"Trying to execute {func.__name__}")
await func(*args, **kwargs)
result = await func(*args, **kwargs)
# If the function returns a value, use it; otherwise return success JSON
if result is not None:
return result
return {"status": "success", "action": func.__name__}, 202
except Exception as e:
# Log the error to console
Expand Down
2 changes: 1 addition & 1 deletion src/server/microdot/Microdot.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ def form(self):
if self.content_type is None:
return None
mime_type = self.content_type.split(';')[0]
if mime_type != 'application/x-www-form-urlencoded':
if mime_type != 'application/x-templates-form-urlencoded':
return None
self._form = self._parse_urlencoded(self.body)
return self._form
Expand Down
70 changes: 70 additions & 0 deletions src/server/microdot/utemplate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from src.server.utemplate import recompile

_loader = None


class Template:
"""A template object.

:param template: The filename of the template to render, relative to the
configured template directory.
"""
@classmethod
def initialize(cls, template_dir='templates',
loader_class=recompile.Loader):
"""Initialize the templating subsystem.

:param template_dir: the directory where templates are stored. This
argument is optional. The default is to load
templates from a *templates* subdirectory.
:param loader_class: the ``utemplate.Loader`` class to use when loading
templates. This argument is optional. The default
is the ``recompile.Loader`` class, which
automatically recompiles templates when they
change.
"""
global _loader
_loader = loader_class(None, template_dir)

def __init__(self, template):
if _loader is None: # pragma: no cover
self.initialize()
#: The name of the template
self.name = template
self.template = _loader.load(template)

def generate(self, *args, **kwargs):
"""Return a generator that renders the template in chunks, with the
given arguments."""
return self.template(*args, **kwargs)

def render(self, *args, **kwargs):
"""Render the template with the given arguments and return it as a
string."""
return ''.join(self.generate(*args, **kwargs))

def generate_async(self, *args, **kwargs):
"""Return an asynchronous generator that renders the template in
chunks, using the given arguments."""
class sync_to_async_iter():
def __init__(self, iter):
self.iter = iter

def __aiter__(self):
return self

async def __anext__(self):
try:
return next(self.iter)
except StopIteration:
raise StopAsyncIteration

return sync_to_async_iter(self.generate(*args, **kwargs))

async def render_async(self, *args, **kwargs):
"""Render the template with the given arguments asynchronously and
return it as a string."""
response = ''
async for chunk in self.generate_async(*args, **kwargs):
response += chunk
return response
14 changes: 14 additions & 0 deletions src/server/utemplate/compiled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Loader:

def __init__(self, pkg, dir):
if dir == ".":
dir = ""
else:
dir = dir.replace("/", ".") + "."
if pkg and pkg != "__main__":
dir = pkg + "." + dir
self.p = dir

def load(self, name):
name = name.replace(".", "_")
return __import__(self.p + name, None, None, (name,)).render
22 changes: 22 additions & 0 deletions src/server/utemplate/recompile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# (c) 2014-2020 Paul Sokolovsky. MIT license.
try:
from uos import remove, stat
except:
from os import stat, remove

from . import source


class Loader(source.Loader):

def load(self, name):
o_path = self.pkg_path + self.compiled_path(name)
i_path = self.pkg_path + self.dir + "/" + name
try:
o_stat = stat(o_path)
i_stat = stat(i_path)
if i_stat[8] > o_stat[8]:
# input file is newer, remove output to force recompile
remove(o_path)
finally:
return super().load(name)
Loading