Skip to content

Commit

Permalink
feat: satellite ws implementation #3101
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Oct 26, 2024
1 parent f489b60 commit bea03af
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ RUN mkdir $COMPANION_CONFIG_BASEDIR && chown companion:companion $COMPANION_CONF

USER companion
# Export ports for web, Satellite API and WebSocket (Elgato Plugin)
EXPOSE 8000 16622 28492
EXPOSE 8000 16622 16623 28492

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD [ "curl", "-fSsq", "http://localhost:8000/" ]

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.prebuild
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ RUN useradd -ms /bin/bash companion \

USER companion
# Export ports for web, Satellite API and WebSocket (Elgato Plugin)
EXPOSE 8000 16622 28492
EXPOSE 8000 16622 16623 28492

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD [ "curl", "-fSsq", "http://localhost:8000/" ]

Expand Down
12 changes: 8 additions & 4 deletions companion/lib/Service/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { ServiceHttps } from './Https.js'
import { ServiceOscListener } from './OscListener.js'
import { ServiceOscSender } from './OscSender.js'
import { ServiceRosstalk } from './Rosstalk.js'
import { ServiceSatellite } from './Satellite.js'
import { ServiceSatelliteTcp } from './SatelliteTcp.js'
import { ServiceSharedUdpManager } from './SharedUdpManager.js'
import { ServiceSurfaceDiscovery } from './SurfaceDiscovery.js'
import { ServiceTcp } from './Tcp.js'
import { ServiceUdp } from './Udp.js'
import { ServiceVideohubPanel } from './VideohubPanel.js'
import type { Registry } from '../Registry.js'
import type { ClientSocket } from '../UI/Handler.js'
import { ServiceSatelliteWebsocket } from './SatelliteWebsocket.js'

