Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion src/actions/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ export function createCommonActions(self: InstanceBaseExt<WingConfig>): Companio
const send = self.connection!.sendCommand.bind(self.connection)
const ensureLoaded = self.stateHandler!.ensureLoaded.bind(self.stateHandler)
const state = self.stateHandler?.state
const logger = self.logger
if (!state) {
self.logger!.error('State handler or state is not available for creating common actions')
logger?.error('State handler or state is not available for creating common actions')
throw new Error('State handler or state is not available')
}
const transitions = self.transitions
Expand Down
27 changes: 25 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface WingConfig {
variableUpdateRate?: number
/** When enabled, the module will request values for all variables on startup */
prefetchVariablesOnStartup?: boolean
startupVariableRequestChunkSize?: number
startupVariableRequestChunkWait?: number

// Advanced Option
requestTimeout?: number
Expand Down Expand Up @@ -128,7 +130,6 @@ export function GetConfigFields(_self: InstanceBaseExt<WingConfig>): SomeCompani
width: 6,
default: true,
},
spacer,
{
type: 'checkbox',
id: 'show-advanced-options',
Expand All @@ -138,6 +139,7 @@ export function GetConfigFields(_self: InstanceBaseExt<WingConfig>): SomeCompani
width: 12,
default: false,
},
spacer,
{
type: 'number',
id: 'requestTimeout',
Expand All @@ -159,6 +161,28 @@ export function GetConfigFields(_self: InstanceBaseExt<WingConfig>): SomeCompani
default: false,
isVisibleExpression: `$(options:show-advanced-options) == true`,
},
{
type: 'number',
id: 'startupVariableRequestChunkSize',
label: `Variable Request Chunk`,
tooltip: 'When are requested in chunks on startup, waiting between each chunk',
width: 6,
min: 200,
max: 2000,
default: 500,
isVisibleExpression: `$(options:show-advanced-options) == true`,
},
{
type: 'number',
id: 'startupVariableRequestChunkWait',
label: `Variable Request Wait Time (ms)`,
tooltip: 'When are requested in chunks on startup, waiting between each chunk',
width: 6,
min: 20,
max: 1000,
default: 100,
isVisibleExpression: `$(options:show-advanced-options) == true`,
},
{
type: 'number',
id: 'subscriptionInterval',
Expand All @@ -170,7 +194,6 @@ export function GetConfigFields(_self: InstanceBaseExt<WingConfig>): SomeCompani
default: 9000,
isVisibleExpression: `$(options:show-advanced-options) == true`,
},
spacer,
{
type: 'static-text',
id: 'osc-forwarding-info',
Expand Down
11 changes: 6 additions & 5 deletions src/handlers/connection-handler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import osc, { OscMessage } from 'osc'
import { ModuleLogger } from './logger.js'

import { EventEmitter } from 'events'
import { OSCSomeArguments } from '@companion-module/base'
import { ModuleLogger } from './logger.js'

/**
* Handles OSC UDP connection management, message sending, and event emission for the Behringer Wing module.
* Emits 'message', 'error', and 'close' events.
*/
export class ConnectionHandler extends EventEmitter {
private osc: osc.UDPPort
private logger?: ModuleLogger
private subscriptionTimer?: NodeJS.Timeout
private subscriptionInterval: number = 9000
private logger?: ModuleLogger

/**
* Create a new ConnectionHandler.
Expand Down Expand Up @@ -60,7 +61,8 @@ export class ConnectionHandler extends EventEmitter {
})

this.osc.on('message', (msg: OscMessage) => {
this.logger?.debug(`Received ${JSON.stringify(msg)}`)
const stringValue = (msg.args as osc.MetaArgument[])[0].value
this.logger?.debug(`State updated for ${msg.address}: ${stringValue}`)
this.emit('message', msg)
})

Expand Down Expand Up @@ -157,8 +159,7 @@ export class ConnectionHandler extends EventEmitter {
args: args,
}
this.osc.send(command)
// this.osc.send({ address: cmd, args: [] }) // a bit ugly, but needed to keep the desk state up to date in companion
if (preventLog) return
this.logger?.debug(`Sending OSC command: ${JSON.stringify(command)}`)
this.logger?.debug(`Sending OSC command: ${command.address} ${argument ?? ''}`)
}
}
20 changes: 13 additions & 7 deletions src/handlers/device-detector.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import osc from 'osc'
import { WingModel } from '../models/types.js'
import { ModuleLogger } from './logger.js'
import { EventEmitter } from 'events'
import { ModuleLogger } from './logger.js'

export interface DeviceInfo {
deviceName: string
Expand All @@ -14,6 +14,7 @@ export interface WingDeviceDetectorInterface {
subscribe(instanceId: string): void
unsubscribe(instanceId: string): void
listKnown(): DeviceInfo[]
addLogger(logger: ModuleLogger): void
}

/**
Expand All @@ -25,23 +26,29 @@ export class WingDeviceDetector extends EventEmitter implements WingDeviceDetect
private osc?: osc.UDPPort
private knownDevices = new Map<string, DeviceInfo>()
private queryTimer: NodeJS.Timeout | undefined
private logger?: ModuleLogger
private noDeviceTimeout: NodeJS.Timeout | undefined
private logger?: ModuleLogger

constructor(logger?: ModuleLogger) {
super()
this.logger = logger
}

/**
* Add a logger
* @param logger An instance of a ModuleLogger
*/
public addLogger(logger: ModuleLogger): void {
this.logger = logger
}

/**
* Register a subscriber to start device detection.
* @param instanceId Unique identifier for the subscriber.
*/
public subscribe(instanceId: string): void {
const startListening = this.subscribers.size === 0

this.subscribers.add(instanceId)

if (startListening) {
this.startListening()
}
Expand Down Expand Up @@ -71,7 +78,6 @@ export class WingDeviceDetector extends EventEmitter implements WingDeviceDetect
*/
private startListening(): void {
this.knownDevices.clear()

if (this.subscribers.size === 0) {
return
}
Expand Down Expand Up @@ -112,6 +118,7 @@ export class WingDeviceDetector extends EventEmitter implements WingDeviceDetect
})

this.osc.on('message', (message): void => {
this.logger?.debug(`received device detector message ${JSON.stringify(message)}`)
const args = message.args as osc.MetaArgument[]
if (!args || args.length === 0 || args[0].type !== 's') {
return
Expand Down Expand Up @@ -141,7 +148,7 @@ export class WingDeviceDetector extends EventEmitter implements WingDeviceDetect
// If a device has not been seen for over a minute, remove it
this.knownDevices.set(info.address, info)
for (const [id, data] of Array.from(this.knownDevices.entries())) {
if (data.lastSeen < Date.now() - 60000) {
if (data.lastSeen < Date.now() - 20000) {
this.logger?.info(`Removing console ${data.deviceName} at ${data.address} from known devices due to timeout`)
this.knownDevices.delete(id)
}
Expand All @@ -152,7 +159,6 @@ export class WingDeviceDetector extends EventEmitter implements WingDeviceDetect
this.noDeviceTimeout = undefined
}
})

this.osc.open()
}

Expand Down
27 changes: 14 additions & 13 deletions src/handlers/feedback-handler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import EventEmitter from 'events'
import { ModuleLogger } from './logger.js'
import debounceFn from 'debounce-fn'
import { FeedbackId } from '../feedbacks.js'
import { OscMessage } from 'osc'
import { WingSubscriptions } from '../state/index.js'
import { ModuleLogger } from './logger.js'

/**
* Handles feedback updates based on incoming OSC messages and manages feedback subscriptions.
Expand All @@ -12,9 +12,9 @@ import { WingSubscriptions } from '../state/index.js'
export class FeedbackHandler extends EventEmitter {
private readonly messageFeedbacks = new Set<FeedbackId>()
private readonly debounceMessageFeedbacks: () => void
private logger?: ModuleLogger
private pollTimeout?: NodeJS.Timeout
private pollInterval: number = 3000
private logger: ModuleLogger | undefined

subscriptions?: WingSubscriptions

Expand All @@ -24,8 +24,8 @@ export class FeedbackHandler extends EventEmitter {
*/
constructor(logger?: ModuleLogger) {
super()
this.logger = logger

this.logger = logger
this.subscriptions = new WingSubscriptions()

this.debounceMessageFeedbacks = debounceFn(
Expand All @@ -47,16 +47,17 @@ export class FeedbackHandler extends EventEmitter {
* Process an OSC message and trigger feedback checks if needed.
* @param msg OSC message to process.
*/
processMessage(msg: OscMessage): void {
this.logger?.debug(`Processing message for feedbacks: ${msg.address}`)

const toUpdate = this.subscriptions?.getFeedbacks(msg.address)
if (toUpdate === undefined) {
return
}
if (toUpdate.length > 0) {
toUpdate.forEach((f) => this.messageFeedbacks.add(f))
this.debounceMessageFeedbacks()
processMessage(msgs: Set<OscMessage>): void {
this.logger?.debug(`Processing messages for feedbacks`)
for (const msg of msgs) {
const toUpdate = this.subscriptions?.getFeedbacks(msg.address)
if (toUpdate === undefined) {
return
}
if (toUpdate.length > 0) {
toUpdate.forEach((f) => this.messageFeedbacks.add(f))
this.debounceMessageFeedbacks()
}
}
}

Expand Down
17 changes: 15 additions & 2 deletions src/handlers/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ type LoggerFunction = (level: LogLevel, message: string) => void
export class ModuleLogger {
private moduleName: string
private loggerFn?: LoggerFunction
private enabled: boolean
debugMode: boolean
timestamps: boolean

/**
* Create a new ModuleLogger.
*
* @param moduleName Name to prefix log messages.
* @param loggerFn Optional custom log function.
* @param debugMode Whether to include source location information in logs.
* @param timestamps Whether to include timestamps in logs.
*/
constructor(moduleName: string, loggerFn?: LoggerFunction) {
this.moduleName = moduleName
this.loggerFn = loggerFn
this.debugMode = false
this.timestamps = false
this.enabled = true
}

/**
Expand All @@ -38,6 +39,7 @@ export class ModuleLogger {
* @param message Message to log.
*/
debug(message: string): void {
if (!this.enabled) return
const msg = this.formatMessage(message)
if (this.loggerFn) {
this.loggerFn('debug', `[${this.moduleName}] ${msg}`)
Expand All @@ -51,6 +53,7 @@ export class ModuleLogger {
* @param message Message to log.
*/
info(message: string): void {
if (!this.enabled) return
const msg = this.formatMessage(message)
if (this.loggerFn) {
this.loggerFn('info', `[${this.moduleName}] ${msg}`)
Expand All @@ -64,6 +67,7 @@ export class ModuleLogger {
* @param message Message to log.
*/
error(message: string): void {
if (!this.enabled) return
const msg = this.formatMessage(message)
if (this.loggerFn) {
this.loggerFn('error', `[${this.moduleName}] ${msg}`)
Expand All @@ -77,6 +81,7 @@ export class ModuleLogger {
* @param message Message to log.
*/
warn(message: string): void {
if (!this.enabled) return
const msg = this.formatMessage(message)
if (this.loggerFn) {
this.loggerFn('warn', `[${this.moduleName}] ${msg}`)
Expand All @@ -85,6 +90,14 @@ export class ModuleLogger {
}
}

enable(): void {
this.enabled = true
}

disable(): void {
this.enabled = false
}

/**
* Format the message with optional timestamp and source location.
* @private
Expand Down
18 changes: 10 additions & 8 deletions src/handlers/osc-forwarder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import { ModuleLogger } from './logger.js'

export class OscForwarder {
private port: osc.UDPPort | undefined
private logger: ModuleLogger
private logger: ModuleLogger | undefined

constructor(logger: ModuleLogger) {
constructor(logger?: ModuleLogger) {
this.logger = logger
}

setup(enabled: boolean | undefined, host?: string, port?: number): void {
setup(enabled: boolean | undefined, host?: string, port?: number, logger?: ModuleLogger): void {
this.close()

this.logger = logger

if (!enabled || !host || !port) {
return
}

this.logger.info(`Setting up OSC forwarder to ${host}:${port}`)
this.logger?.info(`Setting up OSC forwarder to ${host}:${port}`)
try {
this.port = new osc.UDPPort({
localAddress: '0.0.0.0',
Expand All @@ -28,21 +30,21 @@ export class OscForwarder {
})

this.port.on('error', (err: Error): void => {
this.logger.warn(`OSC Forwarder Error: ${err.message}`)
this.logger?.warn(`OSC Forwarder Error: ${err.message}`)
})

this.port.open()
this.logger.info(`OSC forwarding enabled to ${host}:${port}`)
this.logger?.info(`OSC forwarding enabled to ${host}:${port}`)
} catch (err: any) {
this.logger.error(`Failed to setup OSC forwarder: ${err?.message ?? err}`)
this.logger?.error(`Failed to setup OSC forwarder: ${err?.message ?? err}`)
}
}

send(message: OscMessage): void {
try {
this.port?.send(message)
} catch (err: any) {
this.logger.warn(`OSC forward send failed: ${err?.message ?? err}`)
this.logger?.warn(`OSC forward send failed: ${err?.message ?? err}`)
}
}

Expand Down
Loading
Loading