Initial commit

This commit is contained in:
2025-10-22 22:06:16 +02:00
commit d8ca4e154f
141 changed files with 32231 additions and 0 deletions

211
src/CameraRunner.ts Normal file
View File

@@ -0,0 +1,211 @@
import { io, Socket } from 'socket.io-client';
import { Main } from './Main';
import { delay, ServiceState } from './Utils';
const PREFIX = `[CameraRunner]`;
export class CameraRunner {
private _Main: Main;
socket: Socket;
state: ServiceState = 'DISCONNECTED';
message?: string;
error?: string;
processStatus: ProcessStatus = {
state: 'STOPPED',
startTime: -1,
output: { current: [], last: [] },
};
constructor(Main: Main) {
this._Main = Main;
}
handle(command: string, ...args: any[]) {
switch (command) {
case 'restart':
case 'reboot':
const callback: (response: {
succeed: boolean;
message?: string;
}) => void = args[0];
if (typeof callback !== 'function') return;
this.sendCommand(command, callback);
break;
}
}
sendCommand(
command: string,
callback: (response: { succeed: boolean; message?: string }) => void
) {
if (this.socket == null || !this.socket.connected)
return callback({
succeed: false,
message: 'Not connected to CameraRunner',
});
this.socket.emit(
command,
(response: { succeed: boolean; message?: string }) => {
callback(response);
}
);
}
broadcastState() {
this._Main.WebServer.socket.emit('cameraRunnerState', this.getState());
}
getState(): CameraRunnerStatus {
return {
state: this.state,
message: this.message,
error: this.error,
processStatus: this.processStatus,
};
}
setInfo(message: string, error: string, state: ServiceState = 'FAILED') {
this.message = message;
this.error = error;
this.state = state;
this.broadcastState();
if (state == 'FAILED' || state == 'DISCONNECTED')
console.warn(PREFIX, message ?? error);
else console.log(PREFIX, message ?? error);
}
private pollClock: NodeJS.Timeout;
startPollClock() {
const poll = async () => {
const data: ProcessStatus = await new Promise((resolve) => {
this.socket.emit('getStatus', (response: ProcessStatus) =>
resolve(response)
);
});
this.processStatus = data;
this.broadcastState();
};
this.socket.on('simpleStatus', (simpleStatus: ProcessStatusSimple) => {
this.processStatus.state = simpleStatus.state;
this.processStatus.message = simpleStatus.message;
this.processStatus.error = simpleStatus.error;
this.broadcastState();
});
clearInterval(this.pollClock);
this.pollClock = setInterval(
() => poll(),
this._Main.Config.cameraRunner.pollInterval
);
poll();
}
async reconnect() {
clearInterval(this.pollClock);
if (this.socket != null) {
this.socket.disconnect();
this.socket.close();
this.socket = null;
}
await delay(2000);
this.message = 'Reconnecting in 10 seconds...';
this.broadcastState();
await delay(5000);
await this.connect();
}
async connect() {
this.setInfo('Connecting...', null, 'CONNECTING');
await delay(1000);
const ip = this._Main.Config.cameraRunner.webSocket.ip;
const port = this._Main.Config.cameraRunner.webSocket.port;
if (ip == null || port == null) {
return this.setInfo(
'Connection problem',
'Camera Runner WebSocket IP or port is not configured.'
);
}
this.socket = io(`ws://${ip}:${port}`, {
reconnectionAttempts: 0,
reconnectionDelay: 2000,
});
this.socket.on('connect', () => {
this.setInfo('Connected', null, 'CONNECTED');
this.startPollClock();
});
this.socket.on('disconnect', () => {
this.setInfo(
'Disconnected',
'Camera Runner was disconnected unexpectedly',
'DISCONNECTED'
);
this.reconnect();
});
this.socket.on('reconnect', () => {
this.setInfo('Connected (reconnection)', null, 'CONNECTED');
});
this.socket.on('connect_error', (err) => {
this.setInfo('Connection problem', `Connection error: ${err}`);
this.reconnect();
});
this.socket.on('error', (err) => {
this.setInfo('Connection problem', `Socket error: ${err}`);
this.reconnect();
});
this.socket.on('reconnect_attempt', () => {
this.setInfo('Reconnecting', null, 'CONNECTING');
});
this.socket.on('reconnect_failed', () => {
this.setInfo('Connection problem', 'Reconnection failed');
this.reconnect();
});
this.socket.on('close', () => {
this.setInfo('Disconnected', null, 'DISCONNECTED');
this.reconnect();
});
}
}
interface CameraRunnerStatus {
state: ServiceState;
message?: string;
error?: string;
processStatus: ProcessStatus;
}
export type ProcessStatusState = 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM';
interface ProcessStatusSimple {
state: ProcessStatusState;
message?: string;
error?: string;
}
interface ProcessStatus extends ProcessStatusSimple {
startTime: number;
output: { current: string[]; last: string[] };
}