/**
* Class that manages all of the services.
Expand Down Expand Up @@ -46,7 +47,8 @@ export class ServiceController {
readonly emberplus: ServiceEmberPlus
readonly artnet: ServiceArtnet
readonly rosstalk: ServiceRosstalk
readonly satellite: ServiceSatellite
readonly satelliteTcp: ServiceSatelliteTcp
readonly satelliteWebsocket: ServiceSatelliteWebsocket
readonly elgatoPlugin: ServiceElgatoPlugin
readonly videohubPanel: ServiceVideohubPanel
readonly bonjourDiscovery: ServiceBonjourDiscovery
Expand All @@ -63,7 +65,8 @@ export class ServiceController {
this.emberplus = new ServiceEmberPlus(registry)
this.artnet = new ServiceArtnet(registry)
this.rosstalk = new ServiceRosstalk(registry)
this.satellite = new ServiceSatellite(registry)
this.satelliteTcp = new ServiceSatelliteTcp(registry)
this.satelliteWebsocket = new ServiceSatelliteWebsocket(registry)
this.elgatoPlugin = new ServiceElgatoPlugin(registry)
this.videohubPanel = new ServiceVideohubPanel(registry)
this.bonjourDiscovery = new ServiceBonjourDiscovery(registry)
Expand All @@ -85,7 +88,8 @@ export class ServiceController {
this.oscListener.updateUserConfig(key, value)
this.oscSender.updateUserConfig(key, value)
this.rosstalk.updateUserConfig(key, value)
this.satellite.updateUserConfig(key, value)
this.satelliteTcp.updateUserConfig(key, value)
this.satelliteWebsocket.updateUserConfig(key, value)
this.tcp.updateUserConfig(key, value)
this.udp.updateUserConfig(key, value)
this.videohubPanel.updateUserConfig(key, value)
Expand Down
4 changes: 2 additions & 2 deletions companion/lib/Service/SatelliteApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface SatelliteSocketWrapper {
}

export interface SatelliteInitSocketResult {
processMessage(data: Buffer): void
processMessage(data: string): void
cleanupDevices(): number
}

Expand Down Expand Up @@ -225,7 +225,7 @@ export class ServiceSatelliteApi extends CoreBase {
let receivebuffer = ''
return {
processMessage: (data) => {
receivebuffer += data.toString()
receivebuffer += data

let i = 0,
line = '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Registry } from '../Registry.js'
import { ServiceSatelliteApi } from './SatelliteApi.js'

/**
* Class providing the Satellite/Remote Surface api.
* Class providing the Satellite/Remote Surface api over tcp.
*
* @author Håkon Nessjøen <[email protected]>
* @author Keith Rocheck <[email protected]>
Expand All @@ -24,15 +24,15 @@ import { ServiceSatelliteApi } from './SatelliteApi.js'
* develop commercial activities involving the Companion software without
* disclosing the source code of your own applications.
*/
export class ServiceSatellite extends ServiceBase {
export class ServiceSatelliteTcp extends ServiceBase {
readonly #api: ServiceSatelliteApi

server: net.Server | undefined = undefined
#server: net.Server | undefined = undefined

readonly #clients = new Set<Socket>()

constructor(registry: Registry) {
super(registry, 'Service/Satellite', null, null)
super(registry, 'Service/SatelliteTcp', null, null)

this.#api = new ServiceSatelliteApi(registry)

Expand All @@ -42,9 +42,9 @@ export class ServiceSatellite extends ServiceBase {
}

listen() {
this.server = net.createServer((socket) => {
this.#server = net.createServer((socket) => {
const name = socket.remoteAddress + ':' + socket.remotePort
const socketLogger = LogController.createLogger(`Service/Satellite/${name}`)
const socketLogger = LogController.createLogger(`Service/SatelliteTcp/${name}`)

this.#clients.add(socket)

Expand Down Expand Up @@ -75,23 +75,23 @@ export class ServiceSatellite extends ServiceBase {

socket.on('close', doCleanup)

socket.on('data', processMessage)
socket.on('data', (data) => processMessage(data.toString()))
})
this.server.on('error', (e) => {
this.#server.on('error', (e) => {
this.logger.debug(`listen-socket error: ${e}`)
})

try {
this.server.listen(this.port)
this.#server.listen(this.port)
} catch (e) {
this.logger.debug(`ERROR opening port this.port for companion satellite devices`)
this.logger.debug(`ERROR opening tcp port ${this.port} for companion satellite devices`)
}
}

close(): void {
if (this.server) {
this.server.close()
this.server = undefined
if (this.#server) {
this.#server.close()
this.#server = undefined
}

// Disconnect all clients
Expand Down
116 changes: 116 additions & 0 deletions companion/lib/Service/SatelliteWebsocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { ServiceBase } from './Base.js'
import LogController from '../Log/Controller.js'
import type { Registry } from '../Registry.js'
import { ServiceSatelliteApi } from './SatelliteApi.js'
import { WebSocketServer } from 'ws'

/**
* Class providing the Satellite/Remote Surface api over websockets.
*
* @author Håkon Nessjøen <[email protected]>
* @author Keith Rocheck <[email protected]>
* @author William Viker <[email protected]>
* @author Julian Waller <[email protected]>
* @since 2.2.0
* @copyright 2022 Bitfocus AS
* @license
* 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.
*/
export class ServiceSatelliteWebsocket extends ServiceBase {
readonly #api: ServiceSatelliteApi

#server: WebSocketServer | undefined = undefined

constructor(registry: Registry) {
super(registry, 'Service/SatelliteWebsocket', null, null)

this.#api = new ServiceSatelliteApi(registry)

this.port = 16623

this.init()
}

listen() {
if (this.#server === undefined) {
try {
this.#server = new WebSocketServer({
port: this.port,
})

this.#server.on('error', (e) => {
this.logger.debug(`listen-socket error: ${e}`)
})

this.#server.on('connection', (socket) => {
// @ts-expect-error This works but isn't in the types.. TODO: verify this
const name = socket.remoteAddress + ':' + socket.remotePort
const socketLogger = LogController.createLogger(`Service/SatelliteWs/${name}`)

let lastReceived = Date.now()

// socket.setTimeout(5000)
socket.on('error', (e) => {
socketLogger.silly('socket error:', e)
})

const { processMessage, cleanupDevices } = this.#api.initSocket(socketLogger, {
// @ts-expect-error The property exists but not in the types
remoteAddress: socket.remoteAddress,
destroy: () => socket.terminate(),
write: (data) => socket.send(data),
})

const timeoutCheck = setInterval(() => {
if (lastReceived < Date.now() - 5000) {
socketLogger.debug('socket timeout')
socket.terminate()
doCleanup()
}
}, 3000)

const doCleanup = () => {
const count = cleanupDevices()
socketLogger.info(`connection closed with ${count} connected surfaces`)

socket.removeAllListeners('data')
socket.removeAllListeners('close')

clearInterval(timeoutCheck)
}

socket.on('close', doCleanup)

socket.on('message', (data) => {
lastReceived = Date.now()

processMessage(data.toString())
})
})
} catch (e) {
this.logger.debug(`ERROR opening ws port ${this.port} for companion satellite devices`)
}
}
}

close(): void {
if (this.#server) {
this.logger.info('Shutting down')

for (const client of this.#server.clients) {
client.terminate()
}

this.#server.close()
this.#server = undefined
}
}
}
7 changes: 6 additions & 1 deletion webui/src/UserConfig/SatelliteConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ export const SatelliteConfig = observer(function SatelliteConfig(_props: UserCon
<UserConfigHeadingRow label="Satellite" />

<UserConfigStaticTextRow
label={<InlineHelp help="You can't change this value.">Satellite Listen Port</InlineHelp>}
label={<InlineHelp help="You can't change this value.">Satellite TCP Listen Port</InlineHelp>}
text={16622}
/>

<UserConfigStaticTextRow
label={<InlineHelp help="You can't change this value.">Satellite Websocket Listen Port</InlineHelp>}
text={16623}
/>
</>
)
})

0 comments on commit bea03af

Please sign in to comment.