From f62750143620565c927d91e030a8d03848ca6895 Mon Sep 17 00:00:00 2001 From: Lucas Date: Sat, 7 May 2022 19:11:52 -0400 Subject: [PATCH] finished making Macropad OS 1.0 --- config/config.py | 1 + macro_pad_apps/example | 9 +- macropad_os/abstract_app.py | 165 +++++++++++++++++- macropad_os/app_router.py | 1 - macropad_os/default_apps/common/__init__.py | 3 +- .../default_apps/common/helper_funcs.py | 0 .../default_apps/common/light_patterns.py | 24 +-- macropad_os/default_apps/debug_app.py | 77 ++++---- macropad_os/default_apps/options_app.py | 61 +++---- 9 files changed, 233 insertions(+), 108 deletions(-) create mode 100644 macropad_os/default_apps/common/helper_funcs.py diff --git a/config/config.py b/config/config.py index dd53fb5..a407cec 100644 --- a/config/config.py +++ b/config/config.py @@ -18,3 +18,4 @@ class Config(object): def save(self): with open(self.save_file, "w") as f: json.dump(self.data, f) + diff --git a/macro_pad_apps/example b/macro_pad_apps/example index 6a92cf0..d3bacde 100644 --- a/macro_pad_apps/example +++ b/macro_pad_apps/example @@ -4,7 +4,10 @@ "k2":["TWO"], "k3":["THREE"], "k4":["ONE"], - "k5":["TWO"], + "k5":{ + "keys":["TWO"], + "name":"" + } "k6":["THREE"], "k7":["ONE"], "k8":["TWO"], @@ -12,4 +15,6 @@ "k10":["ONE"], "k11":["TWO"], "k12":["THREE"], -} \ No newline at end of file +} + +example.k1.name \ No newline at end of file diff --git a/macropad_os/abstract_app.py b/macropad_os/abstract_app.py index ab2338c..921b814 100644 --- a/macropad_os/abstract_app.py +++ b/macropad_os/abstract_app.py @@ -1,20 +1,50 @@ import displayio +import terminalio +from adafruit_display_text import label +from adafruit_hid.keycode import Keycode from .app_state import AppState, InvalidStateUpdateError - - DISPLAY = displayio.Group() + +def convert_to_keynum(x, y): + return 3 * x + y + + class App(object): def __init__(self, macropad, config): + """ + + :param macropad: + :param config: + """ print("app created") + self._key_lights = [(0, 0, 0) for _ in range(12)] + self._display_group = DISPLAY + self._title = "title" + self._layout = None + self._title_label = label.Label( + y=4, + font=terminalio.FONT, + color=0x0, + text=f"", + background_color=0xFFFFFF, + ) + self._key_tones = [config.data["default_tone"] for _ in range(12)] + self._enabled_key_sounds = True + self._pressed_keys = [] + self._key_pressed_callbacks = [] + self._key_released_callbacks = [] + self._encoder_changed_callbacks = [] + self._encoder_state = 0 + + 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 ") @@ -40,7 +70,7 @@ class App(object): self.state = AppState.RUNNING def _on_resume(self): - pass + self.add_displays_to_group() def on_resume(self): raise NotImplementedError("on_resume not implemented") @@ -59,14 +89,55 @@ class App(object): self.macropad.mouse.release_all() self.macropad.stop_tone() self.macropad.pixels.show() - # self.macropad.display.refresh() + self.remove_displays_from_group() def on_pause(self): raise NotImplementedError("on_pause not implemented") def loop(self): + # We'll fire you if you override this method. + self._on_loop() + self.on_loop() + + def on_loop(self): raise NotImplementedError("Not implemented") + def _on_loop(self): + self._update_lighting() + self._process_keys_pressed() + self._process_wheel_changes() + self.on_loop() + + def _process_keys_pressed(self): + key_event = self.macropad.keys.events.get() + if key_event: + if key_event.key_number < 12: + if key_event.pressed: + self.macropad.stop_tone() + self.macropad.start_tone(self._key_tones[key_event.key_number]) + self._pressed_keys.append(key_event.key_number) + if self._key_pressed_callbacks: + for callback in self._key_pressed_callbacks: + callback(key_event.key_number) + else: + self.macropad.stop_tone() + self._pressed_keys.remove(key_event.key_number) + if self._pressed_keys: + self.macropad.start_tone(self._key_tones[self._pressed_keys[0]]) + if self._key_released_callbacks: + for callback in self._key_pressed_callbacks: + callback(key_event.key_number) + + def _process_wheel_changes(self): + encoder = self.macropad.encoder + if self._encoder_state != encoder: + for callback in self._encoder_changed_callbacks: + if self._encoder_state > encoder: + callback(-1) + else: + callback(1) + self._encoder_state = encoder + def stop(self): if self.state is not AppState.PAUSED: raise InvalidStateUpdateError(f"Stop called but the current app state is {self.state}") @@ -80,3 +151,87 @@ class App(object): def on_stop(self): raise NotImplementedError("on_stop not implemented.") + + def _update_lighting(self): + for index, color in enumerate(self._key_lights): + self.macropad.pixels[index] = color + + def add_displays_to_group(self): + self._display_group.append(self._title_label) + self._display_group.append(self._layout) + self.macropad.display.show(self._display_group) + + def remove_displays_from_group(self): + self._display_group.remove(self._title_label) + self._display_group.remove(self._layout) + + def set_color(self, x, y, color): + key_value = convert_to_keynum(x, y) + if key_value >= 12: + raise ValueError("color index out of range") + if len(color) != 3: + self._key_lights[key_value] = color + + def set_colors(self, colors): + if len(colors) != 12: + raise ValueError("Colors must be passed in as a 12 len array") + for color in colors: + if len(color) != 3: + raise ValueError("Color format error - color must be length 3") + self._key_lights = colors + + def get_colors(self): + return self._key_lights + + def set_tone(self, keypad_num, tone): + if keypad_num >= 12: + raise ValueError("Tone index out of range") + if tone < 20 or tone > 20000: + raise ValueError("Tone format error - tone out of human hearing range (20 - 20000)") + self._key_tones[keypad_num] = tone + + def set_tones(self, tones): + if len(tones) != 12: + raise ValueError("Tones must be passed in as a 12 len array") + for tone in tones: + if tone < 20 or tone > 20000: + raise ValueError("Tone format error - tone out of human hearing range (20 - 20000)") + self._key_tones = tones + + def set_tone_status(self, enable): + self._enabled_key_sounds = enable + + def get_tones(self): + return self._key_tones + + def set_title(self, title): + """ + :string title: Title of your app - shown in the top bar. + + :return: None + """ + if len(title) > 22: + # TODO: Update this to be able to scroll the display if too long? + raise ValueError("Title too long to be displayed on screen") + self._title = title + diff = int((22 - len(title)) / 2) + self._title_label.text = f"{''.join([' ' for _ in range(diff)])}{self._title}{''.join([' ' for _ in range(diff)])}" + + def set_layout(self, layout): + """ + :displayio.Group layout: + + :return: + """ + self._layout = layout + + def register_on_key_pressed(self, function): + self._key_pressed_callbacks.append(function) + + def register_on_key_released(self, function): + self._key_released_callbacks.append(function) + + def register_on_encoder_changed(self, function): + self._encoder_changed_callbacks.append(function) + + diff --git a/macropad_os/app_router.py b/macropad_os/app_router.py index afddd3c..3b46eaf 100644 --- a/macropad_os/app_router.py +++ b/macropad_os/app_router.py @@ -33,7 +33,6 @@ class AppRouter(object): 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() diff --git a/macropad_os/default_apps/common/__init__.py b/macropad_os/default_apps/common/__init__.py index a760168..23bd648 100644 --- a/macropad_os/default_apps/common/__init__.py +++ b/macropad_os/default_apps/common/__init__.py @@ -1,2 +1,3 @@ from .light_patterns import * -from .sound_patterns import * \ No newline at end of file +from .sound_patterns import * +from .helper_funcs import * \ No newline at end of file diff --git a/macropad_os/default_apps/common/helper_funcs.py b/macropad_os/default_apps/common/helper_funcs.py new file mode 100644 index 0000000..e69de29 diff --git a/macropad_os/default_apps/common/light_patterns.py b/macropad_os/default_apps/common/light_patterns.py index 86f5cc8..3df7315 100644 --- a/macropad_os/default_apps/common/light_patterns.py +++ b/macropad_os/default_apps/common/light_patterns.py @@ -1,20 +1,20 @@ arrows_with_enter = [ - (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 100), (0, 0, 0), - (0, 0, 100), (100, 0, 0), (0, 0, 100), - (0, 0, 0), (0, 0, 100), (0, 0, 0) + [(0, 0, 0), (0, 0, 0), (0, 0, 0)], + [(0, 0, 0), (0, 0, 100), (0, 0, 0)], + [(0, 0, 100), (100, 0, 0), (0, 0, 100)], + [(0, 0, 0), (0, 0, 100), (0, 0, 0)] ] up_down_enter = [ - (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 100), (0, 0, 0), - (0, 0, 0), (100, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 100), (0, 0, 0) + [(0, 0, 0), (0, 0, 0), (0, 0, 0)], + [(0, 0, 0), (0, 0, 100), (0, 0, 0)], + [(0, 0, 0), (100, 0, 0), (0, 0, 0)], + [(0, 0, 0), (0, 0, 100), (0, 0, 0)] ] arrows_yes_no = [ - (0, 0, 0), (0, 0, 0), (0, 0, 0), - (0, 0, 0), (0, 0, 100), (0, 0, 0), - (0, 0, 0), (100, 0, 0), (0, 0, 0), - (0, 100, 0), (0, 0, 100), (100, 0, 0) + [(0, 0, 0), (0, 0, 0), (0, 0, 0)], + [(0, 0, 0), (0, 0, 100), (0, 0, 0)], + [(0, 0, 0), (100, 0, 0), (0, 0, 0)], + [(0, 100, 0), (0, 0, 100), (100, 0, 0)] ] diff --git a/macropad_os/default_apps/debug_app.py b/macropad_os/default_apps/debug_app.py index 5b03f96..1f68e09 100644 --- a/macropad_os/default_apps/debug_app.py +++ b/macropad_os/default_apps/debug_app.py @@ -18,74 +18,59 @@ 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, - ) + self.title = "Debug App!" def on_start(self): print("on start from the app!") - self.lit_keys = [False] * 12 - self.macropad.display.show(self.display_group) + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=1)) + self.set_title(self.title) + self.lit_keys = [True] * 12 for _ in range(12): self.labels.append(label.Label(terminalio.FONT, text="")) - for index in range(12): - x = index % 3 - y = index // 3 - self.layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1)) - + # for index in range(12): + # x = index % 3 + # y = index // 3 + # self.layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(1, 1)) + self.set_tone_status(True) + self.set_tones([196, 220, 246, 262, 294, 330, 349, 392, 440, 494, 523, 587]) + self.register_on_key_pressed(self.process_keys_pressed_callback) + self.register_on_key_released(self.process_keys_released_callback) + self.register_on_encoder_changed(self.process_enbcoder_changed) 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) - def on_stop(self): print("Stopping") - def loop(self): - self.process_key_presses() - self.light_keys() - self.light_keys() + def on_loop(self): + self.update_key_colors() - 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): + def update_key_colors(self): self.wheel_offset += 1 + colors = self.get_colors() 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) + colors[pixel] = (r, g, b) else: - self.macropad.pixels[pixel] = 0 + colors[pixel] = 0 + self.set_colors(colors) + + def process_keys_pressed_callback(self, keyevent): + print("PROCESS KEYS CALLBACK FROM DEBUG") + print(keyevent) + + def process_keys_released_callback(self, keyevent): + print("PROCESS KEYS RELEASED CALLBACK FROM DEBUG") + print(keyevent) + def process_enbcoder_changed(self, keyevent): + print("PROCESS Encoder Changed Callback FROM DEBUG") + print(keyevent) diff --git a/macropad_os/default_apps/options_app.py b/macropad_os/default_apps/options_app.py index 0be842f..399dd5d 100644 --- a/macropad_os/default_apps/options_app.py +++ b/macropad_os/default_apps/options_app.py @@ -1,5 +1,3 @@ -import terminalio -from adafruit_display_text import label from adafruit_displayio_layout.layouts.grid_layout import GridLayout from .common import arrows_yes_no, arrows_with_enter, up_down_enter @@ -12,59 +10,40 @@ class OptionsApp(App): super().__init__(macropad, config) self.send_keyboard_inputs = 0 self.labels = [] - self.layout = GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=1) self.key_colors = arrows_yes_no self.possible_key_colors = [arrows_yes_no, arrows_with_enter, up_down_enter] - self.title = label.Label( - y=4, - font=terminalio.FONT, - color=0x0, - text=f" OPTIONS MENU ", - background_color=0xFFFFFF, - ) self.counter = 0 + self.title = "Options" def on_start(self): - print("on start from the app!") - self.lit_keys = [False] * 4 - for _ in range(4): - self.labels.append(label.Label(terminalio.FONT, text="")) - - for index in range(4): - x = 0 - y = index - self.layout.add_content(self.labels[index], grid_position=(x, y), cell_size=(3, 1)) + print("on start from the options app!") + self.set_title(self.title) + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(4, 4), cell_padding=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) 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() - self.key_colors = self.possible_key_colors[self.counter % len(self.possible_key_colors)] - self.counter+=1 + def on_loop(self): + pass + + def on_key_pressed(self, keyevent): + print("ON KEY PRESSED CALLBACK FROM OPTIONS") + print(keyevent) + + def on_key_released(self, keyevent): + print("ON KEY RELEASED CALLBACK FROM OPTIONS") + print(keyevent) + + def on_wheel_change(self, event): + print("ON WHEEL CHANGED CALLBACK") + print(event) + - def process_key_presses(self): - key_event = self.macropad.keys.events.get() - if key_event: - if key_event.key_number < 12: - if key_event.pressed: - self.macropad.stop_tone() - self.macropad.start_tone(440) - else: - self.macropad.stop_tone() - def light_keys(self): - for pixel in range(12): - self.macropad.pixels[pixel] = self.key_colors[pixel]