Skip to content
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

feat: initial VEC Footpedal support #2944

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions assets/linux/50-companion-desktop.rules
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE:="666"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0010", MODE:="666", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0020", MODE:="666", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE:="666", TAG+="uaccess"

# vec footpedal
SUBSYSTEM=="usb", ATTRS{idVendor}=="05f3", ATTRS{idProduct}=="00ff", MODE:="666", TAG+="uaccess"
KERNEL=="hidraw*", ATTRS{idVendor}=="05f3", ATTRS{idProduct}=="00ff", MODE:="666", TAG+="uaccess"
Julusian marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions assets/linux/50-companion-headless.rules
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE:="666"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0010", MODE:="666", GROUP="companion"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0020", MODE:="666", GROUP="companion"
KERNEL=="hidraw*", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0030", MODE:="666", GROUP="companion"

# vec footpedal
SUBSYSTEM=="usb", ATTRS{idVendor}=="05f3", ATTRS{idProduct}=="00ff", MODE:="666", GROUP="companion"
KERNEL=="hidraw*", ATTRS{idVendor}=="05f3", ATTRS{idProduct}=="00ff", MODE:="666", GROUP="companion"
Julusian marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions companion/lib/Data/UserConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class DataUserConfig extends CoreBase {
usb_hotplug: true,
loupedeck_enable: false,
contour_shuttle_enable: false,
vec_footpedal_enable: false,

pin_enable: false,
link_lockouts: false,
Expand Down
18 changes: 18 additions & 0 deletions companion/lib/Surface/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { getStreamDeckDeviceInfo } from '@elgato-stream-deck/node'
import { usb } from 'usb'
// @ts-ignore
import shuttleControlUSB from 'shuttle-control-usb'
// @ts-ignore
import vecFootpedal from 'vec-footpedal'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes:

lib/Surface/Controller.js:29:26 - error TS7016: Could not find a declaration file for module 'vec-footpedal'. '/home/runner/work/companion/companion/node_modules/vec-footpedal/index.js' implicitly has an 'any' type.
Try npm i --save-dev @types/vec-footpedal if it exists or add a new declaration (.d.ts) file containing declare module 'vec-footpedal';

29 import vecFootpedal from 'vec-footpedal'
~~~~~~~~~~~~~~~

Found 1 error in lib/Surface/Controller.js:29

But I can't work out how e.g. Contour Shuttle doesn't cause the same thing...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The // @ts-ignore on line 27 (the line before the import) is what allows it to work.
It would be really good to have typings enabled, but I guess that isn't required for this PR seeing as that one doesn't have it either.

Suggested change
import vecFootpedal from 'vec-footpedal'
// @ts-ignore
import vecFootpedal from 'vec-footpedal'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks, couldn't see the wood for the trees!

If there's an example typing (for a non TS library) I can steal I can add one for one or indeed both in due course (but yes ideally after this PR is in). Presumably ideally the typing gets added to the source package so everyone benefits?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe https://github.com/bitfocus/node-infinitton-idisplay/blob/infinitton-idisplay/index.d.ts or https://github.com/node-hid/node-hid/blob/master/nodehid.d.ts are useful examples?
Ideally the EventEmitter would be typed too, I think that can be done without a library now, but I haven't tried it

import { listLoupedecks, LoupedeckModelId } from '@loupedeck/node'
import SurfaceHandler, { getSurfaceName } from './Handler.js'
import SurfaceIPElgatoEmulator, { EmulatorRoom } from './IP/ElgatoEmulator.js'
Expand All @@ -37,6 +39,7 @@ import XKeysDriver from './USB/XKeys.js'
import LoupedeckLiveDriver from './USB/LoupedeckLive.js'
import SurfaceUSBLoupedeckCt from './USB/LoupedeckCt.js'
import ContourShuttleDriver from './USB/ContourShuttle.js'
import VECFootpedalDriver from './USB/VECFootpedal.js'
import SurfaceIPVideohubPanel from './IP/VideohubPanel.js'
import FrameworkMacropadDriver from './USB/FrameworkMacropad.js'
import CoreBase from '../Core/Base.js'
Expand Down Expand Up @@ -891,6 +894,21 @@ class SurfaceController extends CoreBase {
'infinitton',
InfinittonDriver
)
} else if (
// More specific match has to be above xkeys
deviceInfo.vendorId === vecFootpedal.vids.VEC &&
deviceInfo.productId === vecFootpedal.pids.FOOTPEDAL
) {
if (this.userconfig.getKey('vec_footpedal_enable')) {
await this.#addDevice(
{
path: deviceInfo.path,
options: {},
},
'vec-footpedal',
VECFootpedalDriver
)
}
} else if (deviceInfo.vendorId === 1523 && deviceInfo.interface === 0) {
if (this.userconfig.getKey('xkeys_enable')) {
await this.#addDevice(
Expand Down
169 changes: 169 additions & 0 deletions companion/lib/Surface/USB/VECFootpedal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// @ts-nocheck
/*
* This file is part of the Companion project
* Copyright (c) 2024 Peter Newman
* Author: Peter Newman
*
* This program is free software.
* You should have received a copy of the MIT licence as well as the Bitfocus
* Individual Contributor License Agreement for companion along with
* this program.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the Companion software without
* disclosing the source code of your own applications.
*
*/

import EventEmitter from 'events'
import vecFootpedal from 'vec-footpedal'
import LogController from '../../Log/Controller.js'

const vecFootpedalInfo = {
// Treat as:
// 3 buttons
totalCols: 3,
totalRows: 1,

buttons: [
[0, 0],
[1, 0],
[2, 0],
],
}

function buttonToXy(modelInfo, info) {
return modelInfo.buttons[info - 1]
}

class SurfaceUSBVECFootpedal extends EventEmitter {
/**
* @type {import('winston').Logger}
* @access private
* @readonly
*/
#logger

constructor(devicePath, contourShuttle, modelInfo, deviceInfo) {
super()

this.#logger = LogController.createLogger(`Surface/USB/VECFootpedal/${devicePath}`)

this.vecFootpedal = vecFootpedal
this.deviceInfo = deviceInfo
this.modelInfo = modelInfo

this.config = {
// No config currently present
}

this.#logger.debug(`Adding VEC Footpedal USB device ${devicePath}`)

this.info = {
type: `VEC Footpedal ${this.deviceInfo.name}`,
devicePath: devicePath,
configFields: [],
deviceId: `vecfootpedal:${this.deviceInfo.id}`,
}

this.gridSize = {
columns: this.modelInfo.totalCols,
rows: this.modelInfo.totalRows,
}

this.vecFootpedal.on('error', (error) => {
console.error(error)
this.emit('remove')
})

this.vecFootpedal.on('buttondown', (info) => {
const xy = buttonToXy(this.modelInfo, info)
if (xy === undefined) {
return
}

this.emit('click', ...xy, true)
})

this.vecFootpedal.on('buttonup', (info) => {
const xy = buttonToXy(this.modelInfo, info)
if (xy === undefined) {
return
}

this.emit('click', ...xy, false)
})

this.vecFootpedal.on('disconnect', (error) => {
console.error(error)
this.emit('remove')
})
}

async #init() {
this.#logger.debug(`VEC Footpedal ${this.deviceInfo.name} detected`)
}

/**
* Open a VEC Footpedal
* @param {string} devicePath
* @returns {Promise<SurfaceUSBVECFootpedal>}
*/
static async create(devicePath) {
const pedal = vecFootpedal
// We're doing device search via Companion so don't run it here too
pedal.start(false)
try {
let deviceInfo = null
let info = null
pedal.connect(devicePath)
deviceInfo = pedal.getDeviceByPath(devicePath)
switch (deviceInfo.name) {
case 'VEC Footpedal':
info = vecFootpedalInfo
break
default:
throw new Error(`Unknown VEC Footpedal device detected: ${deviceInfo.name}`)
}
if (!info) {
throw new Error('Unsupported model')
}

const self = new SurfaceUSBVECFootpedal(devicePath, pedal, info, deviceInfo)

await self.#init()

return self
} catch (e) {
pedal.stop()

throw e
}
}

/**
* Process the information from the GUI and what is saved in database
* @param {Record<string, any>} config
* @param {boolean=} _force
* @returns false when nothing happens
*/
setConfig(config, _force) {
// No config currently present
this.config = config
}

quit() {
this.vecFootpedal.close()
}

draw() {
// Should never be fired
}

clearDeck() {
// Not relevant for this device
}
}

export default SurfaceUSBVECFootpedal
1 change: 1 addition & 0 deletions companion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"usb": "2.13.0",
"utf-8-validate": "^6.0.4",
"uuid": "^10.0.0",
"vec-footpedal": "^2.0.1",
"videohub-server": "^0.3.0",
"winston": "^3.13.0",
"workerpool": "^9.1.3",
Expand Down
3 changes: 3 additions & 0 deletions docs/3_config/settings/surfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ More details on supported surfaces are available in the chapter on [Surfaces](#7

- **Enable connected Contour Shuttle (Requires Companion restart)**
Whether to enable support for connecting to Contour Shuttle devices.

- **Enable connected VEC Footpedal (Requires Companion restart)**
Whether to enable support for connecting to VEC Footpedal devices.
5 changes: 5 additions & 0 deletions docs/7_surfaces/vec_footpedal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
It is possible to use the VEC Footpedal with Companion since v3.4.0.

Enable support for it in the Companion settings, and rescan for USB devices.

The layout pretty closely matches what you would expect based on the device.
4 changes: 4 additions & 0 deletions docs/structure.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@
{
"label": "Contour Shuttle",
"file": "7_surfaces/contour_shuttle.md"
},
{
"label": "VEC Footpedal",
"file": "7_surfaces/vec_footpedal.md"
}
]
}
Expand Down
1 change: 1 addition & 0 deletions shared-lib/lib/Model/UserConfigModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface UserConfigModel {
usb_hotplug: boolean
loupedeck_enable: boolean
contour_shuttle_enable: boolean
vec_footpedal_enable: boolean

pin_enable: boolean
link_lockouts: boolean
Expand Down
21 changes: 21 additions & 0 deletions webui/src/UserConfig/SurfacesConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@ export const SurfacesConfig = observer(function SurfacesConfig({ config, setValu
</CButton>
</td>
</tr>
<tr>
<td>
Enable connected VEC Footpedal
<br />
<em>(Requires Companion restart)</em>
</td>
<td>
<CFormSwitch
className="float-right"
color="success"
checked={config.vec_footpedal_enable}
size="xl"
onChange={(e) => setValue('vec_footpedal_enable', e.currentTarget.checked)}
/>
</td>
<td>
<CButton onClick={() => resetValue('vec_footpedal_enable')} title="Reset to default">
<FontAwesomeIcon icon={faUndo} />
</CButton>
</td>
</tr>
</>
)
})
12 changes: 12 additions & 0 deletions webui/src/Wizard/ApplyStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ export function ApplyStep({ oldConfig, newConfig }: ApplyStepProps) {
)
}

