Development Template & Guides for building and deploying QSC Q-Sys Community Plugins.
A resource for development of plugins for the Q-Sys platform.
- Getting Started
- Development
- Plugin File Structure (.lua as .qplug)
- Local Table: PluginInfo
- Function: GetProperties() returns Table
- Function: RectifyProperties(Table) returns Table
- Global Table: Properties
- Function: GetPrettyName(Table) returns String
- Function: GetControls(Table) returns Table
- Global Table: Controls
- Function: GetPages(Table) returns Table
- Function: GetComponents(Table) returns Table
- Function: GetControlLayout(Table) returns Table,Table
- NuSpec File Structure
- Open Q-Sys Designer with the
/dev
command line argument - Under Asset Manager there will now be a cog icon active at the top right
- Add the community server
https://q-sys.soloworks.co.uk/q-sys-community-plugins/
- Note: Entries may double up on first time, refresh view with 🔄 to clear
Some knowledge of Git based workflows is required, to submit changes and improvements please follow standard fork/branch workflow and submit a Pull Request when finished.
Clone a plugin repository to somewhere (we use Documents/GitHub/repo-name)
git clone github.com/q-sys-community/q-sys-plugin-<reponame>
User Defined plugins are defined and placed in the folowing folder. Copy the .qplug file from the content folder to here
%userprofile%\Documents\QSC\Q-Sys Designer\Plugins
This will now appear under user plugins as version 0.0.0.0-master. When finished with changes, copy back to the repo and commit.
Note: Please only submit tested and working files, always ensure they have been linted with the correct extensions (see below) to ensure only code changes are submitted and not syntax and spacing. We are opinionated with code in order to debug logic, not spacing.
Q-Sys Plugins are single file LUA scripts built around a specific framework. They are delivered using NuGet based system.
Plugins are managed in your file system by NuGet using the ID and version in the format id.0.0.0.0
, and by Q-Sys Designer by the PluginInfo.Id
value. This means you can have multiple versions of a plugin installed, but if they are not given unique ids you may only see one.
Do not work on plugins directly from the installed files, instead make a copy of the plugin from the Repo and work in your plugins folder. Always delete and re-load a plugin after making changes to anything except the control code body, and even then if you encounter behaviour that you don't understand, in the first instance re-load designer and re-drag your plugin.
Plugins consist of two clear sections, which need to be understood to avoid confusion:
This section consists of all Plugin configuration:
- PluginInfo - System infomation and MetaData
- Properties - Configurable values presented in Properties Pane
- Controls - Input / Output GUI objects and/or PINs
- GUI Layout - Look and Feel of the presented UI pages
When a plugin is added to a schematic, the above areas are compiled into your program at that point in time. Any changes will not be reflected until either deletion of the control, or possibly a re-load of designer, and will result in very unpredictable results.
This section consists of all control code, setting and reading Controls and Properties. This is where the actual moving parts go, changes to this section are picked up on every re-compile or emulation.
For consistency of coding structure, Visual Studio Code should be used for development with the following extensions:
Markdown All in One Extension
(Search String:yzhang.markdown-all-in-one
)markdownlint
(Search String:davidanson.vscode-markdownlint
)vscode-lua
(Search String:trixnz.vscode-lua
)
Plugin files with extension .qplug can be associated with Visual Studio Code by editing settings.json:
{
"files.associations": {
"*.qplug": "lua"
}
}
When saving .lua files, always automatically format the file, either by right-clicking the code and chosing Format Document
or with the shortcut (Shift-Alt-F). This will help to prevent git code changes flagged based on whitespace or code style.
All plugins are open source, which means you are free to dig in and contribute. To submit changes, you can fork a branch and when done submit a pull request. Once accepted, a plugin will be tagged with a new version number (in the format x.x.x.x), and will be automatically packaged and pushed to the server from which it can be installed and used.
Use the following guides when making commits to maintain a standard structure and keep automation easier:
- How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
- Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0-beta.4/
In summary, the rules to adhere to are:
<type>: <description in 50 chars>
<type>
can be one of:- fix
- feat
- docs
- style
- refactor
- chore
- Do not end with a
"."
- Use the imperative mood (Spoken or written as if giving a command or instruction) e.g.
- Update document to show...
- Stop function returning...
- Alter workflow for control of...
- Commit message body is optional, but should be sensible, concise and descriptive if used
Plugin files are single file LUA scripts with a specific Q-Sys flavour framework. They can have the extension .lua, but convention is to use .qplug.
They consist of the following user editable elements:
- Local Table: PluginInfo
- Global Table: Controls
- Function: GetProperties() returns Table
- Global Table: Properties
- Function: RectifyProperties(Table) returns Table
- Function: GetPrettyName(Table) returns String
- Function: GetControls(Table) returns Table
- Function: GetPages(Table) returns Table
- Function: GetControlLayout(Table) returns Table,Table
- Function: GetComponents(Table) returns Table
- Body: User control code, wrapped in
if Controls
Contains infomation instructing Q-Sys Designer on how to handle the plugin.
Example:
PluginInfo = {
-- Name of plugin in tree
-- `~` allows for folder structure
Name = "FolderName~PluginName",
-- Version String (unused directly by Q-Sys Designer)
Version = "VersionString", // Version String
-- Id is a UID for this plugin within the context of Designer
Id = "qsysc.make.model.version",
-- Description
Description = "DescriptionString",
-- Author (Used to put into group)
-- QSC = QSC Managed
-- Default: User
Author = "AuthorString"
-- Show Debug Window
ShowDebug = true | false,
}
Function to build and store the Properties table (see below)
function GetProperties()
props = {
-- Build as per Properties Table spec below
}
return props
end
Function to change properties table based on other properties. Called on any change of properties values.
function RectifyProperties(props)
-- Amend as per Properties Table spec below
return props
end
Properties = {
{ -- for integer property types
Name = "MyIntegerName",
Type = "integer",
Min = 0, -- Minimum Value
Max = 10, -- Maximum Value
Value = 5, -- Default Value
},
{ -- for boolean property types
Name = "MyBooleanName",
Type = "boolean",
Value = true | false, -- Default State
},
{ -- for enum property types
Name = "property_name",
Type = "enum",
Choices = { -- List of options
"Option_01",
"Option_02"
},
Value = "Option_01", -- Default Option
},
}
Return the string to display on the plugin control on the schamtic canvas. If not present will default to Id.
function GetPrettyName()
return "My Friendly Name String V1.0.0.2"
end
Return the Controls table as per Controls table spec below
function GetControls(props)
ctls = {
-- Build as per Controls Table spec below
}
return ctls
end
Controls = {
{
-- Button
{
Name = "control_name"
ControlType = "Button"
ButtonType = "Momentary" | "Toggle" | "Trigger",
Count = integer, -- if > 0 this will make the control an array
PinStyle = "Input" | "Output" | "Both",
UserPin = true | false
},
-- Knob
{
Name = "control_name",
ControlType = "Knob",
ControlUnit = "dB" | "Hz" | "Float" | "Integer" | "Pan" | "Percent" | "Position" | "Seconds",
Min = value,
Max = value,
Count = integer, -- if > 0 this will make the control an array
PinStyle = "Input" | "Output" | "Both",
UserPin = true | false
},
-- Indicator
{
Name = "control_name",
ControlType = "Indicator",
IndicatorType = "Led" | "Meter" | "Text" | "Status",
Count = integer, -- if > 0 this will make the control an array
PinStyle = "Input" | "Output" | "Both",
UserPin = true | false
},
-- Text
{
Name = "control_name",
ControlType = "Text",
Count = integer, -- if > 0 this will make the control an array
PinStyle = "Input" | "Output" | "Both",
UserPin = true | false
},
}
}
Return table of pages, allowing UI to be tabbed.
local pagenames = {"Mixer","Video Switcher"}
function GetPages(props)
pages = {}
if props["Model"].Value=="Model 1" then
.insert(pages, {name = pagenames[1]}) -- Only the "Mixer" pages shows for Model 1
else
for ix,name in ipairs(pagenames) do
table.insert(pages,{ name = pagenames[ix] }) -- All pages in 'pagenames' show for Model 2
end
end
return pages
end
Store data here that isn't lost at runtime? (ToDo: Requires testing to see how it behaves)
function GetComponents(props)
return {}
end
Define how the Controls are presented and behave on UI and Pins. This section represents the main chunk of work in the plugin presentation, and will be the location of most errors.
Returns two tables:
- Layout - Interactive controls
- Graphics - Aesthetic controls
Images can be included by encoding Svg, Png or Jpeg as a base64 string using a site such as:
For example of the encoded string provided, examine the raw source of this file. The image below is embeded as:
data:image/png;base64,<base64EncodedTextHere>
and produces this image
Example:
MyBase64EncodedImage = "iVBORw0KGgoAAAANS..." -- Truncated Base64
{
Type = "Svg", -- SVG
--Type = "Image", -- PNG,JPEG
Image = MyBase64EncodedImage,
Position = {x, y},
Size = {x, y}
}
Display static text
Example:
{
Style = "Text",
HTextAlign = "Left",
Padding = i,
StrokeWidth = i,
Position = {x, y},
Size = {x, y}
}
- "QSC" will cause plugin to appear in "QSC Managed" tree
- Anything else will appear in "User" tree
The description field is populated by the description.md
file in the root of the plugin repo.
NuGet supports most simple markdown, and the deploy process will convert relative image links from markdown to the slightly different NuGet supported format so that you can create and preview the file in VS Code.
To be included as part of the build at a later date, build up from Git Commit notes.