ws2801-webserver
Version:
A ready-to-use webserver for the WS2801-Pi package.
309 lines (219 loc) • 10.4 kB
text/typescript
import {ChildProcess, fork} from 'child_process';
import express from 'express';
import path from 'path';
import LedController, {LedColor, LedStrip} from 'ws2801-pi';
import {AuthService} from './auth-service';
import {DefaultConfig} from './config/config';
import {validateLedStrip} from './led-strip-validation';
import {SocketIoServer} from './socket-io-server';
import {Webserver} from './webserver';
import {Config} from './types';
export class Ws2801Webserver {
private ledController: LedController;
private webserver: Webserver;
private socketIoServer: SocketIoServer;
private authService: AuthService;
private config: Config;
private currentAnimationProcess: ChildProcess;
constructor(config?: Config, ledController?: LedController) {
this.config = config ? config : DefaultConfig;
if (!ledController && ! this.config.amountOfLeds) {
throw new Error(`Either a ledController must be provided or the amount of leds must be provided via config!`);
}
this.ledController = ledController ? ledController : new LedController(this.config.amountOfLeds);
this.webserver = new Webserver(this.config.port, this.config.logRequests === true);
this.authService = new AuthService(this.config, this.webserver);
this.socketIoServer = new SocketIoServer(this.webserver.getHttpServer());
}
public start(): void {
if (this.config.useAuth) {
this.authService.start();
}
this.addRoutes();
// this.addSocketIoListeners();
this.addSocketIoEmitters();
this.webserver.start();
}
public stop(): void {
this.authService.stop();
this.webserver.stop();
}
public getLedController(): LedController {
return this.ledController;
}
public getExpressServer(): express.Express {
return this.webserver.getExpressServer();
}
private addRoutes(): void {
this.webserver.addGetRoute('/login-required', this.loginRequired.bind(this));
this.webserver.addGetRoute('/led-strip', this.getLedStrip.bind(this));
this.webserver.addPostRoute('/led-strip/fill', this.fillLedStrip.bind(this));
this.webserver.addPostRoute('/led-strip/clear', this.clearLedStrip.bind(this));
this.webserver.addPostRoute('/led-strip/led/:ledIndex/set', this.setSingleLedColor.bind(this));
this.webserver.addPostRoute('/led-strip/brightness/set', this.setBrightness.bind(this));
this.webserver.addGetRoute('/led-strip/brightness', this.getBrightness.bind(this));
this.webserver.addPostRoute('/led-strip/set', this.setLedStrip.bind(this));
this.webserver.addPostRoute('/led-strip/animation/start', this.startAnimation.bind(this));
this.webserver.addDeleteRoute('/led-strip/animation/stop', this.stopAnimation.bind(this));
this.webserver.addGetRoute('/led-strip/animation/finished', this.waitForAnimationToFinish.bind(this));
}
// private addSocketIoListeners(): void {
// }
private addSocketIoEmitters(): void {
this.ledController.onLedStripChanged((ledStrip: LedStrip): void => {
this.socketIoServer.send('led-strip__changed', ledStrip);
});
this.ledController.onBrightnessChanged((brightness: number): void => {
this.socketIoServer.send('brightness__changed', brightness);
});
}
// Route functions
private loginRequired(_: express.Request, response: express.Response): void {
response.status(200).json({loginRequired: this.config.useAuth});
}
private async getLedStrip(_: express.Request, response: express.Response): Promise<void> {
if (this.currentAnimationProcess) {
await new Promise((resolve: Function): void => {
this.currentAnimationProcess.on('message', (receivedLedStrip: LedStrip): void => {
response.status(200).json({ledStrip: receivedLedStrip});
resolve();
});
this.currentAnimationProcess.send({action: 'get-led-strip'});
});
return;
}
const ledStrip: LedStrip = this.ledController.getLedStrip();
response.status(200).json({ledStrip: ledStrip});
}
private async fillLedStrip(request: express.Request, response: express.Response): Promise<void> {
const color: LedColor = request.body.color;
const brightness: number | undefined = request.body.brightness;
if (!color) {
response.status(400).send(`Request body must contain a 'color', e.g. {red: 255, green: 0, blue: 0}.`);
return;
}
this.ledController.fillLeds(color);
if (brightness) {
this.ledController.setBrightness(brightness);
}
await this.ledController.show();
const ledStrip: LedStrip = this.ledController.getLedStrip();
this.socketIoServer.send('led-strip-changed', {ledStrip: ledStrip});
response.status(200).json({ledStrip: ledStrip});
}
private async clearLedStrip(request: express.Request, response: express.Response): Promise<void> {
await this.ledController.clearLeds().show();
const ledStrip: LedStrip = this.ledController.getLedStrip();
this.socketIoServer.send('led-strip-changed', {ledStrip: ledStrip});
response.status(200).json({ledStrip: ledStrip});
}
private async setSingleLedColor(request: express.Request, response: express.Response): Promise<void> {
const enteredLedIndex: string = request.params.ledIndex;
const ledIndex: number = parseInt(enteredLedIndex);
const color: LedColor = request.body.color;
const brightness: number | undefined = request.body.brightness;
if (Number.isNaN(ledIndex) || ledIndex < 0 || ledIndex >= this.config.amountOfLeds) {
response.status(400).send(`URL must contain a led index between 0 and ${this.config.amountOfLeds - 1} (Received ${enteredLedIndex}).`);
return;
}
if (!color) {
response.status(400).send(`Request body must contain a 'color', e.g. {red: 255, green: 0, blue: 0}.`);
return;
}
this.ledController.setLed(ledIndex, color);
if (brightness) {
this.ledController.setBrightness(brightness);
}
await this.ledController.show();
const ledStrip: LedStrip = this.ledController.getLedStrip();
this.socketIoServer.send('led-strip-changed', {ledStrip: ledStrip});
response.status(200).json({ledStrip: ledStrip});
}
private async setBrightness(request: express.Request, response: express.Response): Promise<void> {
const brightness: number | 'auto' = request.body.brightness;
if (brightness == undefined || (typeof brightness !== 'number' && brightness !== 'auto') || brightness < 0 || brightness > 100) {
response.status(400).send(`Request body must contain a 'brightness' (number between 0 and 100 or 'auto' for automatic brightness).`);
return;
}
await this.ledController.setBrightness(brightness).show();
if (this.currentAnimationProcess) {
this.currentAnimationProcess.send({action: 'set-brightness', brightness: brightness});
}
this.socketIoServer.send('brightness-changed', {brightness: brightness});
response.status(200).send('success');
}
private async getBrightness(request: express.Request, response: express.Response): Promise<void> {
const brightness: number | 'auto' = this.ledController.getBrightness();
response.status(200).json({brightness: brightness});
}
private async setLedStrip(request: express.Request, response: express.Response): Promise<void> {
const ledStrip: LedStrip = request.body.ledStrip;
const brightness: number | undefined = request.body.brightness;
if (!ledStrip) {
response.status(400).send(`Request body must contain a 'ledStrip' (an array of LedColors with the length of the led strip).`);
return;
}
try {
validateLedStrip(this.ledController.getLedStrip().length, ledStrip);
} catch (error) {
response.status(400).send(error.message);
return;
}
for (let ledIndex: number = 0; ledIndex < ledStrip.length; ledIndex++) {
this.ledController.setLed(ledIndex, ledStrip[ledIndex]);
}
if (brightness) {
this.ledController.setBrightness(brightness);
}
await this.ledController.show();
const renderedLedStrip: LedStrip = this.ledController.getLedStrip();
this.socketIoServer.send('led-strip-changed', {ledStrip: renderedLedStrip});
response.status(200).json({ledStrip: renderedLedStrip});
}
private async startAnimation(request: express.Request, response: express.Response): Promise<void> {
const animationScript: string = request.body.animationScript;
if (animationScript == undefined) {
response.status(400).send(`Request body must contain a 'animationScript'.`);
return;
}
if (this.currentAnimationProcess) {
this.currentAnimationProcess.kill();
this.currentAnimationProcess = undefined;
}
const brightness: number | 'auto' = this.ledController.getBrightness();
this.currentAnimationProcess =
fork(path.join(__dirname, 'animator.js'), [this.ledController.getLedStrip().length.toString(), animationScript, brightness.toString()], {});
this.socketIoServer.send('animation__started');
// tslint:disable-next-line: typedef no-any
const eventCallback = (message: any): void => {
if (message === 'animation-finished') {
this.currentAnimationProcess.kill();
this.currentAnimationProcess = undefined;
this.socketIoServer.send('animation__finished');
} else if (message.action === 'led-strip-changed') {
this.socketIoServer.send('led-strip__changed', message.ledStrip);
}
};
this.currentAnimationProcess.on('message', eventCallback);
response.status(200).send('success!');
}
private async stopAnimation(request: express.Request, response: express.Response): Promise<void> {
if (this.currentAnimationProcess) {
this.currentAnimationProcess.kill();
this.currentAnimationProcess = undefined;
}
this.socketIoServer.send('animation__stopped');
response.status(200).send('success!');
}
private async waitForAnimationToFinish(request: express.Request, response: express.Response): Promise<void> {
if (!this.currentAnimationProcess) {
response.status(200).send('success!');
}
// tslint:disable-next-line: typedef no-any
this.currentAnimationProcess.on('message', (message: any): void => {
if (message === 'animation-finished') {
response.status(200).send('success!');
}
});
}
}