-
Notifications
You must be signed in to change notification settings - Fork 5
module api 2.1 docs #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
67f7670
first pass of 2.1 docs
Julusian 0ad03ae
wip: continue
Julusian d2ed55c
full docs for 2.1 module api
Julusian 4b21302
add notes about image variables
Julusian 6f979ba
Merge branch 'main' into feat/module-api-2.1
Julusian ee6cbcf
chore(deps): bump actions/checkout from 6 to 7 (#69)
dependabot[bot] deb41a2
Sync user-guide from companion
Julusian 5b69fcc
update
Julusian 248aef9
Merge branch 'main' into feat/module-api-2.1
Julusian e996ecc
review
Julusian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,313 @@ | ||
| --- | ||
| title: API 2.1 (Companion 5.0+) | ||
| sidebar_position: -210 | ||
| description: 'Overview of API 2.1 changes (abort signals, action results, graphics overhaul with gauges, preset alternatives, affectedProperties, internal actions and feedback-driven local variables in presets).' | ||
| --- | ||
|
|
||
| API 2.1 builds on [API 2.0](v2.0.md) with new capabilities and some TypeScript-level type improvements. There are no runtime breaking changes. | ||
|
|
||
| If you haven't migrated to API 2.0 yet, start there — it is a prerequisite for 2.1. | ||
|
|
||
| ## TypeScript: type safety for `subscribe` hooks | ||
|
|
||
| When using a `subscribe` callback on an action, `optionsToMonitorForSubscribe` is now **required** in the TypeScript types rather than optional. | ||
|
|
||
| Before: | ||
|
|
||
| ```ts | ||
| { | ||
| name: 'My Action', | ||
| options: [...], | ||
| callback: async (action) => { ... }, | ||
| subscribe: async (action) => { ... }, | ||
| // optionsToMonitorForSubscribe was optional but recommended | ||
| } | ||
| ``` | ||
|
|
||
| After: | ||
|
|
||
| ```ts | ||
| { | ||
| name: 'My Action', | ||
| options: [...], | ||
| callback: async (action) => { ... }, | ||
| subscribe: async (action) => { ... }, | ||
| optionsToMonitorForSubscribe: ['field1', 'field2'], // now required when subscribe is present | ||
| } | ||
| ``` | ||
|
|
||
| This is a TypeScript-only change. There is no runtime impact; if your module does not use TypeScript, or you already set `optionsToMonitorForSubscribe` on every action that uses `subscribe`, no changes are needed. | ||
|
|
||
| For a refresher on why `optionsToMonitorForSubscribe` matters with expressions, see the [API 2.0 guide](v2.0.md#expression-handling-in-options). | ||
|
|
||
| ## Abort signals | ||
|
|
||
| Companion now passes an `AbortSignal` through the callback context of actions, feedbacks, and learn operations, so long-running work can be cancelled when its result is no longer needed. Each callback receives a `context.signal`: | ||
|
|
||
| - **Actions** — aborts when the result of this execution is no longer needed (for example, the user aborted the actions currently running on a button). | ||
| - **Feedbacks** — aborts when a recheck of the feedback is queued while it is still executing, meaning the in-progress result is about to be superseded. | ||
| - **Learn** — aborts when the user cancels an in-progress learn before it finishes. | ||
|
|
||
| ```ts | ||
| // Action | ||
| callback: async (action, context) => { | ||
| await myDevice.send(action.options.command, { signal: context.signal }) | ||
| }, | ||
|
|
||
| // Feedback | ||
| callback: async (feedback, context) => { | ||
| const value = await myDevice.query(feedback.options.channel, { signal: context.signal }) | ||
| return value > 0 | ||
| }, | ||
|
|
||
| // Learn | ||
| learn: async (action, context) => { | ||
| const response = await fetch('http://device.local/settings', { signal: context.signal }) | ||
| if (context.signal.aborted) return undefined | ||
|
|
||
| const data = await response.json() | ||
| return { setting: data.value } | ||
| }, | ||
| ``` | ||
|
|
||
| Respecting the signal is optional. If you do choose to honour it, stop your work and throw — the thrown error is ignored, and for feedbacks a fresh check is then performed. If a callback is short or synchronous, you can safely ignore `context.signal` entirely. | ||
|
|
||
| ## Action callbacks can return a result value | ||
|
|
||
| Actions can now declare that they produce a result. When an action returns a value, Companion can store it in a local or custom variable of the user's choosing, making it available to subsequent actions in the same sequence. | ||
|
|
||
| Add `hasResult: true` to your action definition and return a `JsonValue` from the callback: | ||
|
|
||
| ```ts | ||
| { | ||
| name: 'Read current value', | ||
| options: [ | ||
| { | ||
| id: 'channel', | ||
| type: 'number', | ||
| label: 'Channel', | ||
| default: 1, | ||
| min: 1, | ||
| max: 64, | ||
| }, | ||
| ], | ||
| hasResult: true, | ||
| callback: async (action, context) => { | ||
| const level = await myDevice.getLevel(action.options.channel) | ||
| return level // a number, string, boolean, null, array, or object | ||
| }, | ||
| } | ||
| ``` | ||
|
|
||
| In Companion's UI, the user will have the option to nominate a local or custom variable to receive the returned value after each execution. | ||
|
|
||
| :::tip | ||
|
|
||
| This pairs well with the expression support introduced in [API 2.0](v2.0.md#automatic-expression-parsing). The stored result can be referenced in later action options via the variable name the user chose. | ||
|
|
||
| ::: | ||
|
|
||
| ## Graphics overhaul | ||
|
|
||
| ### Layered presets | ||
|
|
||
| A new `layered` preset type is available. It lets modules describe button graphics using the same element-based system Companion uses internally, without needing to produce raw image buffers via advanced feedbacks. | ||
|
|
||
| Available element types: | ||
|
|
||
| - **Text** — formatted text label | ||
| - **Image** — raster image | ||
| - **Box** — filled or stroked rectangle | ||
| - **Line** — line segment | ||
| - **Circle** — filled or stroked circle | ||
| - **Gauge** — value-driven bar/ring meter | ||
| - **Group** — a named collection of the above | ||
| - **Composite** — a reusable element defined by your module (see below) | ||
|
|
||
| Existing `simple` presets are unaffected and continue to work as before. The `layered` type is an addition for cases where richer graphics are needed. | ||
|
|
||
| :::tip | ||
|
|
||
| We recommend continuing to use `simple` presets whenever possible, for compatibility with Bitfocus Buttons | ||
|
|
||
| ::: | ||
|
|
||
| ### Composite elements | ||
|
|
||
| Modules can now define reusable composite drawing elements using `setCompositeElementDefinitions`. A composite is a named component built from simpler drawing elements. They can be referenced in layered presets, and users can also add them to their own buttons through the Companion UI. | ||
|
|
||
| ```ts | ||
| this.setCompositeElementDefinitions({ | ||
| 'signal-indicator': { | ||
| name: 'Signal Indicator', | ||
| // ...element composition | ||
| }, | ||
| }) | ||
| ``` | ||
|
|
||
| This is intended as a more structured replacement for the "produce a bitmap in an advanced feedback" pattern, keeping your graphics logic encapsulated and reusable. | ||
|
|
||
| Composite element definitions and layered preset data are strictly validated on receipt, so invalid definitions produce clear warnings in the module debug log rather than silently corrupting button graphics. | ||
|
|
||
| ## Presets: reference Companion's internal actions and feedbacks | ||
|
|
||
| Presets can now reference a small, reserved set of Companion's built-in **internal** actions and feedbacks alongside your module's own, using `internal:*` ids. Companion translates each of these to the matching internal action/feedback when the preset is imported onto a control, resolving any references (such as the button location) to the control the preset is placed on. | ||
|
|
||
| The internal actions available in presets are: | ||
|
|
||
| - **`internal:wait`** — `{ time: number }` — wait for an amount of time (ms) | ||
| - **`internal:customLog`** — `{ message: string }` — write a message to the Companion log | ||
| - **`internal:abortButton`** — `{ skipReleaseActions?: boolean }` — abort the actions currently running on this button | ||
| - **`internal:localVariableSet`** — `{ name: string; value: string }` — set one of this button's local variables | ||
|
|
||
| The internal feedbacks available in presets are: | ||
|
|
||
| - **`internal:checkExpression`** — `{ expression: string }` — boolean, driven by an expression | ||
| - **`internal:buttonPushed`** — `{ treatSteppedAsPressed?: boolean }` — boolean, true while the button is pressed | ||
| - **`internal:buttonCurrentStep`** — `{ step: number }` — boolean, true when the button is on the given step | ||
|
|
||
| In addition, you can use the logic/flow **building blocks**, which nest other actions or conditions via named `children` groups: | ||
|
|
||
| - **`internal:actionGroup`** — `{ executionMode?: 'inherit' | 'concurrent' | 'sequential' }`, children: `{ default: actions }` | ||
| - **`internal:logicIf`** — children: `{ condition: conditions; actions; elseActions? }` | ||
| - **`internal:logicWhile`** — children: `{ condition: conditions; actions }` | ||
| - **`internal:logicOperator`** (feedback) — `{ operation: 'and' | 'or' | 'xor' }`, children: `{ default: conditions }` | ||
|
|
||
| ```ts | ||
| steps: [ | ||
| { | ||
| down: [ | ||
| { actionId: 'my-recall', options: { preset: 1 } }, | ||
| { actionId: 'internal:wait', options: { time: 500 } }, | ||
| { | ||
| actionId: 'internal:logicIf', | ||
| options: {}, | ||
| children: { | ||
| condition: [ | ||
| { feedbackId: 'internal:checkExpression', options: { expression: '$(this:step) == 1' } }, | ||
| ], | ||
| actions: [ | ||
| { actionId: 'internal:customLog', options: { message: 'On first step' } }, | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| up: [], | ||
| }, | ||
| ], | ||
| ``` | ||
|
|
||
| All of these require a module built against API 2.1 (`2.1.0` or newer). The host drops, with a warning, any internal preset entry that the module is too old to use, so the catalog can grow in future API versions without older modules being able to emit ids they predate. | ||
|
|
||
| ## Presets: feedback-based local variables | ||
|
|
||
| Presets can declare local variables on a button via the `localVariables` field. As well as the existing `simple` variables (a fixed startup value), you can now define a variable whose value is driven by the live result of a feedback, using `variableType: 'feedback'`: | ||
|
|
||
| ```ts | ||
| localVariables: [ | ||
| { | ||
| variableType: 'simple', | ||
| variableName: 'last_recalled', | ||
| startupValue: '', | ||
| }, | ||
| { | ||
| variableType: 'feedback', | ||
| variableName: 'current_level', | ||
| feedbackId: 'get-level', // one of your module's feedbacks | ||
| options: { channel: 1 }, | ||
| }, | ||
| ], | ||
| ``` | ||
|
|
||
| The feedback's evaluated value is exposed as the named local variable, so it can be referenced from expressions elsewhere on the button. Pair this with **value** feedbacks (see [API 2.0](v2.0.md)) to surface device state as a variable without the user wiring it up themselves. | ||
|
|
||
| ## Presets: alternatives | ||
|
|
||
| A single preset id can now offer several variants using `type: 'alternatives'`. The host picks the best variant it supports, so the user only ever sees one copy of the preset. This is the clean way to ship a rich [layered](#layered-presets) look with a [simple](../connection-basics/presets.md#simple-button-preset-definitions) fallback for hosts which don't support all of the functionality of the module API (such as Bitfocus Buttons). List the variants most-preferred first: | ||
|
|
||
| ```ts | ||
| presets['play'] = { | ||
| type: 'alternatives', | ||
| variants: [ | ||
| { | ||
| type: 'layered', | ||
| name: 'Play', | ||
| elements: [ | ||
| /* ... */ | ||
| ], | ||
| steps: [ | ||
| /* ... */ | ||
| ], | ||
| feedbacks: [], | ||
| }, | ||
| { | ||
| type: 'simple', | ||
| name: 'Play', | ||
| style: { text: 'Play', size: '18', color: 0xffffff, bgcolor: 0x000000 }, | ||
| steps: [ | ||
| /* ... */ | ||
| ], | ||
| feedbacks: [], | ||
| }, | ||
| ], | ||
| } | ||
| ``` | ||
|
|
||
| See [Preset alternatives](../connection-basics/presets.md#preset-alternatives) for details. | ||
|
|
||
| ## Advanced feedbacks: declare `affectedProperties` | ||
|
|
||
| Advanced feedbacks (`type: 'advanced'`) should now declare which button style properties they modify, via the new `affectedProperties` field: | ||
|
|
||
| ```ts | ||
| { | ||
| type: 'advanced', | ||
| name: 'Status Colour', | ||
| options: [ | ||
| { | ||
| id: 'active', | ||
| type: 'checkbox', | ||
| label: 'Active', | ||
| default: false, | ||
| }, | ||
| ], | ||
| affectedProperties: ['bgcolor', 'color'], | ||
| callback: async (event) => { | ||
| return event.options.active | ||
| ? { bgcolor: '#00cc00', color: '#ffffff' } | ||
| : { bgcolor: '#cc0000', color: '#ffffff' } | ||
| }, | ||
| } | ||
| ``` | ||
|
|
||
| Companion uses this to configure a more accurate set of style overrides for the button. | ||
|
|
||
| From API 2.1 onwards, Companion will emit a warning in the module debug log for any advanced feedback that does not declare `affectedProperties`. Boolean and value feedbacks are unaffected. | ||
|
|
||
| :::tip | ||
| Where possible, prefer boolean or value feedbacks, or the new [layered presets](#layered-presets), over advanced feedbacks. Advanced feedbacks are intended only for cases that cannot be expressed any other way. | ||
| ::: | ||
|
|
||
| ## Smaller additions | ||
|
|
||
| A few smaller, opt-in additions also landed in 2.1: | ||
|
|
||
| - **`sortSelection` on multidropdown fields** — set `sortSelection: true` on a `multidropdown` input field to have the UI keep the selected values in the same order they appear in the `choices` array (any custom values are sorted alphabetically at the end). | ||
| - **Duplicate option ids are rejected** — option `id`s must be unique within a single action or feedback definition, and config field `id`s must be unique across the config. From 2.1, Companion drops the later duplicates and logs a warning in the module debug log, rather than letting fields silently overwrite each other. | ||
|
|
||
| ## Node 26 support | ||
|
|
||
| Modules can now declare `node26` as their runtime, by setting `runtime.type` in `companion/manifest.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "runtime": { | ||
| "type": "node26", | ||
| "api": "nodejs-ipc", | ||
| "apiVersion": "0.0.0", | ||
| "entrypoint": "../main.js" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| `node22` remains supported, but we encourage modules to update when they can. Node 26 is an LTS release and is suitable for new modules or modules that want to adopt the latest platform features. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.