View File

@@ -0,0 +1,134 @@
import { ensureDir, pathExists, readJSON, writeFile } from 'fs-extra';
import { Main } from '../Main';
import { join } from 'path';
import { DefaultConfiguration } from './DefaultConfiguration';
const PREFIX = '[ConfigurationManager]';
export class ConfigurationManager {
private _Main: Main;
constructor(Main: Main) {
this._Main = Main;
}
async load() {
console.log(PREFIX, 'Loading');
await ensureDir(join(this._Main.dataPath));
let configPath = join(this._Main.dataPath, 'config.json');
let configExists = await pathExists(configPath);
if (!configExists) {
await writeFile(
configPath,
JSON.stringify(DefaultConfiguration, null, 4)
);
this._Main.Config = DefaultConfiguration;
console.log(PREFIX, 'Written default configuration');
} else {
var config: Config = await readJSON(configPath);
this._Main.Config = await this.validateConfig(config);
console.log(PREFIX, 'Loaded configuration');
}
}
async validateConfig(config: Config): Promise<Config> {
const normalizedConfig: Config = this.normalizeConfig(
config,
DefaultConfiguration
);
const hasChanges =
JSON.stringify(config) !== JSON.stringify(normalizedConfig);
if (hasChanges) {
this._Main.Config = normalizedConfig;
const configPath = join(this._Main.dataPath, 'config.json');
await writeFile(
configPath,
JSON.stringify(normalizedConfig, null, 4)
);
console.log(PREFIX, 'Configuration updated and saved');
}
return normalizedConfig;
}
private normalizeConfig(current: any, template: any): any {
if (template === null || template === undefined) {
return template;
}
if (typeof template !== 'object' || Array.isArray(template)) {
if (
current !== null &&
current !== undefined &&
typeof current === typeof template
) {
if (Array.isArray(template) && Array.isArray(current)) {
if (
template.length > 0 &&
typeof template[0] === 'object' &&
!Array.isArray(template[0])
) {
return current.map((item) =>
this.normalizeConfig(item, template[0])
);
}
return current;
}
return current;
}
return template;
}
const result: any = {};
for (const key in template) {
if (template.hasOwnProperty(key)) {
result[key] = this.normalizeConfig(
current?.[key],
template[key]
);
}
}
return result;
}
}
export interface Config {
webServer: ConfigWebServer;
unity: ConfigUnity;
cameraRunner: ConfigCameraRunner;
}
export interface ConfigWebServer {
port: number;
}
export interface ConfigUnity {
executable: {
path: string;
arguments: string[];
startUpDelay?: number;
};
webSocket: {
ip: string;
port: number;
};
heartbeatInterval: number;
calibrationImageInterval: number;
}
export interface ConfigCameraRunner {
webSocket: {
ip: string;
port: number;
};
pollInterval: number;
}

View File

@@ -0,0 +1,30 @@
import { Config } from './ConfigurationManager';
export const DefaultConfiguration: Config = {
webServer: {
port: 6300,
},
unity: {
executable: {
path: '',
arguments: [],
startUpDelay: 5000,
},
webSocket: {
ip: '127.0.0.1',
port: 3000,
},
heartbeatInterval: 1000,
calibrationImageInterval: 2000,
},
cameraRunner: {
webSocket: {
ip: '127.0.0.1',
port: 6301,
},
pollInterval: 5000,
},
};

4
src/Entry.ts Normal file
View File

@@ -0,0 +1,4 @@
import { Main } from './Main';
const _Main = new Main();
_Main.start();

34
src/Main.ts Normal file
View File

@@ -0,0 +1,34 @@
import { join } from 'path';
import { WebServer } from './WebServer/WebServer';
import { homedir } from 'os';
import {
Config,
ConfigurationManager,
} from './Configuration/ConfigurationManager';
import { CameraRunner } from './CameraRunner';
import { UnityRunner } from './Unity/UnityRunner';
import { UnityWebSocket } from './Unity/UnityWebSocket';
export class Main {
dataPath = join(homedir(), 'MorphixProductions', 'NTSHControl');
ConfigurationManager = new ConfigurationManager(this);
WebServer = new WebServer(this);
CameraRunner = new CameraRunner(this);
UnityRunner = new UnityRunner(this);
UnityWebSocket = new UnityWebSocket(this);
Config: Config;
async start() {
await this.ConfigurationManager.load();
await this.WebServer.listen();
await this.CameraRunner.connect();
setTimeout(() => {
this.UnityRunner.start();
}, this.Config.unity.executable.startUpDelay ?? 0);
}
}

274
src/Unity/UnityRunner.ts Normal file
View File

