222 lines
4.9 KiB
TypeScript
222 lines
4.9 KiB
TypeScript
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;
|
|
|
|
errorTriggerStartupDelay = 10000;
|
|
bootTime = Date.now();
|
|
|
|
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: 'reboot' | 'restart',
|
|
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 (
|
|
error != null &&
|
|
Date.now() - this.bootTime > this.errorTriggerStartupDelay
|
|
)
|
|
this._Main.Twilio.sendError('CameraRunner', error);
|
|
|
|
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._Main.Twilio.resetError('CameraRunner');
|
|
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[] };
|
|
}
|