adding initial MacroPad OS commit!
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
from .abstract_app import App
|
||||||
|
from .app_state import AppState, InvalidStateUpdateError
|
||||||
|
from .app_router import AppRouter
|
||||||
|
from .debug_app import DebugApp
|
||||||
|
from .options_app import OptionsApp
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import displayio
|
||||||
|
|
||||||
|
from .app_state import AppState, InvalidStateUpdateError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DISPLAY = displayio.Group()
|
||||||
|
|
||||||
|
class App(object):
|
||||||
|
|
||||||
|
def __init__(self, macropad, config):
|
||||||
|
print("app created")
|
||||||
|
self.macropad = macropad
|
||||||
|
self.config = config
|
||||||
|
self.name = "app"
|
||||||
|
self.state = AppState.STOPPED
|
||||||
|
self.display_group = DISPLAY
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
print("Start from base class ")
|
||||||
|
if self.state is not AppState.STOPPED:
|
||||||
|
raise InvalidStateUpdateError(f"Start called but the current app state is {self.state}")
|
||||||
|
self.state = AppState.STARTING
|
||||||
|
self._on_start()
|
||||||
|
self.on_start()
|
||||||
|
self.state = AppState.PAUSED
|
||||||
|
|
||||||
|
def _on_start(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
raise NotImplementedError("on_start not implemented")
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
if self.state is not AppState.PAUSED:
|
||||||
|
raise InvalidStateUpdateError(f"Resume called but the current app state is {self.state}")
|
||||||
|
self.state = AppState.RESUMING
|
||||||
|
self._on_resume()
|
||||||
|
self.on_resume()
|
||||||
|
self.state = AppState.RUNNING
|
||||||
|
|
||||||
|
def _on_resume(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_resume(self):
|
||||||
|
raise NotImplementedError("on_resume not implemented")
|
||||||
|
|
||||||
|
def pause(self):
|
||||||
|
if self.state is not AppState.RUNNING:
|
||||||
|
raise InvalidStateUpdateError(f"Pause called but the current app state is {self.state}")
|
||||||
|
self.state = AppState.PAUSING
|
||||||
|
self._on_pause()
|
||||||
|
self.on_pause()
|
||||||
|
self.state = AppState.PAUSED
|
||||||
|
|
||||||
|
def _on_pause(self):
|
||||||
|
self.macropad.keyboard.release_all()
|
||||||
|
self.macropad.consumer_control.release()
|
||||||
|
self.macropad.mouse.release_all()
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
self.macropad.pixels.show()
|
||||||
|
# self.macropad.display.refresh()
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
raise NotImplementedError("on_pause not implemented")
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
raise NotImplementedError("Not implemented")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
if self.state is not AppState.PAUSED:
|
||||||
|
raise InvalidStateUpdateError(f"Stop called but the current app state is {self.state}")
|
||||||
|
self.state = AppState.STOPPING
|
||||||
|
self._on_stop()
|
||||||
|
self.on_stop()
|
||||||
|
self.state = AppState.STOPPED
|
||||||
|
|
||||||
|
def _on_stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
raise NotImplementedError("on_stop not implemented.")
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from .app_state import AppState
|
||||||
|
from .options_app import OptionsApp
|
||||||
|
|
||||||
|
|
||||||
|
class AppRouter(object):
|
||||||
|
def __init__(self, macropad, config, apps):
|
||||||
|
print("app router")
|
||||||
|
self.macropad = macropad
|
||||||
|
self.app_index = 0
|
||||||
|
self.apps = apps
|
||||||
|
self.options = OptionsApp(macropad, config)
|
||||||
|
self.current_app = apps[self.app_index]
|
||||||
|
self.config = config
|
||||||
|
self.encoder_state = False
|
||||||
|
self.options_time = 1000000000 # .5 seconds in nanoseconds
|
||||||
|
self.click_time = 0
|
||||||
|
|
||||||
|
def swap_to_app(self, app):
|
||||||
|
print("Pausing current app")
|
||||||
|
self.current_app.pause()
|
||||||
|
print("Selecting new app")
|
||||||
|
self.current_app = app
|
||||||
|
if self.current_app.state is AppState.STOPPED:
|
||||||
|
print("Starting new app")
|
||||||
|
self.current_app.start()
|
||||||
|
if self.current_app.state is AppState.PAUSED:
|
||||||
|
print("Starting new app")
|
||||||
|
self.current_app.resume()
|
||||||
|
print(time.monotonic_ns())
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.current_app.start()
|
||||||
|
self.current_app.resume()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# detect if the current app is what should be running
|
||||||
|
# stop current app and start new one if not
|
||||||
|
self.current_app.loop()
|
||||||
|
# any other finite state machine logic that comes up
|
||||||
|
if self.macropad.encoder_switch_debounced.pressed and not self.encoder_state:
|
||||||
|
self.macropad.play_tone(1000, .1)
|
||||||
|
self.encoder_state = True
|
||||||
|
self.click_time = time.monotonic_ns()
|
||||||
|
|
||||||
|
elif self.macropad.encoder_switch_debounced.released and self.encoder_state:
|
||||||
|
self.encoder_state = False
|
||||||
|
if self.current_app is self.options:
|
||||||
|
print("Moving from options to the last opened app.")
|
||||||
|
else:
|
||||||
|
self.app_index += 1
|
||||||
|
print("Moving to the next app on the list. ")
|
||||||
|
self.swap_to_app(self.apps[self.app_index % len(self.apps)])
|
||||||
|
print("released encoder")
|
||||||
|
if self.encoder_state and self.click_time:
|
||||||
|
self.release_time = time.monotonic_ns()
|
||||||
|
print(self.release_time)
|
||||||
|
print(self.click_time)
|
||||||
|
print(self.release_time - self.click_time)
|
||||||
|
if (time.monotonic_ns() - self.click_time) > self.options_time:
|
||||||
|
self.macropad.play_tone(1000, .1)
|
||||||
|
self.swap_to_app(self.options)
|
||||||
|
self.encoder_state = False
|
||||||
|
self.macropad.encoder_switch_debounced.update()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
class InvalidStateUpdateError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AppState(object):
|
||||||
|
STARTING = "starting"
|
||||||
|
RESUMING = "resuming"
|
||||||
|
RUNNING = "running"
|
||||||
|
PAUSING = "pausing"
|
||||||
|
PAUSED = "paused"
|
||||||
|
STOPPING = "stopping"
|
||||||
|
STOPPED = "stopped"
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import terminalio
|
||||||
|
from adafruit_display_text import bitmap_label as label
|
||||||
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
|
from rainbowio import colorwheel
|
||||||
|
|
||||||
|
from .abstract_app import App
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_from_int(rgb):
|
||||||
|
blue = rgb & 255
|
||||||
|
green = (rgb >> 8) & 255
|
||||||
|
red = (rgb >> 16) & 255
|
||||||
|
return red, green, blue
|
||||||
|
|
||||||
|
|
||||||
|
class DebugApp(App):
|
||||||
|
|
||||||
|
def __init__(self, macropad, config, name):
|
||||||
|
super().__init__(macropad, config)
|
||||||
|
self.name = name
|
||||||
|
self.tones = [196, 220, 246, 262, 294, 330, 349, 392, 440, 494, 523, 587]
|
||||||
|
self.wheel_offset = 0
|
||||||
|
self.send_keyboard_inputs = 0
|
||||||
|
self.lit_keys = [False] * 12
|
||||||
|
self.labels = []
|
||||||
|
self.layout = GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=1)
|
||||||
|
self.title = label.Label(
|
||||||
|
y=4,
|
||||||
|
font=terminalio.FONT,
|
||||||
|
color=0x0,
|
||||||
|
text=f" {self.name} MENU ",
|
||||||
|
background_color=0xFFFFFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
print("on start from the app!")
|
||||||
|
self.lit_keys = [False] * 12
|
||||||
|
self.macropad.display.show(self.display_group)
|
||||||
|
for _ in range(12):
|
||||||
|
self.labels.append(label.Label(terminalio.FONT, text=""))
|
||||||
|
|
||||||
|
for index in range(12):
|
||||||
|
x = index % 3 + 1
|
||||||
|
y = index // 3
|
||||||
|
self.layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1))
|
||||||
|
|
||||||
|
|
||||||
|
def on_resume(self):
|
||||||
|
print("resume from the debug app!")
|
||||||
|
|
||||||
|
print(id(self.display_group))
|
||||||
|
print(self.display_group)
|
||||||
|
self.display_group.append(self.title)
|
||||||
|
self.display_group.append(self.layout)
|
||||||
|
self.macropad.display.show(self.display_group)
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
print("Pausing")
|
||||||
|
|
||||||
|
self.display_group.remove(self.title)
|
||||||
|
self.display_group.remove(self.layout)
|
||||||
|
# self.macropad.display.show(None)
|
||||||
|
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
print("Stopping")
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.process_key_presses()
|
||||||
|
self.light_keys()
|
||||||
|
|
||||||
|
def process_key_presses(self):
|
||||||
|
key_event = self.macropad.keys.events.get()
|
||||||
|
if key_event:
|
||||||
|
if key_event.pressed:
|
||||||
|
self.labels[key_event.key_number].text = "KEY{}".format(key_event.key_number)
|
||||||
|
print(self.macropad.keys)
|
||||||
|
self.lit_keys[key_event.key_number] = not self.lit_keys[key_event.key_number]
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
self.macropad.start_tone(self.tones[key_event.key_number])
|
||||||
|
else:
|
||||||
|
self.labels[key_event.key_number].text = ""
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
|
||||||
|
def light_keys(self):
|
||||||
|
self.wheel_offset += 1
|
||||||
|
for pixel in range(12):
|
||||||
|
if self.lit_keys[pixel]:
|
||||||
|
(r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset))
|
||||||
|
self.macropad.pixels[pixel] = (r * .1, g * .1, b * .1)
|
||||||
|
else:
|
||||||
|
self.macropad.pixels[pixel] = 0
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import terminalio
|
||||||
|
from adafruit_display_text import label
|
||||||
|
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
|
||||||
|
from rainbowio import colorwheel
|
||||||
|
|
||||||
|
from .abstract_app import App
|
||||||
|
from .debug_app import rgb_from_int
|
||||||
|
|
||||||
|
|
||||||
|
class OptionsApp(App):
|
||||||
|
|
||||||
|
def __init__(self, macropad, config):
|
||||||
|
super().__init__(macropad, config)
|
||||||
|
self.tones = [196, 220, 246, 262, 294, 330, 349, 392, 440, 494, 523, 587]
|
||||||
|
self.wheel_offset = 0
|
||||||
|
self.send_keyboard_inputs = 0
|
||||||
|
self.lit_keys = [False] * 12
|
||||||
|
self.labels = []
|
||||||
|
self.layout = GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 1), cell_padding=1)
|
||||||
|
self.title = label.Label(
|
||||||
|
y=4,
|
||||||
|
font=terminalio.FONT,
|
||||||
|
color=0x0,
|
||||||
|
text=f" OPTIONS MENU ",
|
||||||
|
background_color=0xFFFFFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
print("on start from the app!")
|
||||||
|
self.lit_keys = [False] * 4
|
||||||
|
# self.macropad.display.show(self.display_group)
|
||||||
|
for _ in range(4):
|
||||||
|
self.labels.append(label.Label(terminalio.FONT, text=""))
|
||||||
|
|
||||||
|
for index in range(4):
|
||||||
|
x = index % 3 + 1
|
||||||
|
y = index // 3
|
||||||
|
self.layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1))
|
||||||
|
|
||||||
|
def on_resume(self):
|
||||||
|
print("resume from the options app!")
|
||||||
|
self.display_group.append(self.title)
|
||||||
|
self.display_group.append(self.layout)
|
||||||
|
self.macropad.display.show(self.display_group)
|
||||||
|
print(self.display_group)
|
||||||
|
print(id(self.display_group))
|
||||||
|
|
||||||
|
def on_pause(self):
|
||||||
|
print("Pausing")
|
||||||
|
self.display_group.remove(self.title)
|
||||||
|
self.display_group.remove(self.layout)
|
||||||
|
|
||||||
|
def on_stop(self):
|
||||||
|
print("Stopping")
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
self.process_key_presses()
|
||||||
|
self.light_keys()
|
||||||
|
|
||||||
|
def process_key_presses(self):
|
||||||
|
key_event = self.macropad.keys.events.get()
|
||||||
|
if key_event:
|
||||||
|
if key_event.key_number < 4:
|
||||||
|
if key_event.pressed :
|
||||||
|
self.labels[key_event.key_number].text = "KEY{}".format(key_event.key_number)
|
||||||
|
print(self.macropad.keys)
|
||||||
|
self.lit_keys[key_event.key_number] = not self.lit_keys[key_event.key_number]
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
self.macropad.start_tone(self.tones[key_event.key_number])
|
||||||
|
else:
|
||||||
|
self.labels[key_event.key_number].text = ""
|
||||||
|
self.macropad.stop_tone()
|
||||||
|
|
||||||
|
def light_keys(self):
|
||||||
|
self.wheel_offset += 1
|
||||||
|
for pixel in range(4):
|
||||||
|
if self.lit_keys[pixel]:
|
||||||
|
(r, g, b) = rgb_from_int(colorwheel((pixel / 4 * 256) + self.wheel_offset))
|
||||||
|
self.macropad.pixels[pixel] = (r * .1, g * .1, b * .1)
|
||||||
|
else:
|
||||||
|
self.macropad.pixels[pixel] = 0
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import storage
|
||||||
|
|
||||||
|
from config import Config, ConfigVars
|
||||||
|
|
||||||
|
config = Config("config.json")
|
||||||
|
|
||||||
|
print("test")
|
||||||
|
print(config.data)
|
||||||
|
|
||||||
|
if not config.data.get(ConfigVars.DEV_MODE.name):
|
||||||
|
storage.disable_usb_drive()
|
||||||
|
print("file system should be writable")
|
||||||
|
storage.remount("/", False)
|
||||||
|
config.data[ConfigVars.DEV_MODE.name] = True
|
||||||
|
config.save()
|
||||||
|
print("success")
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import displayio
|
||||||
|
|
||||||
|
from app import AppRouter, DebugApp
|
||||||
|
from config import Config
|
||||||
|
from adafruit_macropad import MacroPad
|
||||||
|
|
||||||
|
macropad = MacroPad()
|
||||||
|
config = Config("config.json")
|
||||||
|
|
||||||
|
|
||||||
|
ar = AppRouter(macropad, config, [
|
||||||
|
DebugApp(macropad, config, "debug 1"),
|
||||||
|
# DebugApp(macropad, config, "debug 2"),
|
||||||
|
# DebugApp(macropad, config, "debug 3")
|
||||||
|
])
|
||||||
|
|
||||||
|
ar.start()
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"dev_mode": true,
|
||||||
|
"brightness": 100
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from .config_vars import ConfigVars, ConVar
|
||||||
|
from .config import Config
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from .config_vars import ConfigVars
|
||||||
|
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
def __init__(self, save_file):
|
||||||
|
self.data = {}
|
||||||
|
self.save_file = save_file
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
with open(self.save_file, "r") as f:
|
||||||
|
self.data = json.load(f)
|
||||||
|
for convar in ConfigVars.__dict__.keys():
|
||||||
|
print(convar)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with open(self.save_file, "w") as f:
|
||||||
|
json.dump(self.data, f)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class ConVar(object):
|
||||||
|
def __init__(self, name, default):
|
||||||
|
self.name = name
|
||||||
|
self.default = default
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigVars(object):
|
||||||
|
DEV_MODE = ConVar("dev_mode", True)
|
||||||
|
TEST_VAR = ConVar("test_var", False)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
xcopy .\* E:\ /s /d /exclude:excludedfileslist.txt /Y
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.\.idea\
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
adafruit_circuitpython_debouncer
|
||||||
|
adafruit_circuitpython_macropad
|
||||||
|
adafruit_circuitpython_pixelbuf
|
||||||
|
adafruit_circuitpython_simple_text_display
|
||||||
|
adafruit_circuitpython_ticks
|
||||||
|
neopixel
|
||||||
|
adafruit_circuitpython_bitmap_font
|
||||||
|
adafruit_circuitpython_display_text
|
||||||
|
adafruit_circuitpython_hid
|
||||||
|
adafruit_circuitpython_midi
|
||||||
|
adafruit_circuitpython_display_shapes
|
||||||
|
adafruit_circuitpython_displayio_layout
|
||||||
|
adafruit_circuitpython_imageload
|
||||||
Reference in New Issue
Block a user