khimaros / ambit

Take control of your Palette.
GNU General Public License v3.0
18 stars 2 forks source link

how can I add custom actions to ambit? #4

Open jafny opened 2 years ago

jafny commented 2 years ago

Let me start by saying thanks for this great repo, really excited to play around with it! One question I have is where can I register callbacks to execute when I get an Input? I see that I can use actionmaps for the executeCommand action when I receive input...but I was curious where I can define those callbacks.

Such as the below example: "rotation_left": { "action": "adjustTemperature", "behavior": "delta", "invert": true }

Where can I define what the adjustTemperature string argument maps to a callback function?

khimaros commented 2 years ago

hello @jafny -- this is a completely reasonable question!

the callback system is not quite as extensible/modular as i'd like and requires modifying a few different parts of the core ambit code in order to add a new native action. this is suboptimal and will require you to maintain patches against ambit and possibly deal with merge conflicts as the core code evolves. however, for posterity, the places are:

in the meantime, your best bet might actually be to use executeCommand with an external program written in any language of your choice. there are some examples of this in the included layouts: https://github.com/khimaros/ambit/tree/master/ambit/resources/layouts/multifunction-buttons

the executeCommand action is documented at https://github.com/khimaros/ambit/blob/master/docs/CONFIG.md#executecommand and usage would look something like:

{ "action": "executeCommand", "argv": [ "./example/scripts/temperature.sh" ], "limits": [50, 80] }

with the accompanying external program at https://github.com/khimaros/ambit/blob/master/example/scripts/temperature.sh

if you are interested in writing native modules in python or executeCommand doesn't fit your needs, please let me know. i can prioritize adding a plugin based system for this, as it was already on the roadmap.

khimaros commented 2 years ago

i can envision a few ways to support defining custom actions in ambit, i'm interested to hear your thoughts on these.

regardless, i'd like the end result to support using custom external actions in the layout like:

{ "action": "externalAction", "data": {"somekey": "somevalue"}, "limits": [50, 80] }

option 1: enable plugins in config file

this would allow loading of modules entirely from the config and would not require writing any python code except for the module. it would also make it easier to use community made modules. loading the plugin in the layout config would look like:

{
    "action_plugins": ["/path/to/external_action.py"],
    "module_mappings": {...}
}

and the module itself would look like:

class Behavior:
    items = [...]

class Actions:
    def externalAction(self, ctrl, value, data):
        ctrl.screen_string('E: %s %s' % (value, data['somekey']))

option 2: subclass ambit controller

this option would require creating a new main entrypoint like bin/ambit and executing that instead of the upstream bin. a downside is that this would require making your own copy of any such upstream you use such as bin/ambit_demoscene. it also means making more of the core ambit API stable across releases.

class CustomController(ambit.Controller):
    @ambit.Callback('externalAction')
    def external_action_callback(self, value, data):
        self.screen_string('E: %s %s' % (value, data['somekey']))

def main():
    config = ambit.StandardConfiguration()
    ctrl = CustomController(config)
    ...

option 3: register callbacks procedurally

same limitations as option 2.

def external_action_callback(ctrl, value, data):
   ctrl.screen_string('E: %s %s' % (value, data['somekey']))

def main():
    config = ambit.StandardConfiguration()
    ctrl = ambit.Controller(config)
    ctrl.register_external_action('externalAction', external_action_callback, items=[...])
    ...

arguably, we could have some combination of these, but curious which you'd prefer. in terms of ease of implementation for me, the order would be: option 3, 2, 1

jafny commented 2 years ago

Sorry for the delayed response! If I am picturing the easy option 3 I think it will work well for my use case. At it's most basic, I want to create a method of interfacing with a websocket API where an ambitt powered MonogramCC is my UI. If I can write all my websocket interactions as modules, and then use the proposed "register_external_action" callback to inject that into a button push or knob dial....that feels like it would work great.

khimaros commented 2 years ago

@jafny thank you for the feedback. i just want to confirm: does executeCommand fit your needs in the short term or is there something you'd like to do that isn't supported there?