@@ -0,0 +1,274 @@
import { pathExistsSync } from 'fs-extra';
import { ChildProcess, exec, spawn } from 'child_process';
import { delay } from '../Utils';
import { Main } from '../Main';
const PREFIX = '[UnityRunner]';
const LOG_OUTPUT = !process.argv.includes('--no-output-log');
export class UnityRunner {
private _Main: Main;
state: UnityRunnerState;
message?: string = 'Awaiting startup delay...';
error?: string;
startTime: number = null;
output: { current: string[]; last: string[] } = { current: [], last: [] };
process: ChildProcess;
constructor(Main: Main) {
this._Main = Main;
}
handle(command: string, ...args: any[]) {
switch (command) {
case 'restart':
const callback: (response: {
succeed: boolean;
message?: string;
}) => void = args[0];
if (typeof callback !== 'function') return;
callback(this.requestRestart());
break;
}
}
private statusClock: NodeJS.Timeout;
startStatusClock() {
clearInterval(this.statusClock);
this.statusClock = setInterval(() => {
this.broadcastState();
}, 3000);
}
requestRestart(): { succeed: boolean; message?: string } {
if (this.state !== 'RUNNING')
return {
succeed: false,
message:
'Cannot restart when process is not running. It is probably restarting already.',
};
this.restart(true);
return { succeed: true };
}
broadcastState() {
this._Main.WebServer.socket.emit('unityRunnerState', this.getStatus());
}
setInfo(
message: string,
error: string,
state: UnityRunnerState = 'PROBLEM'
) {
this.message = message;
this.error = error;
this.state = state;
this.broadcastState();
this.output.current.push(
`[${new Date().toLocaleTimeString('nl-NL')}] [System] [${state}] ${
message ?? error
}`
);
if (state == 'PROBLEM' || state == 'STOPPED')
console.warn(PREFIX, message ?? error);
else console.log(PREFIX, message ?? error);
}
private restartTriggered: boolean = false;
async restart(instant: boolean = false) {
if (this.restartTriggered) return;
clearInterval(this.statusClock);
this._Main.WebServer.Calibration.hasCalibrationImage = false;
this._Main.UnityWebSocket.disconnect();
this.restartTriggered = true;
this.startTime = -1;
this.broadcastState();
await delay(2000);
if (this.output.current.length > 0) {
this.output.last = [...this.output.current];
this.output.current = [];
}
if (instant)
this.setInfo('Process will restart shortly...', null, 'STOPPED');
if (this.process != null) {
this.process.kill('SIGTERM');
await delay(3000);
if (!this.process.killed && this.process.exitCode === null) {
this.process.kill('SIGKILL');
console.log(PREFIX, 'Sent SIGKILL to process.');
}
}
this.startTime = -1;
if (!instant) {
this.message = 'Reconnecting in 10 seconds...';
this.broadcastState();
await delay(10000);
}
await this.start();
}
async start() {
if (this.output.current.length > 0) {
this.output.last = [...this.output.current];
this.output.current = [];
}
this.startTime = Date.now();
this.restartTriggered = false;
this.broadcastState();
this._Main.WebServer.Calibration.hasCalibrationImage = false;
clearInterval(this.statusClock);
const path = this._Main.Config.unity.executable.path;
if (path == null || !pathExistsSync(path)) {
this.setInfo(
'Executable problem',
`Executable path is not set or does not exist: ${path}`
);
return;
}
if (this._Main.CameraRunner.state !== 'CONNECTED')
await new Promise<void>((resolve) => {
this.setInfo(
'Waiting for CameraRunner to connect...',
null,
'STARTING'
);
var c = setInterval(() => {
if (this._Main.CameraRunner.state !== 'CONNECTED') return;
clearInterval(c);
resolve();
}, 1000);
});
if (this._Main.CameraRunner.processStatus?.state !== 'RUNNING')
await new Promise<void>((resolve) => {
this.setInfo(
'Waiting for CameraRunner process to start...',
null,
'STARTING'
);
var c = setInterval(() => {
if (
this._Main.CameraRunner.processStatus?.state !==
'RUNNING'
)
return;
clearInterval(c);
resolve();
}, 1000);
});
const fileName = path.split(/(\/|\\)/).pop();
this.setInfo(`Starting executable: ${fileName}`, null, 'STARTING');
this.process = spawn(
path,
this._Main.Config.unity.executable.arguments,
{
stdio: 'pipe',
}
);
this.process.on('exit', (code, signal) => {
if (this.restartTriggered) return;
this.setInfo(
'Process exited',
`Process exited with code ${code} and signal ${signal}`,
'STOPPED'
);
this.restart();
});
this.process.on('error', (err) => {
this.setInfo('Process error', err.message);
this.restart();
});
this.process.stdout?.on('data', (data) => {
const lines = data
.toString()
.trim()
.split('\n')
.filter((line) => line.length > 0);
lines.forEach((line) => {
const formattedLine = `[${new Date().toLocaleTimeString(
'nl-NL'
)}] [${fileName}] ${line}`;
if (LOG_OUTPUT) console.log(PREFIX, formattedLine);
this.output.current.push(formattedLine);
});
});
this.process.stderr?.on('data', (data) => {
const lines = data
.toString()
.trim()
.split('\n')
.filter((line) => line.length > 0);
lines.forEach((line) => {
const formattedLine = `[${new Date().toLocaleTimeString(
'nl-NL'
)}] [${fileName}] [ERROR] ${line}`;
if (LOG_OUTPUT) console.error(PREFIX, formattedLine);
this.output.current.push(formattedLine);
});
});
this.startStatusClock();
setTimeout(() => {
if (
this.process == null ||
this.process?.killed ||
this.process?.exitCode != null ||
this.restartTriggered
)
return;
this.setInfo('Running', '', 'RUNNING');
this._Main.UnityWebSocket.connect();
}, 5000);
}
getStatus(): UnityRunnerStatus {
return {
state: this.state,
message: this.message,
error: this.error,
startTime: this.startTime,
output: this.output,
};
}
}
interface UnityRunnerStatus {
state: 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM';
message?: string;
error?: string;
startTime: number;
output: { current: string[]; last: string[] };
}
export type UnityRunnerState = 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM';