if (oldConfig.setup_wizard === 0 || oldConfig.vec_footpedal_enable !== newConfig.vec_footpedal_enable) {
changes.push(
newConfig.vec_footpedal_enable ? (
<li>VEC Footpedal hardware will be detected by Companion.</li>
) : (
<li>
VEC Footpedal hardware will {oldConfig.setup_wizard > 0 ? 'no longer' : 'not'} be detected by Companion.
</li>
)
)
}

if (oldConfig.setup_wizard === 0 || oldConfig.videohub_panel_enabled !== newConfig.videohub_panel_enabled) {
changes.push(
newConfig.videohub_panel_enabled ? (
Expand Down
4 changes: 2 additions & 2 deletions webui/src/Wizard/FinishStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export function FinishStep({ oldConfig, newConfig }: FinishStepProps) {
{((newConfig.elgato_plugin_enable && oldConfig.elgato_plugin_enable !== newConfig.elgato_plugin_enable) ||
(!newConfig.xkeys_enable && oldConfig.xkeys_enable !== newConfig.xkeys_enable) ||
(!newConfig.loupedeck_enable && oldConfig.loupedeck_enable !== newConfig.loupedeck_enable) ||
(!newConfig.contour_shuttle_enable &&
oldConfig.contour_shuttle_enable !== newConfig.contour_shuttle_enable)) && (
(!newConfig.contour_shuttle_enable && oldConfig.contour_shuttle_enable !== newConfig.contour_shuttle_enable) ||
(!newConfig.vec_footpedal_enable && oldConfig.vec_footpedal_enable !== newConfig.vec_footpedal_enable)) && (
<CAlert color="danger">
After completing this wizard a restart of Companion is required to apply your USB detection settings. You will
need to do this manually.
Expand Down
8 changes: 8 additions & 0 deletions webui/src/Wizard/SurfacesStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export function SurfacesStep({ config, setValue }: SurfacesStepProps) {
/>
</div>

<div className="indent3">
<CFormCheck
label="VEC Footpedal USB Devices"
checked={config.vec_footpedal_enable}
onChange={(e) => setValue('vec_footpedal_enable', e.currentTarget.checked)}
/>
</div>

<br />
<h5>IP Surface Listeners</h5>
<div className="indent3">
Expand Down
11 changes: 11 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6755,6 +6755,7 @@ asn1@evs-broadcast/node-asn1:
usb: "npm:2.13.0"
utf-8-validate: "npm:^6.0.4"
uuid: "npm:^10.0.0"
vec-footpedal: "npm:^2.0.1"
videohub-server: "npm:^0.3.0"
webpack: "npm:^5.92.1"
webpack-cli: "npm:^5.1.4"
Expand Down Expand Up @@ -15163,6 +15164,16 @@ asn1@evs-broadcast/node-asn1:
languageName: node
linkType: hard

"vec-footpedal@npm:^2.0.1":
version: 2.0.1
resolution: "vec-footpedal@npm:2.0.1"
dependencies:
node-hid: "npm:^3.1.0"
usb: "npm:^2.9.0"
checksum: 10c0/2e158005d8ffc8547a929e9d9ab4ce2f1762d19df9c89037f701ec65d4915e1fc0543f16fd611c11de77298ddd81ff485484e34c68c8dd6fca9b49c6e7842d22
languageName: node
linkType: hard

"verror@npm:^1.10.0":
version: 1.10.1
resolution: "verror@npm:1.10.1"
Expand Down