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

Add support for key/controller input #36

Open
wants to merge 12 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
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";