@mikejobson/qsys-lib
v0.3.1
Published
QsysLib is an Angular service for communicating with QSC Q-Sys cores over WebSocket using the QRC (Q-Sys Remote Control) protocol.
Downloads
53
Readme
QsysLib
QsysLib is an Angular service for communicating with QSC Q-Sys cores over WebSocket using the QRC (Q-Sys Remote Control) protocol.
Installation
Install the library from npm:
npm install @mikejobson/qsys-libYou can view the package on npm here: https://www.npmjs.com/package/@mikejobson/qsys-lib
Setup
- Import the
QsysLibModulein your app module:
import { QsysLibModule } from "@mikejobson/qsys-lib";
@NgModule({
imports: [
QsysLibModule,
// other imports
],
// ...
})
export class AppModule {}- Inject the
QsysLibServicein your components:
import { QsysLibService } from "@mikejobson/qsys-lib";
@Component({
// ...
})
export class YourComponent {
constructor(private qsys: QsysLibService) {}
}Basic Usage
Connecting to a Q-Sys Core
// Connect directly with a raw WebSocket URL - no automatic formatting
this.qsys.connect("wss://192.168.1.100/qrc");
// For convenience, you can use the formatWebsocketUrl helper:
// 1. Connect with an IP address or hostname
const formattedUrl = QsysLibService.formatWebsocketUrl("192.168.1.100");
this.qsys.connect(formattedUrl);
// Connects to "wss://192.168.1.100/qrc"
// 2. Connect using a path - uses the current host with the specified path
const pathUrl = QsysLibService.formatWebsocketUrl("/api/qrc");
this.qsys.connect(pathUrl);
// Formats to "wss://current.host.com/api/qrc" or "ws://current.host.com/api/qrc"
// 3. Preserve existing protocols
const wsUrl = QsysLibService.formatWebsocketUrl("ws://my-qsys-core.example.com/qrc");
this.qsys.connect(wsUrl); // Uses ws: (unsecure)
// 4. Convert HTTP URLs to WebSocket URLs
const httpUrl = QsysLibService.formatWebsocketUrl("http://my-qsys-core.example.com/qrc");
this.qsys.connect(httpUrl); // Converts to "ws://"
// Get the current WebSocket URL
console.log(this.qsys.websocketUrl);
// Set the WebSocket URL directly (doesn't connect automatically)
this.qsys.websocketUrl = "wss://another-core.example.com/qrc";
this.qsys.connect(this.qsys.websocketUrl);
// Optional parameter for maximum reconnection attempts (default is 0 - infinite attempts)
this.qsys.connect("wss://192.168.1.100/qrc", 5);
// Monitor connection status
this.qsys.getConnectionStatus().subscribe((status) => {
if (status.connected) {
console.log("Connected to Q-Sys Core");
console.log("Engine status:", status.engineStatus);
// Check if the design has changed since last connection
if (status.newDesign) {
console.log("The Q-Sys design has changed, refreshing components");
// Reload your components here as needed
}
} else {
console.log("Disconnected from Q-Sys Core");
if (status.noReconnect) {
console.log("No reconnection will be attempted");
}
}
});
// Disconnect when done
this.qsys.disconnect();Getting Engine Status
this.qsys.getEngineStatus().subscribe((status) => {
if (status) {
console.log("Design name:", status.DesignName);
console.log("Design Code:", status.DesignCode);
console.log("Platform:", status.Platform);
console.log("Status:", status.Status.String);
} else {
console.log("No engine status available");
}
});Working with Components
When the service connects to the api for the first time, the service requests all available components and controls from the websocket connection. The components are cached and monitored using a default change group named 'auto'. This is also set to poll after controls are changed locally, and listen at a default rate for external changes.
If the connection drops it should re-establish the change groups automatically as long as the design code is the same. If the design code has changed, note that all objects cached are removed and you should get the component and controls again. The old objects of these will not update otherwise or may be non-existant in the design.
// Get all components in the design
const components = await this.qsys.getAllComponents();
components.forEach((component) => {
console.log(`Component: ${component.name}, Type: ${component.type}`);
});
// Get a specific component by name
const mixer = await this.qsys.getComponent("MainMixer");
if (mixer) {
console.log(`Found mixer: ${mixer.name}`);
// Get component properties
console.log("Properties:", mixer.properties);
// Get controls
mixer.controls.forEach((control) => {
console.log(`Control: ${control.name}, Type: ${control.type}, Value: ${control.value}`);
});
// Get a specific control
const fader = mixer.getControl("fader1");
if (fader) {
console.log(`Current fader value: ${fader.value}`);
}
}Controlling Components
// Change a control's value
const mixer = await this.qsys.getComponent("MainMixer");
const gain = mixer?.getControl("input.1.gain");
if (gain) {
// Set value directly to -20dB
await gain.setValue(-20);
// With ramping (time in seconds), sets value to 0dB over 2.5 seconds
await gain.rampValue(0, 2.5);
// Set position (for controls that support it, values between 0-1) of gain to half way
await gain.setPosition(0.5); // 50%
// With ramping
await gain.rampPosition(0.75, 3.0); // Ramp to 75% over 3 seconds
}
const mute = mixer?.getControl("input.1.mute");
if (mute) {
// Read the mute value as a Boolean
var muteValue: Boolean = gain.value;
// Set the value
await mute.setValue(true);
}Subscribing to Control Changes
// Subscribe to updates for a specific control
const mixer = await this.qsys.getComponent("MainMixer");
const fader = mixer?.getControl("input.1.gain");
if (fader) {
fader.updated.subscribe((control) => {
console.log(`Fader updated: ${control.value}`);
});
// Subscribe to all controls in a component
mixer.updated.subscribe((controls) => {
console.log(
"Updated controls:",
controls.map((c) => c.name)
);
});
}Using Direct Commands
Direct commands allow asynchronous comms directly to the QRC protocol rather than using the object methods above.
Note these methods don't return object classes with notifications and control methods, but rather just the data which may be easier for simple tasks.
// Send a command and get the response
try {
const response = await this.qsys.sendCommandAsync("Component.GetComponents", {});
console.log("Components:", response);
} catch (error) {
console.error("Error:", error);
}
// You can also use the getComponents method to receive data for each component.
try {
const response = await this.qsys.getComponents();
console.log("Components:", response);
} catch (error) {
console.error("Error:", error);
}
// Alternatively you can include the controls in there also
try {
const response = await this.qsys.getComponents(true);
console.log("Components with controls:", response);
} catch (error) {
console.error("Error:", error);
}
// Get controls for a specific component
try {
const controls = await this.qsys.getControls("MainMixer");
console.log("Controls:", controls);
// Controls are sorted by name and contain properties like:
// Name, Type, Value, ValueMin, ValueMax, String, Position, Direction
} catch (error) {
console.error("Error:", error);
}
// Set a component's control value directly
try {
// Set a gain control to -10 dB
await this.qsys.setComponentValue("MainMixer", "input.1.gain", -10);
// Set a gain control to -20 dB with a 2 second ramp
await this.qsys.setComponentValue("MainMixer", "input.1.gain", -20, 2);
// Set a mute control to true
await this.qsys.setComponentValue("MainMixer", "input.1.mute", true);
} catch (error) {
console.error("Error:", error);
}
// Set a component's position directly (values between 0-1)
try {
// Set a fader to 50%
await this.qsys.setComponentPosition("MainMixer", "input.1.gain", 0.5);
// Set a fader to 75% with a 3 second ramp
await this.qsys.setComponentPosition("MainMixer", "input.1.gain", 0.75, 3);
} catch (error) {
console.error("Error:", error);
}Change Groups
// Create a change group with specific controls
const groupId = "myGroup";
await this.qsys.changeGroupAddControls(groupId, "MainMixer", ["fader1", "mute1"]);
// Poll the change group manually
const changes = await this.qsys.changeGroupPoll(groupId);
console.log("Changes:", changes);
// Set up auto-polling (rate in seconds)
await this.qsys.changeGroupAutoPoll(groupId, 0.5);
// Listen for change group updates
this.qsys.getChangeGroupUpdates().subscribe((update) => {
if (update.changeGroupId === groupId) {
console.log("Updates:", update.changes);
}
});Building and Publishing
This library is automatically built and published to npm using GitHub Actions when changes are pushed to the main branch.
Automated Publishing Workflow
- When changes are pushed to the
mainbranch that affect files in theprojects/qsys-lib/directory, a GitHub Actions workflow is triggered. - The workflow builds the library and checks if the version in
package.jsonhas been incremented. - If the version has been updated, the workflow automatically publishes the new version to npm.
Contributing Changes
If you're contributing changes to this library:
- Make your changes in a feature branch
- Update the version in
projects/qsys-lib/package.jsonaccording to semver guidelines:- Patch version for backwards compatible bug fixes (0.2.2 → 0.2.3)
- Minor version for backwards compatible new features (0.2.2 → 0.3.0)
- Major version for breaking changes (0.2.2 → 1.0.0)
- Create a pull request targeting the
mainbranch - Once merged, the GitHub Actions workflow will automatically publish the new version
Manual Building
To build the library locally for testing:
ng build qsys-libThis command will compile the library, and the build artifacts will be placed in the dist/ directory.
Running unit tests
To execute unit tests with the Karma test runner, use the following command:
ng testAdditional Resources
For more information on using the Angular CLI, including detailed command references, visit the Angular CLI Overview and Command Reference page.