286
src/Unity/UnityWebSocket.ts Normal file
View File

@@ -0,0 +1,286 @@
import { RawData, WebSocket } from 'ws';
import { Main } from '../Main';
import { delay, ServiceState } from '../Utils';
const PREFIX = '[Unity]';
export class UnityWebSocket {
private _Main: Main;
state: ServiceState = 'DISCONNECTED';
message?: string = 'Waiting for process...';
error?: string;
parameters: UnityParameters = {
timelineWatching: false,
timelineStanding: false,
timelineProgress: 0,
zedPath: '',
zedReady: false,
zedFPS: '-',
parameters: [],
};
socket: WebSocket;
constructor(Main: Main) {
this._Main = Main;
}
handle(command: string, ...args: any[]) {
switch (command) {
case 'parameterValue':
const sliderIndex: number = args[0];
const percentage: number = args[1];
if (this.parameters.parameters[sliderIndex] == undefined)
return;
this.parameters.parameters[sliderIndex].outputValue =
percentage;
if (
this.socket == null ||
this.socket.readyState !== WebSocket.OPEN
)
return;
this.socket.send(
JSON.stringify({
type: 'set_slider_value',
sliderIndex: sliderIndex,
sliderValue: percentage,
})
);
this.broadcastState();
break;
}
}
broadcastState() {
this._Main.WebServer.socket.emit(
'unityWebSocketState',
this.getState()
);
}
setInfo(message: string, error: string, state: ServiceState = 'FAILED') {
this.message = message;
this.error = error;
this.state = state;
this.broadcastState();
if (state == 'FAILED' || state == 'DISCONNECTED')
console.warn(PREFIX, message ?? error);
else console.log(PREFIX, message ?? error);
}
stopFetchClocks() {
clearInterval(this.heartbeatClock);
clearInterval(this.calibrationImageClock);
}
handleSocketMessage(data: RawData) {
let message: UnitySocketMessage;
try {
message = JSON.parse(data.toString());
} catch (error) {
return;
}
switch (message.type) {
case 'heartbeat_data':
this.parameters.timelineWatching =
message.heartbeat.dataTimeline.isWatching;
this.parameters.timelineStanding =
message.heartbeat.dataTimeline.isStanding;
this.parameters.timelineProgress =
message.heartbeat.dataTimeline.timelineProgress;
this.parameters.zedPath = `${message.heartbeat.zedCamera.streamInputIP}:${message.heartbeat.zedCamera.streamInputPort}`;
this.parameters.zedReady =
message.heartbeat.zedCamera.isZedReady;
this.parameters.zedFPS = message.heartbeat.zedCamera.cameraFPS;
this.parameters.parameters = message.heartbeat.dataSliders;
this.broadcastState();
break;
case 'response_camera_frame':
this._Main.WebServer.Calibration.writeCalibrationImage(
message.imageBase64
);
break;
}
}
disconnected: boolean = false;
async disconnect() {
this.restartRequested = true;
this.disconnected = true;
if (this.socket != null) {
this.socket.close();
this.socket = null;
}
this.stopFetchClocks();
this.setInfo('Waiting for process...', null, 'DISCONNECTED');
}
private restartRequested = false;
async reconnect() {
if (this.restartRequested) return;
if (this.disconnected) return;
this.restartRequested = true;
this.stopFetchClocks();
if (this.socket != null) {
this.socket.close();
this.socket = null;
}
await delay(2000);
if (this.disconnected) return;
this.message = `Reconnecting in 10 seconds...`;
this.broadcastState();
await delay(10000);
if (this.disconnected) return;
await this.connect();
}
async connect() {
this.restartRequested = false;
this.disconnected = false;
this.stopFetchClocks();
this.setInfo('Connecting...', null, 'CONNECTING');
await delay(1000);
this.socket = new WebSocket(
`ws://${this._Main.Config.unity.webSocket.ip}:${this._Main.Config.unity.webSocket.port}`
);
this.socket.on('error', (error) => {
if (this.restartRequested) return;
this.setInfo(
'Connection error',
`Could not connect: ${error.message}`,
'FAILED'
);
this.reconnect();
});
this.socket.on('open', () => {
this.startFetchClocks();
this.setInfo('Connected', null, 'CONNECTED');
});
this.socket.on('close', () => {
if (this.restartRequested) return;
this.setInfo(
'Disconnected',
'Unity was disconnected unexpectedly',
'FAILED'
);
this.reconnect();
});
this.socket.on('message', (data) => this.handleSocketMessage(data));
}
private heartbeatClock: NodeJS.Timeout;
private calibrationImageClock: NodeJS.Timeout;
startFetchClocks() {
this.socket.send(
JSON.stringify({ type: 'set_heartbeat_auto_send', autoSend: false })
);
this.heartbeatClock = setInterval(() => {
if (
this.socket == null ||
this.socket.readyState !== WebSocket.OPEN
)
return;
this.socket.send(JSON.stringify({ type: 'request_heartbeat' }));
}, this._Main.Config.unity.heartbeatInterval);
this.calibrationImageClock = setInterval(() => {
if (
this.socket == null ||
this.socket.readyState !== WebSocket.OPEN
)
return;
this.socket.send(JSON.stringify({ type: 'request_camera_frame' }));
}, this._Main.Config.unity.calibrationImageInterval);
}
getState(): UnityWebSocketStatus {
return {
state: this.state,
message: this.message,
error: this.error,
parameters: this.parameters,
};
}
}
interface UnityWebSocketStatus {
state: ServiceState;
message?: string;
error?: string;
parameters: UnityParameters;
}
interface UnityParameters {
timelineWatching: boolean;
timelineStanding: boolean;
timelineProgress: number;
zedPath: string;
zedReady: boolean;
zedFPS: string;
parameters: UnitySocketMessageHeartbeat['heartbeat']['dataSliders'];
}
type UnitySocketMessage =
| UnitySocketMessageHeartbeat
| UnitySocketMessageCameraFrame;
interface UnitySocketMessageBase {
type: string;
timestamp: number;
}
interface UnitySocketMessageHeartbeat extends UnitySocketMessageBase {
type: 'heartbeat_data';
heartbeat: {
dataSliders: {
sliderIndex: number;
sliderName: string;
outputValue: number;
}[];
dataTimeline: {
isStanding: boolean;
isWatching: boolean;
timelineProgress: number;
};
zedCamera: {
cameraFPS: string;
isZedReady: boolean;
streamInputIP: string;
streamInputPort: number;
zedGrabError: number;
};
};
}
interface UnitySocketMessageCameraFrame extends UnitySocketMessageBase {
type: 'response_camera_frame';
imageBase64: string;
}

