Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";