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
27 changes: 20 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 @@ -20,15 +26,22 @@ async function sleep(time: number) {
(async () => {
const lh = new LighthouseWebsocket(auth);
await lh.open();

lh.addKeyListener(e => {
console.log(`Got key input: ${JSON.stringify(e)}`);
});

lh.addControllerListener(e => {
console.log(`Got controller input: ${JSON.stringify(e)}`);
});

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 Uint8Array(LIGHTHOUSE_WIDTH * LIGHTHOUSE_HEIGHT * 3).map((_, j) => (j % 3 === i ? 255 : 0));
await lh.sendDisplay(data);

// eslint-disable-next-line no-console
console.log(msg);
i += 1;
i %= 3;
await sleep(1000 / 5);
Expand Down
64 changes: 0 additions & 64 deletions src/Lighthouse/LighthouseWebsocket.ts

This file was deleted.

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;
38 changes: 38 additions & 0 deletions src/Lighthouse/protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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 LighthouseRequest<U extends string, P> {
REID: string;
AUTH: LighthouseAuth<U>;
VERB: LighthouseVerb;
PATH: LighthousePath<U>;
META: object;
PAYL: P;
}

export interface LighthouseEvent<P> {
REID: string;
RNUM: number;
RESPONSE: string;
META: object;
PAYL: P;
WARNINGS: string[];
}

export interface InputEvent {
src: number;
dwn: number;
}

export interface KeyEvent extends InputEvent {
key: number;
}

export interface ControllerEvent extends InputEvent {
btn: number;
}
24 changes: 0 additions & 24 deletions src/Lighthouse/types.ts

This file was deleted.

113 changes: 113 additions & 0 deletions src/Lighthouse/websocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { decode, encode } from "@msgpack/msgpack";
import { v4 as uuid } from "uuid";
import { WebSocket } from "ws";
import { LighthouseAuth, LighthousePath, LighthouseRequest, LighthouseEvent, LighthouseVerb, KeyEvent, ControllerEvent } from "./protocol";

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 streamRequested: boolean = false;

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

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 = 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) => {
this.ws?.once("open", (code: number) => {
res(code);
});
});
}

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>> {
this.streamRequested = true;
return await this.send("STREAM", ["user", this.auth.USER, "model"], undefined);
}

public addKeyListener(cb: (event: KeyEvent) => void): void {
if (!this.streamRequested) {
this.requestStream();
}
this.registerEventHandler(raw => {
const event = raw as LighthouseEvent<KeyEvent>;
const input = event.PAYL;
if (input.src && input.key && input.dwn) {
cb(input);
}
});
}

public addControllerListener(cb: (event: ControllerEvent) => void): void {
if (!this.streamRequested) {
this.requestStream();
}
this.registerEventHandler(raw => {
const event = raw as LighthouseEvent<ControllerEvent>;
const input = event.PAYL;
if (input.src && input.btn && input.dwn) {
cb(input);
}
});
}

private async send<P>(verb: LighthouseVerb, path: LighthousePath<U>, payload: P): Promise<LighthouseEvent<unknown>> {
const id = uuid();
const request: LighthouseRequest<U, P> = {
AUTH: this.auth,
META: {},
PATH: path,
PAYL: payload,
REID: id,
VERB: verb,
};
if (this.ws?.readyState === WebSocket.OPEN) {
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(request));
return prom;
}
throw new Error("Websocket is currently not open!");
}

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 {
this.ws?.close();
}
}
5 changes: 3 additions & 2 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/types";
export * from "./lighthouse/websocket";
export * from "./lighthouse/constants";
export * from "./lighthouse/protocol";