Lots of changes
- ✅ Feedback van dataSensor array - ✅ dataSliders min/max and unit - ✅ Camera before Unity - ✅ Restart knop buiten service mode - ✅ Operator phonenumber button - ✅ Gracefull shutdown - ✅ Out of service control
This commit is contained in:
@@ -39,7 +39,7 @@ export class CameraRunner {
|
||||
}
|
||||
|
||||
sendCommand(
|
||||
command: string,
|
||||
command: 'reboot' | 'restart',
|
||||
callback: (response: { succeed: boolean; message?: string }) => void
|
||||
) {
|
||||
if (this.socket == null || !this.socket.connected)
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface Config {
|
||||
webServer: ConfigWebServer;
|
||||
unity: ConfigUnity;
|
||||
cameraRunner: ConfigCameraRunner;
|
||||
support: ConfigSupport;
|
||||
}
|
||||
|
||||
export interface ConfigWebServer {
|
||||
@@ -132,3 +133,7 @@ export interface ConfigCameraRunner {
|
||||
|
||||
pollInterval: number;
|
||||
}
|
||||
|
||||
export interface ConfigSupport {
|
||||
telephone: string;
|
||||
}
|
||||
|
||||
@@ -27,4 +27,7 @@ export const DefaultConfiguration: Config = {
|
||||
|
||||
pollInterval: 5000,
|
||||
},
|
||||
support: {
|
||||
telephone: '+31613392837',
|
||||
},
|
||||
};
|
||||
|
||||
31
src/Main.ts
31
src/Main.ts
@@ -31,4 +31,35 @@ export class Main {
|
||||
this.UnityRunner.start();
|
||||
}, this.Config.unity.executable.startUpDelay ?? 0);
|
||||
}
|
||||
|
||||
async restart() {
|
||||
console.log('Stopping UnityRunner...');
|
||||
await this.UnityRunner.stop();
|
||||
|
||||
const doReboot = !process.argv.includes('--no-reboot');
|
||||
console.log(`${doReboot ? 'Rebooting' : 'Restarting'} CameraRunner...`);
|
||||
const succeed = await new Promise((resolve) => {
|
||||
this.CameraRunner.sendCommand(
|
||||
doReboot ? 'reboot' : 'restart',
|
||||
(response: { succeed: boolean; message?: string }) => {
|
||||
if (!response.succeed) {
|
||||
console.error(
|
||||
'Failed to reboot CameraRunner:',
|
||||
response.message
|
||||
);
|
||||
resolve(false);
|
||||
} else {
|
||||
console.log('CameraRunner rebooted successfully.');
|
||||
resolve(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
if (!succeed) return;
|
||||
|
||||
console.log('Starting UnityRunner...');
|
||||
await this.UnityRunner.start();
|
||||
|
||||
console.log('Restart complete.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,42 +82,77 @@ export class UnityRunner {
|
||||
else console.log(PREFIX, message ?? error);
|
||||
}
|
||||
|
||||
private restartTriggered: boolean = false;
|
||||
async restart(instant: boolean = false) {
|
||||
if (this.restartTriggered) return;
|
||||
clearInterval(this.statusClock);
|
||||
async stop() {
|
||||
if (this.process == null) return;
|
||||
|
||||
this._Main.WebServer.Calibration.hasCalibrationImage = false;
|
||||
this.restartTriggered = true;
|
||||
|
||||
if (this._Main.UnityWebSocket.state === 'CONNECTED') {
|
||||
this._Main.UnityWebSocket.quitApplication();
|
||||
|
||||
this.setInfo(
|
||||
'Requested quit through WebSocket...',
|
||||
null,
|
||||
'STARTING'
|
||||
);
|
||||
} else {
|
||||
this.process.kill('SIGTERM');
|
||||
this.setInfo('Requested quit through SIGTERM...', null, 'STARTING');
|
||||
}
|
||||
|
||||
const quitSucceeded = await new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
clearInterval(c);
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
|
||||
const c = setInterval(() => {
|
||||
if (
|
||||
this.process != null &&
|
||||
!this.process.killed &&
|
||||
this.process.exitCode == null
|
||||
)
|
||||
return;
|
||||
|
||||
clearTimeout(timeout);
|
||||
clearInterval(c);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
|
||||
if (!quitSucceeded) this.process.kill('SIGKILL');
|
||||
|
||||
this.setInfo('Stopped', null, 'STOPPED');
|
||||
|
||||
this._Main.UnityWebSocket.disconnect();
|
||||
|
||||
this._Main.WebServer.Calibration.hasCalibrationImage = false;
|
||||
this.startTime = -1;
|
||||
}
|
||||
|
||||
restartTriggered: boolean = false;
|
||||
async restart(instant: boolean = false) {
|
||||
if (this.restartTriggered) return;
|
||||
this.restartTriggered = true;
|
||||
clearInterval(this.statusClock);
|
||||
|
||||
this.startTime = -1;
|
||||
this.broadcastState();
|
||||
|
||||
await delay(2000);
|
||||
if (!instant) await delay(2000);
|
||||
|
||||
if (this.output.current.length > 0) {
|
||||
this.output.last = [...this.output.current];
|
||||
this.output.current = [];
|
||||
}
|
||||
|
||||
if (instant)
|
||||
await this.stop();
|
||||
|
||||
if (instant) {
|
||||
await delay(1000);
|
||||
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) {
|
||||
await delay(2000);
|
||||
} else {
|
||||
this.message = 'Reconnecting in 10 seconds...';
|
||||
this.broadcastState();
|
||||
|
||||
@@ -199,7 +234,7 @@ export class UnityRunner {
|
||||
this.setInfo(
|
||||
'Process exited',
|
||||
`Process exited with code ${code} and signal ${signal}`,
|
||||
'STOPPED'
|
||||
'PROBLEM'
|
||||
);
|
||||
this.restart();
|
||||
});
|
||||
|
||||
@@ -17,7 +17,9 @@ export class UnityWebSocket {
|
||||
zedPath: '',
|
||||
zedReady: false,
|
||||
zedFPS: '-',
|
||||
parameters: [],
|
||||
outOfService: null,
|
||||
sliders: [],
|
||||
sensors: [],
|
||||
};
|
||||
|
||||
socket: WebSocket;
|
||||
@@ -32,30 +34,72 @@ export class UnityWebSocket {
|
||||
const sliderIndex: number = args[0];
|
||||
const percentage: number = args[1];
|
||||
|
||||
if (this.parameters.parameters[sliderIndex] == undefined)
|
||||
return;
|
||||
this.parameters.parameters[sliderIndex].outputValue =
|
||||
percentage;
|
||||
this.setSliderValue(sliderIndex, percentage);
|
||||
break;
|
||||
|
||||
if (
|
||||
this.socket == null ||
|
||||
this.socket.readyState !== WebSocket.OPEN
|
||||
)
|
||||
return;
|
||||
case 'enableOutOfService':
|
||||
const enableCallback: Function = args[0];
|
||||
if (typeof enableCallback !== 'function') return;
|
||||
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
type: 'set_slider_value',
|
||||
sliderIndex: sliderIndex,
|
||||
sliderValue: percentage,
|
||||
})
|
||||
);
|
||||
this.setOutOfService(true);
|
||||
|
||||
this.broadcastState();
|
||||
enableCallback({ succeed: true });
|
||||
break;
|
||||
|
||||
case 'disableOutOfService':
|
||||
const disableCallback: Function = args[0];
|
||||
if (typeof disableCallback !== 'function') return;
|
||||
|
||||
this.setOutOfService(false);
|
||||
|
||||
disableCallback({ succeed: true });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
quitApplication() {
|
||||
if (this.socket == null || this.socket.readyState !== WebSocket.OPEN)
|
||||
return;
|
||||
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
type: 'quit_application',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
setSliderValue(sliderIndex: number, sliderValue: number) {
|
||||
if (this.socket == null || this.socket.readyState !== WebSocket.OPEN)
|
||||
return;
|
||||
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
type: 'set_slider_value',
|
||||
sliderIndex,
|
||||
sliderValue,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.parameters.sliders[sliderIndex] == undefined) return;
|
||||
this.parameters.sliders[sliderIndex].outputValue = sliderValue;
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
setOutOfService(state: boolean) {
|
||||
if (this.socket == null || this.socket.readyState !== WebSocket.OPEN)
|
||||
return;
|
||||
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
type: 'set_out_of_service',
|
||||
showOutOfService: state,
|
||||
})
|
||||
);
|
||||
|
||||
this.parameters.outOfService = true;
|
||||
this.broadcastState();
|
||||
}
|
||||
|
||||
broadcastState() {
|
||||
this._Main.WebServer.socket.emit(
|
||||
'unityWebSocketState',
|
||||
@@ -99,7 +143,28 @@ export class UnityWebSocket {
|
||||
this.parameters.zedReady =
|
||||
message.heartbeat.zedCamera.isZedReady;
|
||||
this.parameters.zedFPS = message.heartbeat.zedCamera.cameraFPS;
|
||||
this.parameters.parameters = message.heartbeat.dataSliders;
|
||||
this.parameters.outOfService =
|
||||
message.heartbeat.showOutOfService ?? false;
|
||||
this.parameters.sensors = message.heartbeat.dataSensors;
|
||||
this.parameters.sliders = message.heartbeat.dataSliders.map(
|
||||
(slider) => {
|
||||
return {
|
||||
...slider,
|
||||
min: slider.min ?? 0,
|
||||
max: slider.max ?? 1,
|
||||
unit: slider.unit ?? '%',
|
||||
visualMultiplier:
|
||||
(slider.min ?? 0) == 0 && (slider.max ?? 1) == 1
|
||||
? 100
|
||||
: null,
|
||||
decimalPlaces:
|
||||
(slider.min ?? 0) == 0 && (slider.max ?? 1) == 1
|
||||
? 0
|
||||
: 2,
|
||||
} as UnityParameterSlider;
|
||||
}
|
||||
);
|
||||
|
||||
this.broadcastState();
|
||||
break;
|
||||
|
||||
@@ -182,6 +247,7 @@ export class UnityWebSocket {
|
||||
});
|
||||
|
||||
this.socket.on('close', () => {
|
||||
if (this._Main.UnityRunner.restartTriggered) return;
|
||||
if (this.restartRequested) return;
|
||||
this.setInfo(
|
||||
'Disconnected',
|
||||
@@ -247,7 +313,16 @@ interface UnityParameters {
|
||||
zedPath: string;
|
||||
zedReady: boolean;
|
||||
zedFPS: string;
|
||||
parameters: UnitySocketMessageHeartbeat['heartbeat']['dataSliders'];
|
||||
outOfService: boolean;
|
||||
sliders: UnityParameterSlider[];
|
||||
sensors: UnitySocketMessageHeartbeat['heartbeat']['dataSensors'];
|
||||
}
|
||||
|
||||
type UnityHeartbeatSlider =
|
||||
UnitySocketMessageHeartbeat['heartbeat']['dataSliders'][number];
|
||||
interface UnityParameterSlider extends UnityHeartbeatSlider {
|
||||
visualMultiplier?: number;
|
||||
decimalPlaces?: number;
|
||||
}
|
||||
|
||||
type UnitySocketMessage =
|
||||
@@ -261,10 +336,18 @@ interface UnitySocketMessageBase {
|
||||
interface UnitySocketMessageHeartbeat extends UnitySocketMessageBase {
|
||||
type: 'heartbeat_data';
|
||||
heartbeat: {
|
||||
dataSensors: {
|
||||
sensorIndex: number;
|
||||
deviceName: string;
|
||||
outputValue: number;
|
||||
}[];
|
||||
dataSliders: {
|
||||
sliderIndex: number;
|
||||
sliderName: string;
|
||||
outputValue: number;
|
||||
min: number;
|
||||
max: number;
|
||||
unit: string;
|
||||
}[];
|
||||
dataTimeline: {
|
||||
isStanding: boolean;
|
||||
@@ -278,6 +361,7 @@ interface UnitySocketMessageHeartbeat extends UnitySocketMessageBase {
|
||||
streamInputPort: number;
|
||||
zedGrabError: number;
|
||||
};
|
||||
showOutOfService?: boolean;
|
||||
};
|
||||
}
|
||||
interface UnitySocketMessageCameraFrame extends UnitySocketMessageBase {
|
||||
|
||||
@@ -50,7 +50,30 @@ export class WebServer {
|
||||
'unityWebSocketState',
|
||||
this._Main.UnityWebSocket.getState()
|
||||
);
|
||||
socket.emit(
|
||||
'supportNumber',
|
||||
this._Main.Config.support.telephone.replace('+', '')
|
||||
);
|
||||
|
||||
socket.on(
|
||||
'restartInstallation',
|
||||
(
|
||||
callback: (response: {
|
||||
succeed: boolean;
|
||||
message?: string;
|
||||
}) => void
|
||||
) => {
|
||||
if (this._Main.CameraRunner.state !== 'CONNECTED')
|
||||
return callback({
|
||||
succeed: false,
|
||||
message:
|
||||
'Cannot reboot camera runner because it is not connected.',
|
||||
});
|
||||
|
||||
this._Main.restart();
|
||||
callback({ succeed: true });
|
||||
}
|
||||
);
|
||||
socket.on('cameraRunner', (command: string, ...args: any[]) =>
|
||||
this._Main.CameraRunner.handle(command, ...args)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user