Skip to content

Commit f7bc8c8

Browse files
authored
v2.0.0 (#11)
* Stash changes to pull in firmware changes * Safety commit with actions and variables polling * Safety checking with complete functionality * All files pass lint and format * Upgrade functionality complete * Switch development to Legion * Add JSDoc and fix a few small bugs found while creating demo video * more cleanup of text headers * Support variables in action fields * Add variables support to feedbacks and fix sending multiple messages. * Safety checkin before polling chanig * Fix synchorization isses with a sequential queue * changes to .gitignore * removed previously unignored files * bugs found while building video * update HELP.md with latest YouTube link
1 parent e9fb4c4 commit f7bc8c8

26 files changed

+3917
-2752
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[*]
2+
end_of_line = lf
3+
insert_final_newline = true

.eslintrc.cjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
extends: './node_modules/@companion-module/tools/eslint/main.cjs',
3+
}

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ blank_issues_enabled: false
22
contact_links:
33
- name: BUG OR FEATURE REQUEST FOR COMPANION ITSELF
44
url: https://github.com/bitfocus/companion/issues
5-
about: Report it against the companion itself, to get the relevant people notified.
5+
about: Report it against the companion itself, to get the relevant people notified.

.github/workflows/companion-module-checks.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,3 @@ jobs:
1515
uses: bitfocus/actions/.github/workflows/module-checks.yaml@main
1616
# with:
1717
# upload-artifact: true # uncomment this to upload the built package as an artifact to this workflow that you can download and share with others
18-

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
node_modules/
22
package-lock.json
33
/pkg
4-
/pkg.tgz
4+
/pkg.tgz
5+
out/
6+
DEBUG-PACKAGED
7+
.yarn/*
8+
ptzoptics-superjoy*.tgz

.prettierignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
package.json
2-
/LICENSE.md
2+
/LICENSE.md
3+
node_modules
4+
dist
5+
package-lock.json
6+
*.md

.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

DEBUG-PACKAGED

Whitespace-only changes.

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
# companion-module-ptzoptics-superjoy
22

3-
This module integrates with the PTZ Optics Superjoy controller.
3+
This module allows you to control the [PTZOptics SuperJoy Controller](https://ptzoptics.com/superjoy/). It provides all the functionality available in the [SuperJoy HTTP-CGI Commands documentation](https://ptzoptics.com/wp-content/uploads/2025/03/SuperJoy-HTTP-CGI-Commands.pdf) as of January 2, 2026.
44

5-
Available functions are:
5+
## Getting Started
66

7-
- Select active camera
8-
- Direct preset select (camera and preset)
9-
- Feedback based on last known preset per-camera and current preset query.
7+
See [HELP.md](https://github.com/bitfocus/companion-module-birddog-ptz/blob/main/companion/HELP.md)
108

11-
See [HELP.md](./companion/HELP.md) and [LICENSE](./LICENSE)
9+
## Changelog
10+
11+
### v2.0.0
12+
13+
- Fix
14+
- Complete rewrite of module to cover the entire API.
15+
- Treats one SuperJoy as one connection to Companion rather than by Group
16+
17+
### v1.0.0
18+
19+
- New
20+
- Initial version

actions.js

Lines changed: 144 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,147 @@
1-
module.exports = function (self) {
2-
self.setActionDefinitions({
3-
sample_action: {
4-
name: 'My First Action',
5-
options: [
6-
{
7-
id: 'num',
8-
type: 'number',
9-
label: 'Test',
10-
default: 5,
11-
min: 0,
12-
max: 100,
1+
import { FIELDS } from './fields.js'
2+
import { SuperJoyCommandError } from './error.js'
3+
4+
const ACTION_NAMES = {
5+
hdmiout: 'HDMI Output Control',
6+
custom: 'Trigger Custom Button',
7+
camselect: 'Select Group and Camera',
8+
directpresets: 'Select Group, Camera, and Preset',
9+
presets: 'Select Preset on Current Camera',
10+
}
11+
12+
/**
13+
* Class representing PTZ SuperJoy actions.
14+
* @class
15+
*
16+
* This is implemented as a class so that the SuperJoy instance can be stored
17+
* as a member variable for use in callbacks and error messages.
18+
*/
19+
export class PTZSuperJoyActions {
20+
/**
21+
* @property {PTZSuperJoyInstance} superJoyInstance - The instance of the SuperJoy controller.
22+
*/
23+
superJoyInstance = null
24+
25+
/**
26+
* Constructor for PTZSuperJoyActions.
27+
* @param {PTZSuperJoyInstance} superJoyInstance - The instance of the SuperJoy controller.
28+
*/
29+
constructor(superJoyInstance) {
30+
this.superJoyInstance = superJoyInstance
31+
this.initActions()
32+
}
33+
34+
/**
35+
* Callback function for handling action responses.
36+
* @param {*} json - The json representation of the http response
37+
* @param {*} data - callback data given by the caller with the current url added
38+
*/
39+
actionCallback = (json, data) => {
40+
if (json.result !== '0') {
41+
throw new SuperJoyCommandError(`Result is ${json.result}, expect zero`, {
42+
url: data.url,
43+
superJoyInstance: this.superJoyInstance,
44+
})
45+
}
46+
}
47+
48+
/**
49+
* Initialize action definitions for the SuperJoy controller.
50+
*/
51+
initActions() {
52+
let actions = {
53+
hdmiout: {
54+
name: ACTION_NAMES['hdmiout'],
55+
options: [FIELDS.HDMIControl],
56+
callback: async (action) => {
57+
let argMap = new Map([['action', action.options.hdmicontrol]])
58+
this.superJoyInstance.sendCommand('hdmiout', argMap, {
59+
function: this.actionCallback,
60+
data: null,
61+
})
62+
},
63+
},
64+
custom: {
65+
name: ACTION_NAMES['custom'],
66+
options: [FIELDS.CustomButton],
67+
callback: async (action, context) => {
68+
let fields = this.superJoyInstance.getFields()
69+
let buttonTxt = await context.parseVariablesInString(action.options.buttonid)
70+
let buttonId = fields.validateCustomButton(ACTION_NAMES['custom'], buttonTxt)
71+
let argMap = new Map([
72+
['action', 'trigger'],
73+
['buttonid', buttonId],
74+
])
75+
this.superJoyInstance.sendCommand('custom', argMap, {
76+
function: this.actionCallback,
77+
data: null,
78+
})
79+
},
80+
},
81+
camselect: {
82+
name: ACTION_NAMES['camselect'],
83+
options: [FIELDS.Group, FIELDS.Camera],
84+
callback: async (action, context) => {
85+
let fields = this.superJoyInstance.getFields()
86+
let groupTxt = await context.parseVariablesInString(action.options.group)
87+
let camidTxt = await context.parseVariablesInString(action.options.id)
88+
let groupAndCam = fields.validateGroupAndCamera(ACTION_NAMES['camselect'], groupTxt, camidTxt)
89+
let argMap = new Map([
90+
['group', groupAndCam.group],
91+
['camid', groupAndCam.camid],
92+
])
93+
this.superJoyInstance.sendCommand('camselect', argMap, {
94+
function: this.actionCallback,
95+
data: null,
96+
})
97+
},
98+
},
99+
directpresets: {
100+
name: ACTION_NAMES['directpresets'],
101+
options: [FIELDS.Group, FIELDS.Camera, FIELDS.Preset, FIELDS.Speed],
102+
callback: async (action, context) => {
103+
let fields = this.superJoyInstance.getFields()
104+
let groupTxt = await context.parseVariablesInString(action.options.group)
105+
let camidTxt = await context.parseVariablesInString(action.options.id)
106+
let groupAndCam = fields.validateGroupAndCamera(ACTION_NAMES['directpresets'], groupTxt, camidTxt)
107+
let presetTxt = await context.parseVariablesInString(action.options.preset)
108+
let presetSpeedTxt = await context.parseVariablesInString(action.options.speed)
109+
let preset = fields.validatePreset(ACTION_NAMES['directpresets'], presetTxt)
110+
let presetSpeed = fields.validatePresetSpeed(ACTION_NAMES['directpresets'], presetSpeedTxt)
111+
let argMap = new Map([
112+
['action', 'recall'],
113+
['group', groupAndCam.group],
114+
['camid', groupAndCam.camid],
115+
['preset', preset],
116+
['presetspeed', presetSpeed],
117+
])
118+
this.superJoyInstance.sendCommand('directpresets', argMap, {
119+
function: this.actionCallback,
120+
data: null,
121+
})
122+
},
123+
},
124+
presets: {
125+
name: ACTION_NAMES['presets'],
126+
options: [FIELDS.Preset, FIELDS.Speed],
127+
callback: async (action, context) => {
128+
let fields = this.superJoyInstance.getFields()
129+
let presetTxt = await context.parseVariablesInString(action.options.preset)
130+
let presetSpeedTxt = await context.parseVariablesInString(action.options.speed)
131+
let preset = fields.validatePreset(ACTION_NAMES['presets'], presetTxt)
132+
let presetSpeed = fields.validatePresetSpeed(ACTION_NAMES['presets'], presetSpeedTxt)
133+
let argMap = new Map([
134+
['action', 'recall'],
135+
['preset', preset],
136+
['presetspeed', presetSpeed],
137+
])
138+
this.superJoyInstance.sendCommand('presets', argMap, {
139+
function: this.actionCallback,
140+
data: null,
141+
})
13142
},
14-
],
15-
callback: async (event) => {
16-
console.log('Hello world!', event.options.num)
17143
},
18-
},
19-
})
144+
}
145+
this.superJoyInstance.setActionDefinitions(actions)
146+
}
20147
}

0 commit comments

Comments
 (0)