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[] };
}