9
src/Utils.ts Normal file
View File

@@ -0,0 +1,9 @@
export type ServiceState =
| 'CONNECTING'
| 'CONNECTED'
| 'DISCONNECTED'
| 'FAILED';
export function delay(duration: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, duration));
}

View File

@@ -0,0 +1,41 @@
import { Router } from 'express';
import { Main } from '../Main';
import { join } from 'path';
import { pathExistsSync, readdirSync, readFile, writeFile } from 'fs-extra';
export class CalibrationRouter {
private _Main: Main;
Router: Router;
calibrationImagePath: string;
hasCalibrationImage: boolean = false;
constructor(Main: Main) {
this._Main = Main;
this.Router = Router();
this.calibrationImagePath = join(
this._Main.dataPath,
'calibrationImage.png'
);
this.registerRoutes();
}
async registerRoutes() {
this.Router.get('/calibrationImage', (req, res) => {
if (!this.hasCalibrationImage)
return res.redirect('/img/noCalibrationImage.png');
res.sendFile(this.calibrationImagePath);
});
}
async writeCalibrationImage(base64: string) {
await writeFile(
this.calibrationImagePath,
Buffer.from(base64, 'base64')
);
this.hasCalibrationImage = true;
}
}

View File

@@ -0,0 +1,51 @@
import { Router } from 'express';
import { Main } from '../Main';
import { join } from 'path';
import { pathExistsSync, readdirSync, readFile } from 'fs-extra';
export class DashboardRouter {
private _Main: Main;
Router: Router;
path: string;
constructor(Main: Main) {
this._Main = Main;
this.Router = Router();
this.path = join(
__filename,
'..',
'..',
'..',
'frontend',
'views',
'dashboard'
);
this.registerRoutes();
}
async registerRoutes() {
this.Router.get(
['/', '/dashboard', '/calibration', '/cameralogs', '/unitylogs'],
async (req, res) => {
const htmlContent = await readFile(
join(this.path, 'index.html')
);
res.setHeader('Content-Type', 'text/html');
res.send(htmlContent);
}
);
this.Router.get('/style.css', async (req, res) => {
const styleContent = await readFile(join(this.path, 'style.css'));
res.setHeader('Content-Type', 'text/css');
res.send(styleContent);
});
this.Router.get('/script.js', async (req, res) => {
const scriptContent = await readFile(join(this.path, 'script.js'));
res.setHeader('Content-Type', 'application/javascript');
res.send(scriptContent);
});
}
}

