Skip to content
Snippets Groups Projects
Commit 04aa3ab8 authored by Simon Döring's avatar Simon Döring
Browse files

Add functionality to notify controller

parent 40f0b23c
Branches
No related tags found
No related merge requests found
......@@ -33,6 +33,12 @@ Below is a description of the config file's properties:
* `cameraSlots`: The camera slots available for this room. Defaults to `4`.
* `notifyPath`: A path to a file which the camera server will append messages to.
This is used to notify the controller (usually PULT).
The path can either be absolute or relative to the config file but must not start with a tilde.
If this property is emitted or an empty string is provided, the notify feature will not be used.
## Stdin-Interface
The camera server is controlled by PULT via its stdin. One could also implement the interface in any other program to manage the CVH-Camera.
......@@ -68,6 +74,20 @@ The current state of the camera feeds is saved in the server's memory and will b
| `hide` | Hides the feed of the provided slot. <br/> Note that the feed will still be transmitted to the viewer but is just hidden. By doing that, the feed can be shown again with a very low latency. <br/><br/> **Usage**: `hide <slot>`
| `show` | Shows the feed of the provided slot in case it was hidden. <br/><br/> **Usage**: `show <slot>`
## Notify-Interface
The camera server can notify its controller (usually PULT) by writing to a file which is provided in the config.
The controller can then read the file and process the messages.
In the case of PULT this file is a named pipe (mkfifo), which works perfectly fine.
This is a list of all sent messages. Note that a newline character `\n` is appended to every message.
| Message | Description
| ---------------------------------- | -----------
| `new_feed <slot>` | Sent after a sender on a slot has started transmitting a feed.
| `remove_feed <slot>` | Sent after a sender on a slot has stopped transmitting a feed or the slot is deactivated (which also removes the feed).
## Socket Traffic
This section describes the socket traffic and the socket.io events that are used.
......
{
"port": 5000,
"cameraSlots": 4
"cameraSlots": 4,
"notifyPath": "./path-relative-to-config/or-absolute-path/camera-server-output"
}
......@@ -4,13 +4,13 @@ import * as path from 'path';
interface Config {
port: number;
cameraSlots: number;
notifyPath: string;
}
// Required to access config with config[key]
// But only an object of type Config should be returned
interface IndexableConfig extends Config {
// Change type once non-number values are added
[key: string]: number;
[key: string]: number | string;
}
let configPath = process.env.CONFIG_PATH;
......@@ -22,7 +22,7 @@ if (configPath) {
fileContent = fs.readFileSync(configPath).toString();
} catch (err) {
console.log(
`Could not read config at ${path.resolve(configPath)}:`,
`Error: Could not read config at ${path.resolve(configPath)}:`,
err
);
console.log('Using default values');
......@@ -33,7 +33,8 @@ if (configPath) {
const indexableConfig: IndexableConfig = {
port: 5000,
cameraSlots: 4
cameraSlots: 4,
notifyPath: ''
};
if (fileContent) {
......@@ -59,12 +60,12 @@ if (fileContent) {
);
}
} else {
console.log(`Unknown property ${key} in config`);
console.log(`Error: Unknown property ${key} in config`);
}
});
} else {
console.log(
`Config at ${path.resolve(
`Error: Config at ${path.resolve(
configPath!
)} is malformed - using default values`
);
......@@ -72,6 +73,12 @@ if (fileContent) {
}
const config = indexableConfig as Config;
if (config.notifyPath && !path.isAbsolute(config.notifyPath) && configPath) {
config.notifyPath = path.resolve(
path.dirname(configPath),
config.notifyPath
);
}
console.log('Using config:', config);
......
import * as fs from 'fs';
import { config } from '../../config/config';
// Saves the amount of blocking append calls
// in case the named pipe is not read by PULT
let blockingAppendCalls = 0;
const timeoutTime = 5000;
const printTimeoutMessage = (notifyPath: string) => {
console.log(
`Error: Controller did not read file at path '${notifyPath}' for ${timeoutTime}ms`
);
};
const notifyController = (message: string) => {
const { notifyPath } = config;
if (notifyPath) {
if (fs.existsSync(notifyPath)) {
const timeoutId = setTimeout(
printTimeoutMessage.bind(null, notifyPath),
timeoutTime
);
console.log(
`Notifying controller about message '${message}' using file at path '${notifyPath}'`
);
blockingAppendCalls += 1;
fs.appendFile(notifyPath, message + '\n', (err) => {
if (err) {
console.log(
`Error: Tried to notify controller about message '${message}' but could not write to path '${notifyPath}' - Error:`,
err
);
}
blockingAppendCalls -= 1;
clearTimeout(timeoutId);
});
} else {
console.log(
`Error: Tried to notify controller about message '${message}' using file at path '${notifyPath}' which does not exist`
);
}
}
};
export const isBlocking = () => blockingAppendCalls !== 0;
export const notifyNewFeed = (slot: number) => {
notifyController(`new_feed ${slot}`);
};
export const notifyRemoveFeed = (slot: number) => {
notifyController(`remove_feed ${slot}`);
};
import { socketIO } from '../socket-io';
import { cameraSlotState } from '../../state/camera-slot-state';
import { CommandDescriptor } from '../../models/command-descriptor';
import {
notifyNewFeed,
notifyRemoveFeed
} from '../../io-interface/handlers/output-handlers';
export const emitNewFeed = (slot: number) => {
const cameraState = cameraSlotState[slot];
const slotState = cameraSlotState[slot];
socketIO.emit('new_feed', {
slot,
feedId: cameraState.feedId,
visibility: cameraState.visibility,
geometry: cameraState.geometry
feedId: slotState.feedId,
visibility: slotState.visibility,
geometry: slotState.geometry
});
notifyNewFeed(slot);
};
export const emitRemoveFeed = (slot: number) => {
socketIO.emit('remove_feed', { slot });
notifyRemoveFeed(slot);
};
export const handleQueryState = (fn: Function) => {
......@@ -26,12 +32,12 @@ export const handleQueryState = (fn: Function) => {
};
} = {};
for (let i = 0; i < cameraSlotState.length; i++) {
const cameraState = cameraSlotState[i];
if (cameraState.feedActive) {
const slotState = cameraSlotState[i];
if (slotState.feedActive) {
response[i] = {
feedId: cameraState.feedId,
visibility: cameraState.visibility,
geometry: cameraState.geometry
feedId: slotState.feedId,
visibility: slotState.visibility,
geometry: slotState.geometry
};
}
}
......
import { socketIO } from '../socket-io/socket-io';
import { isBlocking } from '../io-interface/handlers/output-handlers';
interface ExitHandlerOptions {
cleanup?: boolean;
exit?: boolean;
}
const cleanup = () => {
const exitHandler = (
options: ExitHandlerOptions,
exitCode: string | number
) => {
if (options.cleanup) {
console.log('cleanup');
socketIO.emit('remove_all_feeds');
};
const exitHandler = (options: ExitHandlerOptions, exitCode: number) => {
if (options.cleanup) cleanup();
if (exitCode || exitCode === 0) console.log(exitCode);
if (options.exit) process.exit();
}
if (exitCode || exitCode === 0) {
console.log('Exit code:', exitCode);
if (exitCode === 0 && isBlocking()) {
console.log('Aborting process due to blocking file append');
process.abort();
}
}
if (options.exit) {
process.exit();
}
};
export const registerCleanupLogic = () => {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment