diff --git a/code.py b/code.py index 8663e88..238c329 100644 --- a/code.py +++ b/code.py @@ -10,31 +10,14 @@ default_config = Config("default_config.json").load() config = Config("config.json").load(default_config) PYTHON_APP_FOLDER = "./macropad_apps/python" +JSON_APP_FOLDER = "./macropad_apps/json" -apps = [] - -files = os.listdir(PYTHON_APP_FOLDER) -files.sort() - -app_classes = [] -for filename in files: - if filename.endswith('.py') and not filename.startswith('._'): - try: - print(filename) - module = __import__(PYTHON_APP_FOLDER + '/' + filename[:-3]) - classes = [getattr(module, a) for a in dir(module) - if isinstance(getattr(module, a), type)] - for cls in classes: - if issubclass(cls, App) and cls.__name__ != "App": - app_classes.append(cls) - print(app_classes) - except (SyntaxError, ImportError, AttributeError, KeyError, NameError, IndexError, TypeError) as err: - print("ERROR in", filename) - import traceback - - traceback.print_exception(err, err, err.__traceback__) - -ar = MacropadOS(macropad, config, apps=app_classes) +ar = MacropadOS( + macropad, + config, + python_apps=PYTHON_APP_FOLDER, + json_apps=JSON_APP_FOLDER +) # sc = SerialComms(config) # _thread.start_new_thread(sc.run, (sc)) ar.start() diff --git a/macropad_apps/json/example.json b/macropad_apps/json/example.json deleted file mode 100644 index bf2a8eb..0000000 --- a/macropad_apps/json/example.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "profileName" : "", - "sortOrder" : 99, - - "key1" : { - "text" : [""], - "value" : [""] - }, - "key2" : { - "text" : [""], - "value" : [""] - }, - "key3" : { - "text" : [""], - "value" : [""] - }, - "key4" : { - "text" : [""], - "value" : [""] - }, - "key5" : { - "text" : [""], - "value" : [""] - }, - "key6" : { - "text" : [""], - "value" : [""] - }, - "key7" : { - "text" : [""], - "value" : [""] - }, - "key8" : { - "text" : [""], - "value" : [""] - }, - "key9" : { - "text" : [""], - "value" : [""] - }, - "key10" : { - "text" : [""], - "value" : [""] - }, - "key11" : { - "text" : [""], - "value" : [""] - }, - "key12" : { - "text" : [""], - "value" : [""] - } -} - diff --git a/macropad_apps/json/gamepad.json b/macropad_apps/json/gamepad.json index 643e585..559e738 100644 --- a/macropad_apps/json/gamepad.json +++ b/macropad_apps/json/gamepad.json @@ -1,54 +1,53 @@ { - "profileName" : "Gamepad", + "profileName" : "Gamepad Json", "sortOrder" : 1, - "key1" : { - "text" : ["ESC"], - "value" : ["ESCAPE"] + "text" : "ESC", + "values" : ["ESCAPE"] }, "key2" : { - "text" : ["Tab"], - "value" : ["TAB"] + "text" : "Tab", + "values" : ["TAB"] }, "key3" : { - "text" : ["R"], - "value" : ["R"] + "text" : "R", + "values" : ["R"] }, "key4" : { - "text" : ["Q"], - "value" : ["Q"] + "text" : "Q", + "values" : ["Q"] }, "key5" : { - "text" : ["W"], - "value" : ["W"] + "text" : "W", + "values" : ["W"] }, "key6" : { - "text" : ["E"], - "value" : ["E"] + "text" : "E", + "values" : ["E"] }, "key7" : { - "text" : ["A"], - "value" : ["A"] + "text" : "A", + "values" : ["A"] }, "key8" : { - "text" : ["S"], - "value" : ["S"] + "text" : "S", + "values" : ["S"] }, "key9" : { - "text" : ["D"], - "value" : ["D"] + "text" : "D", + "values" : ["D"] }, "key10" : { - "text" : ["Shift"], - "value" : [""] + "text" : "Shift", + "values" : ["SHIFT"] }, "key11" : { - "text" : ["C"], - "value" : ["C"] + "text" : "C", + "values" : ["C"] }, "key12" : { - "text" : ["Space"], - "value" : ["SPACEBAR"] + "text" : "Space", + "values" : ["SPACEBAR"] } } diff --git a/macropad_apps/json/numpad.json b/macropad_apps/json/numpad.json index bf7ed36..5bfca10 100644 --- a/macropad_apps/json/numpad.json +++ b/macropad_apps/json/numpad.json @@ -1,53 +1,52 @@ { - "profileName" : "NumPad", + "profileName" : "Numpad Json", "sortOrder" : 0, - "key1" : { - "text" : ["7"], - "value" : ["SEVEN"] + "text" : "7", + "values" : ["SEVEN"] }, "key2" : { - "text" : ["8"], - "value" : ["EIGHT"] + "text" : "8", + "values" : ["EIGHT"] }, "key3" : { - "text" : ["9"], - "value" : ["NINE"] + "text" : "9", + "values" : ["NINE"] }, "key4" : { - "text" : ["4"], - "value" : ["FOUR"] + "text" : "4", + "values" : ["FOUR"] }, "key5" : { - "text" : ["5"], - "value" : ["FIVE"] + "text" : "5", + "values" : ["FIVE"] }, "key6" : { - "text" : ["6"], - "value" : ["SIX"] + "text" : "6", + "values" : ["SIX"] }, "key7" : { - "text" : ["1"], - "value" : ["ONE"] + "text" : "1", + "values" : ["ONE"] }, "key8" : { - "text" : ["2"], - "value" : ["TWO"] + "text" : "2", + "values" : ["TWO"] }, "key9" : { - "text" : ["3"], - "value" : ["THREE"] + "text" : "3", + "values" : ["THREE"] }, "key10" : { - "text" : ["0"], - "value" : ["ZERO"] + "text" : "0", + "values" : ["ZERO"] }, "key11" : { - "text" : ["."], - "value" : ["PERIOD"] + "text" : ".", + "values" : ["PERIOD"] }, "key12" : { - "text" : ["ENT"], - "value" : ["RETURN"] + "text" : "ENT", + "values" : ["RETURN"] } } diff --git a/macropad_apps/json_format.json b/macropad_apps/json_format.json new file mode 100644 index 0000000..03de17d --- /dev/null +++ b/macropad_apps/json_format.json @@ -0,0 +1,54 @@ +{ + "profileName" : "", + "sortOrder" : 99, + + "key1" : { + "text" : "", + "values" : [""] + }, + "key2" : { + "text" : "", + "values" : [""] + }, + "key3" : { + "text" : "", + "values" : [""] + }, + "key4" : { + "text" : "", + "values" : [""] + }, + "key5" : { + "text" : "", + "values" : [""] + }, + "key6" : { + "text" : "", + "values" : [""] + }, + "key7" : { + "text" : "", + "values" : [""] + }, + "key8" : { + "text" : "", + "values" : [""] + }, + "key9" : { + "text" : "", + "values" : [""] + }, + "key10" : { + "text" : "", + "values" : [""] + }, + "key11" : { + "text" : "", + "values" : [""] + }, + "key12" : { + "text" : "", + "values" : [""] + } +} + diff --git a/macropad_apps/python/numpad.py b/macropad_apps/python/numpad.py index 2961ea1..9ab812d 100644 --- a/macropad_apps/python/numpad.py +++ b/macropad_apps/python/numpad.py @@ -16,14 +16,12 @@ COLOR_UPDATE_RATE = 33000000 # .033 seconds class NumpadApp(App): def __init__(self, macropad, config): super().__init__(macropad, config) - self.name = "Numpad" + self.name = "NumpadApp" self.wheel_offset = 0 - self.lit_keys = [False] * 12 self.labels = [] self.title = "Numpad" self.modifier_pressed = False self.last_color_update = 0 - self.modifier_pressed = False self.macros = MacroSet( [ Macro("7", Keycode.SEVEN), Macro("8", Keycode.EIGHT), Macro("9", Keycode.NINE), @@ -36,7 +34,7 @@ class NumpadApp(App): encoder_down=Macro("-", Keycode.KEYPAD_MINUS), ) self.mod_macros = MacroSet([ - Macro("<", Keycode.SHIFT, Keycode.COMMA), Macro(">", Keycode.SHIFT, Keycode.PERIOD), Macro("&", Keycode.SHIFT, Keycode.SEVEN), + Macro("<", Keycode.SHIFT, Keycode.COMMA), Macro(">", Keycode.SHIFT, Keycode.PERIOD), Macro("BACKSP", Keycode.BACKSPACE), Macro("(", Keycode.SHIFT, Keycode.NINE), Macro(")", Keycode.SHIFT, Keycode.ZERO),Macro("%", Keycode.SHIFT, Keycode.FIVE), @@ -62,7 +60,6 @@ class NumpadApp(App): print("on start from the app!") self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1)) self.set_title(self.title) - self.lit_keys = [True] * 12 for i in range(12): self.labels.append(Label(terminalio.FONT, text=self.active_macros.get_macro_from_key(i).name)) for index in range(12): @@ -93,11 +90,8 @@ class NumpadApp(App): if last_update_ago > COLOR_UPDATE_RATE: self.last_color_update = monotonic_ns() for pixel in range(12): - if self.lit_keys[pixel]: - (r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset)) - colors.append((r, g, b)) - else: - colors.append((0, 0, 0)) + (r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset)) + colors.append((r, g, b)) self.set_colors(colors) def process_keys_pressed_callback(self, key_event): diff --git a/macropad_os/abstract_app.py b/macropad_os/abstract_app.py index 5218ba8..329fe17 100644 --- a/macropad_os/abstract_app.py +++ b/macropad_os/abstract_app.py @@ -143,12 +143,13 @@ class App(object): def _stop_tone_for_key(self, key_number): self.macropad.stop_tone() - self._pressed_keys.remove(key_number) - if self._pressed_keys and self.config.key_tone_enabled(): - if key_number in self._key_tones: - self.macropad.start_tone(self._key_tones[self._pressed_keys[0]]) - else: - self.macropad.start_tone(self.config.key_tone_hz()) + if key_number in self._pressed_keys: + self._pressed_keys.remove(key_number) + if self._pressed_keys and self.config.key_tone_enabled(): + if key_number in self._key_tones: + self.macropad.start_tone(self._key_tones[self._pressed_keys[0]]) + else: + self.macropad.start_tone(self.config.key_tone_hz()) def _process_wheel_changes(self) -> None: encoder = self.macropad.encoder diff --git a/macropad_os/macropad_os.py b/macropad_os/macropad_os.py index 41c5aac..ab4101e 100644 --- a/macropad_os/macropad_os.py +++ b/macropad_os/macropad_os.py @@ -1,23 +1,29 @@ -import time +import time, os + +from . import App from .app_state import AppState -from macropad_os.system_apps import OptionsApp, DebugApp +from macropad_os.system_apps import OptionsApp, DebugApp, JsonApp class MacropadOS(object): - def __init__(self, macropad, config, apps): + def __init__(self, macropad, config, python_apps: str, json_apps: str): print("app router") self.macropad = macropad self.app_index = 0 - self.apps = [a(macropad, config) for a in apps] self.options = OptionsApp(macropad, config) - self.current_app = self.apps[self.app_index] self.config = config self.encoder_state = False self.options_time = 500000000 # .5 seconds in nanoseconds self.click_time = 0 self.debug_app = DebugApp(macropad, config, "DEBUG APP") self.debug_app_active = self.config.debug_app_enabled() + self.python_apps_location = python_apps + self.json_apps_location = json_apps + self.apps: [App] = [] + self.load_python_apps() + self.load_json_apps() + self.current_app = self.apps[self.app_index] if self.debug_app_active: self.apps.append(self.debug_app) @@ -38,6 +44,39 @@ class MacropadOS(object): print("Starting new app") self.current_app.resume() + def load_python_apps(self): + app_classes = [] + for filename in sorted(os.listdir(self.python_apps_location)): + if filename.endswith('.py') and not filename.startswith('._'): + try: + print(filename) + module = __import__(self.python_apps_location + '/' + filename[:-3]) + classes = [getattr(module, a) for a in dir(module) + if isinstance(getattr(module, a), type)] + for cls in classes: + if issubclass(cls, App) and cls.__name__ != "App": + app_classes.append(cls) + print(app_classes) + except (SyntaxError, ImportError, AttributeError, KeyError, NameError, IndexError, TypeError) as err: + print("ERROR in", filename) + import traceback + traceback.print_exception(err, err, err.__traceback__) + self.apps.extend(a(self.macropad, self.config) for a in app_classes) + + def load_json_apps(self): + json_apps = [] + for filename in sorted(os.listdir(self.json_apps_location)): + if filename.endswith('.json'): + json_apps.append( + JsonApp( + self.macropad, + self.config, + self.json_apps_location + "/" + filename + ) + ) + json_apps.sort(key=lambda x: x.sort_order) + self.apps.extend(json_apps) + def start(self) -> None: print(self.current_app) self.current_app.start() diff --git a/macropad_os/system_apps/__init__.py b/macropad_os/system_apps/__init__.py index e7da3a5..9c23b0f 100644 --- a/macropad_os/system_apps/__init__.py +++ b/macropad_os/system_apps/__init__.py @@ -1,2 +1,3 @@ from .debug_app import DebugApp -from .options_app import OptionsApp \ No newline at end of file +from .options_app import OptionsApp +from .json_app import JsonApp \ No newline at end of file diff --git a/macropad_os/system_apps/json_app.py b/macropad_os/system_apps/json_app.py new file mode 100644 index 0000000..a935c7c --- /dev/null +++ b/macropad_os/system_apps/json_app.py @@ -0,0 +1,134 @@ +import terminalio +import json + +from time import monotonic_ns + +from adafruit_display_text.bitmap_label import Label +from adafruit_displayio_layout.layouts.grid_layout import GridLayout +from adafruit_hid.keycode import Keycode +from macropad_os import App +from macropad_os.app_utils import rgb_from_int, Macro, MacroSet +from rainbowio import colorwheel + +COLOR_UPDATE_RATE = 33000000 # .033 seconds + +JSON_FILE_LOCATION = "./macropad_apps/json/" + +PROFILE_NAME = "profileName" +SORT_ORDER = "sortOrder" +KEY_KEYS = ["key1", "key2", "key3", "key4", "key5", "key6", "key7", "key8", "key9", "key10", "key11", "key12"] +REQUIRED_KEYS = KEY_KEYS.copy() +REQUIRED_KEYS.extend(x for x in [PROFILE_NAME, SORT_ORDER]) + + +class JsonApp(App): + def __init__(self, macropad, config, json_file): + super().__init__(macropad, config) + self.json_file = json_file + self.wheel_offset = 0 + self.labels = [] + self.title = "" + self.last_color_update = 0 + self.active_macro = 0 + self.name = "JSON_APP" + self.json_dict = JsonApp.read_json_file(self.json_file) + self.macros = JsonApp.get_macroset_from_dict(self.json_dict) + self.title = self.json_dict[PROFILE_NAME] + self.sort_order = self.json_dict[SORT_ORDER] + + @staticmethod + def create_macro_from_dict(key_dict) -> Macro: + print(f"Creating macro for {key_dict}") + name = key_dict["text"] + values = key_dict["values"] + codes = [] + for value in values: + if isinstance(value, str): + value_mod = 1 + if value[0] is '-': + value_mod = -1 + value = value[1:] + if hasattr(Keycode, value): + keycode = getattr(Keycode, value) + codes.append(keycode * value_mod) + else: + codes.append(value) + if isinstance(value, int) or isinstance(value, float) or isinstance(value, dict) or isinstance(value, list): + # TODO: Add validation for lists and dictionaries + codes.append(value) + print(name, codes) + return Macro(name, *codes) + + @staticmethod + def get_macroset_from_dict(json_dict: {}) -> MacroSet: + key_macros = [] + for key in KEY_KEYS: + key_macros.append(JsonApp.create_macro_from_dict(json_dict[key])) + return MacroSet(key_macros=key_macros, encoder_up=Macro("Test"), encoder_down=Macro("Test")) + + @staticmethod + def read_json_file(json_file) -> {}: + try: + f = open(json_file, "r") + json_dict = json.load(f) + print(json_dict) + for key in REQUIRED_KEYS: + if key not in json_dict: + raise ValueError(f"JSON FOR FILE - {json_file} IS INVALID - KEY MISSING {key}") + return json_dict + except Exception as e: + print(e) + + def on_start(self): + print("on start from the app!") + self.set_layout(GridLayout(x=0, y=9, width=128, height=54, grid_size=(3, 4), cell_padding=1)) + self.set_title(self.title) + for i in range(12): + self.labels.append( + Label(terminalio.FONT, text=self.macros.get_macro_from_key(i).name)) + 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.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!") + + def on_pause(self): + print("Pausing") + + def on_stop(self): + print("Stopping") + + def on_loop(self): + self.update_key_colors() + + def update_key_colors(self): + self.wheel_offset += 1 + colors = [] + last_update_ago = monotonic_ns() - self.last_color_update + if last_update_ago > COLOR_UPDATE_RATE: + self.last_color_update = monotonic_ns() + for pixel in range(12): + (r, g, b) = rgb_from_int(colorwheel((pixel / 12 * 256) + self.wheel_offset)) + colors.append((r, g, b)) + self.set_colors(colors) + + def process_keys_pressed_callback(self, key_event): + print(f"KEY PRESSED - {key_event}") + print(self.macros) + macro = self.macros.get_macro_from_key(key_event) + print(macro) + print(macro.name) + print(macro.codes) + + self.press_macro(self.macros.get_macro_from_key(key_event)) + + def process_keys_released_callback(self, key_event): + self.release_macro(self.macros.get_macro_from_key(key_event)) + + def process_enbcoder_changed(self, key_event): + print(key_event)