View File

@@ -0,0 +1,78 @@
import { Application } from 'express';
import * as express from 'express';
import { Server as SocketIOServer } from 'socket.io';
import { createServer, Server as HTTPServer } from 'http';
import { Main } from '../Main';
import { DashboardRouter } from './DashboardRouter';
import { join } from 'path';
import { CalibrationRouter } from './CalibrationRouter';
const PREFIX = '[WebServer]';
export class WebServer {
private _Main: Main;
Dashboard: DashboardRouter;
Calibration: CalibrationRouter;
httpServer: HTTPServer;
app: Application;
socket: SocketIOServer;
constructor(Main: Main) {
this._Main = Main;
this.Dashboard = new DashboardRouter(this._Main);
this.Calibration = new CalibrationRouter(this._Main);
this.prepare();
}
private prepare() {
this.app = express();
this.httpServer = createServer(this.app);
this.socket = new SocketIOServer(this.httpServer);
this.app.use(
express.static(
join(__filename, '..', '..', '..', 'frontend', 'static')
)
);
this.app.use(this.Dashboard.Router);
this.app.use(this.Calibration.Router);
this.socket.on('connection', (socket) => {
socket.emit(
'cameraRunnerState',
this._Main.CameraRunner.getState()
);
socket.emit('unityRunnerState', this._Main.UnityRunner.getStatus());
socket.emit(
'unityWebSocketState',
this._Main.UnityWebSocket.getState()
);
socket.on('cameraRunner', (command: string, ...args: any[]) =>
this._Main.CameraRunner.handle(command, ...args)
);
socket.on('unityRunner', (command: string, ...args: any[]) =>
this._Main.UnityRunner.handle(command, ...args)
);
socket.on('unityWebSocket', (command: string, ...args: any[]) =>
this._Main.UnityWebSocket.handle(command, ...args)
);
});
}
listen() {
return new Promise<void>((resolve) => {
const port = this._Main.Config.webServer.port;
this.httpServer.listen(port, () => {
console.log(
PREFIX,
`Listening on port http://127.0.0.1:${port}`
);
resolve();
});
});
}
}