Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the disco fancier #34

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions examples/disco/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "",
"main": "build/index.js",
"scripts": {
"dev": "tsc -w",
"build": "tsc",
"start": "node .",
"build:start": "npm run build && npm run start"
Expand Down
48 changes: 41 additions & 7 deletions examples/disco/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
/* eslint-disable no-await-in-loop */
import { config } from "dotenv";
import { LighthouseAuth, LighthouseWebsocket } from "lighthouse.js";
import { LighthouseAuth, LighthouseWebsocket, LIGHTHOUSE_WIDTH, LIGHTHOUSE_HEIGHT } from "lighthouse.js";

config();

const user = process.env.LIGHTHOUSE_USER ?? "";
function getEnv(name: string): string {
const value = process.env[name];
if (!value) throw Error(`Environment variable ${name} is not defined!`);
return value;
}

const user = getEnv("LIGHTHOUSE_USER");

const auth: LighthouseAuth<typeof user> = {
USER: user,
TOKEN: process.env.LIGHTHOUSE_TOKEN ?? "",
TOKEN: getEnv("LIGHTHOUSE_TOKEN"),
};

async function sleep(time: number) {
Expand All @@ -17,20 +23,48 @@ async function sleep(time: number) {
});
}

/** Converts a color from the HSV space to RGB. */
function hsvToRgb(h: number, s: number, v: number): { r: number, g: number, b: number } {
// Source: Wikipedia
const hi = 6 * h;
const hif = Math.floor(hi);
const f = hi - hif;
const p = v * (1 - s);
const q = v * (1 - s * f);
const t = v * (1 - s * (1 - f));
switch (hif) {
case 0: return { r: v, g: t, b: p };
case 1: return { r: q, g: v, b: p };
case 2: return { r: p, g: v, b: t };
case 3: return { r: p, g: q, b: v };
case 4: return { r: t, g: p, b: v };
default: return { r: v, g: p, b: q };
}
}

(async () => {
const lh = new LighthouseWebsocket(auth);
await lh.open();
let i = 0;
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-loop-func
const data = new Array(28 * 14 * 3).fill(0).map((_, j) => (j % 3 === i ? 255 : 0));
const msg = await lh.send(data);
const data = new Array(LIGHTHOUSE_WIDTH * LIGHTHOUSE_HEIGHT)
.fill(0)
.flatMap((_, j) => {
const x = (j % LIGHTHOUSE_WIDTH) / LIGHTHOUSE_WIDTH;
const y = (j / LIGHTHOUSE_WIDTH) / LIGHTHOUSE_HEIGHT;
const hue = (x + y) / 2 * Math.sin(i * 4);
const saturation = 1;
const value = Math.cos(i * 4);
const { r, g, b } = hsvToRgb(hue, saturation, value);
return [r, g, b].map(x => Math.round(x * 255));
});
const msg = await lh.sendDisplay(new Uint8Array(data));

// eslint-disable-next-line no-console
console.log(msg);
i += 1;
i %= 3;
i++;
await sleep(1000 / 5);
}
})();
55 changes: 38 additions & 17 deletions src/Lighthouse/LighthouseWebsocket.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import { decode, encode } from "@msgpack/msgpack";
import { v4 as uuid } from "uuid";
import { WebSocket } from "ws";
import { LighthouseAuth, RequestPayload, ResponsePayload } from "./types";
import { LighthouseAuth, LighthousePath, LighthouseRequest, LighthouseEvent, LighthouseVerb } from "./types";

type ResponseHandler = (data: ResponsePayload) => void;
type LighthouseEventHandler<P> = (event: LighthouseEvent<P>) => void;

export class LighthouseWebsocket<U extends string> {
private static readonly serverAddress = "wss://lighthouse.uni-kiel.de/websocket";

private ws?: WebSocket;

private responseHandlers: Map<string, ResponseHandler>;
private responseHandlers: Map<string, LighthouseEventHandler<unknown>> = new Map();
private eventHandlers: LighthouseEventHandler<unknown>[] = [];

constructor(private readonly auth: LighthouseAuth<U>) {
this.responseHandlers = new Map<string, ResponseHandler>();
}
constructor(private readonly auth: LighthouseAuth<U>) {}

public async open(address = LighthouseWebsocket.serverAddress): Promise<number> {
this.ws = new WebSocket(address);

this.ws.on("message", (data) => {
const response: ResponsePayload = decode(new Uint8Array(data as Buffer)) as ResponsePayload;
const response = decode(new Uint8Array(data as Buffer)) as LighthouseEvent<unknown>;
const handler = this.responseHandlers.get(response.REID);
if (handler && typeof handler === "function") {
handler(response);
this.responseHandlers.delete(response.REID);
} else {
for (const handler of this.eventHandlers) {
handler(response);
}
}
});
return new Promise<number>((res) => {
Expand All @@ -34,28 +37,46 @@ export class LighthouseWebsocket<U extends string> {
});
}

public async send(payload: number[]): Promise<ResponsePayload> {
public async sendDisplay(rgbValues: Uint8Array): Promise<LighthouseEvent<unknown>> {
return await this.send("PUT", ["user", this.auth.USER, "model"], rgbValues);
}

public async requestStream(): Promise<LighthouseEvent<unknown>> {
return await this.send("STREAM", ["user", this.auth.USER, "model"], undefined);
}

private async send<P>(verb: LighthouseVerb, path: LighthousePath<U>, payload: P): Promise<LighthouseEvent<unknown>> {
const id = uuid();
const data: RequestPayload<U> = {
const request: LighthouseRequest<U, P> = {
AUTH: this.auth,
META: {},
PATH: ["user", this.auth.USER, "model"],
PAYL: new Uint8Array(payload),
PATH: path,
PAYL: payload,
REID: id,
VERB: "PUT",
VERB: verb,
};
if (this.ws?.readyState === WebSocket.OPEN) {
const prom = new Promise<ResponsePayload>((res) => {
this.registerResponseHandler(id, res);
const prom = new Promise<LighthouseEvent<unknown>>((resolve, reject) => {
this.registerResponseHandler(id, response => {
if (response.RNUM === 200) {
resolve(response);
} else {
reject(`${response.RNUM} ${response.RESPONSE}`);
}
});
});
this.ws?.send(encode(data));
this.ws?.send(encode(request));
return prom;
}
throw new Error("Websocket is currently not open!");
}

private registerResponseHandler(id: string, cb: ResponseHandler) {
this.responseHandlers.set(id, cb);
private registerResponseHandler<P>(id: string, cb: LighthouseEventHandler<P>): void {
this.responseHandlers.set(id, cb as LighthouseEventHandler<unknown>);
}

private registerEventHandler<P>(cb: LighthouseEventHandler<P>): void {
this.eventHandlers.push(cb as LighthouseEventHandler<unknown>);
}

public close(): void {
Expand Down
2 changes: 2 additions & 0 deletions src/Lighthouse/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const LIGHTHOUSE_WIDTH: number = 28;
export const LIGHTHOUSE_HEIGHT: number = 14;
15 changes: 8 additions & 7 deletions src/Lighthouse/types.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
type Verb = "POST" | "CREATE" | "MKDIR" | "DELETE" | "LIST" | "GET" | "PUT" | "STREAM" | "STOP" | "LINK" | "UNLINK";
export type LighthouseVerb = "POST" | "CREATE" | "MKDIR" | "DELETE" | "LIST" | "GET" | "PUT" | "STREAM" | "STOP" | "LINK" | "UNLINK";
export type LighthousePath<U extends string> = ["user", U, "model"];

export interface LighthouseAuth<U extends string> {
USER: U;
TOKEN: string;
}

export interface RequestPayload<U extends string> {
export interface LighthouseRequest<U extends string, P> {
REID: string;
AUTH: LighthouseAuth<U>;
VERB: Verb;
PATH: ["user", U, "model"];
VERB: LighthouseVerb;
PATH: LighthousePath<U>;
META: object;
PAYL: unknown;
PAYL: P;
}

export interface ResponsePayload {
export interface LighthouseEvent<P> {
REID: string;
RNUM: number;
RESPONSE: string;
META: object;
PAYL: unknown;
PAYL: P;
WARNINGS: string[];
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./Lighthouse/LighthouseWebsocket";
export * from "./Lighthouse/constants";
export * from "./Lighthouse/types";