Skip to content

Commit 5ac5480

Browse files
committed
POC minimal working server, which successfully returns devices list
1 parent 0a9f986 commit 5ac5480

1 file changed

Lines changed: 11 additions & 354 deletions

File tree

http-server/server.js

Lines changed: 11 additions & 354 deletions
Original file line numberDiff line numberDiff line change
@@ -1,367 +1,24 @@
1-
const net = require("net");
21
const express = require("express");
32
const morgan = require("morgan");
3+
require("dotenv").config();
44
require("dotenv").config({ path: __dirname + "/.env" });
55

6+
const { Client } = require("openrgb-sdk");
7+
68
const app = express();
79
app.use(express.json());
8-
app.use(morgan("combined"));
9-
10-
const OPENRGB_HOST = process.env.OPENRGB_HOST || "doganm95-openrgb-tcp-server";
11-
const OPENRGB_PORT = Number(process.env.OPENRGB_PORT) || 6742;
12-
13-
const PacketType = { HELLO: 0, REQUEST: 1, RESPONSE: 2, NOTIFY: 3, ERROR: 4 };
14-
15-
const Command = {
16-
GET_CONTROLLER_COUNT: 1,
17-
GET_CONTROLLER_DATA: 2,
18-
SET_LED_COLOR: 4,
19-
SET_LED_COLOR_ALL: 5,
20-
SET_MODE: 7,
21-
SET_BRIGHTNESS: 8,
22-
SET_LED_BRIGHTNESS: 9,
23-
SET_SPEED: 10,
24-
SET_LED_SPEED: 11,
25-
GET_PROFILE: 12,
26-
SET_PROFILE: 13,
27-
SAVE_PROFILE: 14,
28-
GET_DEVICE_COUNT: 15,
29-
GET_DEVICE_DATA: 16,
30-
};
31-
32-
function writeInt32LE(value) {
33-
const buf = Buffer.alloc(4);
34-
buf.writeInt32LE(value, 0);
35-
return buf;
36-
}
37-
38-
function writeUInt16LE(value) {
39-
const buf = Buffer.alloc(2);
40-
buf.writeUInt16LE(value, 0);
41-
return buf;
42-
}
43-
44-
function writeString(str) {
45-
const strBuf = Buffer.from(str, "utf8");
46-
const lenBuf = writeInt32LE(strBuf.length);
47-
return Buffer.concat([lenBuf, strBuf]);
48-
}
49-
50-
class OpenRGBClient {
51-
constructor(host, port) {
52-
this.host = host;
53-
this.port = port;
54-
this.socket = null;
55-
this.nextRequestId = 1;
56-
this.callbacks = new Map();
57-
this.buffer = Buffer.alloc(0);
58-
}
59-
60-
connect() {
61-
return new Promise((resolve, reject) => {
62-
this.socket = net.createConnection(this.port, this.host, () => {
63-
this.sendHello().then(resolve).catch(reject);
64-
});
65-
66-
this.socket.on("data", (data) => this.onData(data));
67-
this.socket.on("error", (err) => {
68-
console.error("Socket error:", err);
69-
this.rejectAll(err);
70-
});
71-
this.socket.on("close", () => {
72-
this.rejectAll(new Error("Connection closed"));
73-
});
74-
});
75-
}
76-
77-
rejectAll(err) {
78-
for (const cb of this.callbacks.values()) {
79-
cb.reject(err);
80-
}
81-
this.callbacks.clear();
82-
}
83-
84-
onData(data) {
85-
this.buffer = Buffer.concat([this.buffer, data]);
86-
while (this.buffer.length >= 8) {
87-
const packetLen = this.buffer.readInt32LE(0);
88-
if (this.buffer.length < packetLen + 4) break;
89-
90-
const packetBuf = this.buffer.slice(4, 4 + packetLen);
91-
this.buffer = this.buffer.slice(4 + packetLen);
92-
93-
this.handlePacket(packetBuf);
94-
}
95-
}
96-
97-
handlePacket(buffer) {
98-
const packetType = buffer.readInt32LE(0);
99-
const requestId = buffer.readInt32LE(4);
100-
101-
if (packetType === PacketType.RESPONSE) {
102-
const cb = this.callbacks.get(requestId);
103-
if (cb) {
104-
this.callbacks.delete(requestId);
105-
cb.resolve(buffer.slice(8));
106-
}
107-
}
108-
}
109-
110-
sendPacket(packetType, requestId, payload) {
111-
const length = 8 + (payload ? payload.length : 0);
112-
const header = Buffer.alloc(8);
113-
header.writeInt32LE(length - 4, 0);
114-
header.writeInt32LE(packetType, 4);
115-
116-
const requestBuf = Buffer.alloc(4);
117-
requestBuf.writeInt32LE(requestId, 0);
118-
119-
const packet = payload ? Buffer.concat([header, requestBuf, payload]) : Buffer.concat([header, requestBuf]);
120-
this.socket.write(packet);
121-
}
10+
app.use(morgan("dev"));
12211

123-
sendRequest(commandId, payload) {
124-
const requestId = this.nextRequestId++;
125-
return new Promise((resolve, reject) => {
126-
this.callbacks.set(requestId, { resolve, reject });
127-
128-
const commandBuf = writeInt32LE(commandId);
129-
const fullPayload = payload ? Buffer.concat([commandBuf, payload]) : commandBuf;
130-
131-
this.sendPacket(PacketType.REQUEST, requestId, fullPayload);
132-
133-
setTimeout(() => {
134-
if (this.callbacks.has(requestId)) {
135-
this.callbacks.delete(requestId);
136-
reject(new Error("OpenRGB request timeout"));
137-
}
138-
}, 5000);
139-
});
140-
}
141-
142-
sendHello() {
143-
const clientName = "OpenRGB-REST-API";
144-
const clientNameBuf = writeString(clientName);
145-
const versionBuf = writeInt32LE(1);
146-
const payload = Buffer.concat([clientNameBuf, versionBuf]);
147-
return new Promise((resolve, reject) => {
148-
this.socket.once("data", (data) => {
149-
resolve();
150-
});
151-
this.sendPacket(PacketType.HELLO, 0, payload);
152-
});
153-
}
154-
155-
// --- SDK commands ---
156-
157-
async getControllerCount() {
158-
const resp = await this.sendRequest(Command.GET_CONTROLLER_COUNT);
159-
return resp.readInt32LE(0);
160-
}
161-
162-
async getControllerData(controllerIndex) {
163-
const payload = writeInt32LE(controllerIndex);
164-
const resp = await this.sendRequest(Command.GET_CONTROLLER_DATA, payload);
165-
return this.parseControllerData(resp);
166-
}
167-
168-
parseControllerData(buffer) {
169-
// Parse according to OpenRGB SDK documentation:
170-
// https://gitlab.com/CalcProgrammer1/OpenRGB/-/wikis/OpenRGB-SDK-Protocol
171-
// Example parsing for device name, LEDs count, modes, zones etc.
172-
173-
let offset = 0;
174-
175-
function readInt32() {
176-
const val = buffer.readInt32LE(offset);
177-
offset += 4;
178-
return val;
179-
}
180-
181-
function readString() {
182-
const len = readInt32();
183-
const str = buffer.toString("utf8", offset, offset + len);
184-
offset += len;
185-
return str;
186-
}
187-
188-
const name = readString();
189-
const type = readInt32();
190-
const deviceType = this.deviceTypeToString(type);
191-
192-
const activeMode = readInt32();
193-
const ledCount = readInt32();
194-
195-
const leds = [];
196-
for (let i = 0; i < ledCount; i++) {
197-
const ledName = readString();
198-
leds.push(ledName);
199-
}
200-
201-
const modeCount = readInt32();
202-
const modes = [];
203-
for (let i = 0; i < modeCount; i++) {
204-
const modeName = readString();
205-
modes.push(modeName);
206-
}
207-
208-
const zoneCount = readInt32();
209-
const zones = [];
210-
for (let i = 0; i < zoneCount; i++) {
211-
const zoneName = readString();
212-
zones.push(zoneName);
213-
}
214-
215-
return { name, deviceType, activeMode, leds, modes, zones };
216-
}
217-
218-
deviceTypeToString(type) {
219-
const types = ["Unknown", "Motherboard", "DRAM", "GPU", "Peripheral"];
220-
return types[type] || "Unknown";
221-
}
222-
223-
async setLedColor(controllerIndex, ledIndex, color) {
224-
const payload = Buffer.alloc(4 + 4 + 4);
225-
payload.writeInt32LE(controllerIndex, 0);
226-
payload.writeInt32LE(ledIndex, 4);
227-
payload.writeUInt8(color.red, 8);
228-
payload.writeUInt8(color.green, 9);
229-
payload.writeUInt8(color.blue, 10);
230-
// white channel ignored here
231-
232-
await this.sendRequest(Command.SET_LED_COLOR, payload);
233-
}
234-
235-
async setLedColorAll(controllerIndex, color) {
236-
const payload = Buffer.alloc(4 + 4 + 4);
237-
payload.writeInt32LE(controllerIndex, 0);
238-
payload.writeInt32LE(0, 4); // ignored
239-
payload.writeUInt8(color.red, 8);
240-
payload.writeUInt8(color.green, 9);
241-
payload.writeUInt8(color.blue, 10);
242-
243-
await this.sendRequest(Command.SET_LED_COLOR_ALL, payload);
244-
}
245-
246-
async setMode(controllerIndex, modeIndex) {
247-
const payload = Buffer.alloc(8);
248-
payload.writeInt32LE(controllerIndex, 0);
249-
payload.writeInt32LE(modeIndex, 4);
250-
251-
await this.sendRequest(Command.SET_MODE, payload);
252-
}
253-
254-
async setBrightness(controllerIndex, brightness) {
255-
const payload = Buffer.alloc(8);
256-
payload.writeInt32LE(controllerIndex, 0);
257-
payload.writeInt32LE(brightness, 4);
258-
259-
await this.sendRequest(Command.SET_BRIGHTNESS, payload);
260-
}
261-
262-
async setSpeed(controllerIndex, speed) {
263-
const payload = Buffer.alloc(8);
264-
payload.writeInt32LE(controllerIndex, 0);
265-
payload.writeInt32LE(speed, 4);
266-
267-
await this.sendRequest(Command.SET_SPEED, payload);
268-
}
269-
270-
// Add more methods like profile save/load, etc., as needed
271-
}
272-
273-
// REST API endpoints
274-
275-
const client = new OpenRGBClient(OPENRGB_HOST, OPENRGB_PORT);
12+
const client = new Client("OpenRGB-REST-API", Number(process.env.OPENRGB_PORT), process.env.OPENRGB_HOST);
27613

27714
app.get("/devices", async (req, res) => {
278-
try {
279-
const count = await client.getControllerCount();
280-
const devices = [];
281-
for (let i = 0; i < count; i++) {
282-
const data = await client.getControllerData(i);
283-
devices.push({ id: i, ...data });
284-
}
285-
res.json(devices);
286-
} catch (e) {
287-
res.status(500).json({ error: e.message });
288-
}
289-
});
290-
291-
app.get("/device/:id", async (req, res) => {
292-
const id = Number(req.params.id);
293-
try {
294-
const device = await client.getControllerData(id);
295-
res.json(device);
296-
} catch (e) {
297-
res.status(500).json({ error: e.message });
298-
}
299-
});
300-
301-
app.post("/device/:id/color", async (req, res) => {
302-
const id = Number(req.params.id);
303-
const { red, green, blue } = req.body;
304-
if ([red, green, blue].some((v) => v === undefined || v < 0 || v > 255)) {
305-
return res.status(400).json({ error: "Invalid color values 0-255 required" });
306-
}
307-
try {
308-
await client.setLedColorAll(id, { red, green, blue });
309-
res.json({ status: "success", device: id, color: { red, green, blue } });
310-
} catch (e) {
311-
res.status(500).json({ error: e.message });
312-
}
313-
});
314-
315-
app.post("/device/:id/mode", async (req, res) => {
316-
const id = Number(req.params.id);
317-
const { modeIndex } = req.body;
318-
if (typeof modeIndex !== "number" || modeIndex < 0) {
319-
return res.status(400).json({ error: "modeIndex (number) required" });
320-
}
321-
try {
322-
await client.setMode(id, modeIndex);
323-
res.json({ status: "success", device: id, modeIndex });
324-
} catch (e) {
325-
res.status(500).json({ error: e.message });
326-
}
327-
});
328-
329-
app.post("/device/:id/brightness", async (req, res) => {
330-
const id = Number(req.params.id);
331-
const { brightness } = req.body;
332-
if (typeof brightness !== "number" || brightness < 0 || brightness > 100) {
333-
return res.status(400).json({ error: "brightness must be 0-100" });
334-
}
335-
try {
336-
await client.setBrightness(id, brightness);
337-
res.json({ status: "success", device: id, brightness });
338-
} catch (e) {
339-
res.status(500).json({ error: e.message });
340-
}
341-
});
342-
343-
app.post("/device/:id/speed", async (req, res) => {
344-
const id = Number(req.params.id);
345-
const { speed } = req.body;
346-
if (typeof speed !== "number" || speed < 0 || speed > 100) {
347-
return res.status(400).json({ error: "speed must be 0-100" });
348-
}
349-
try {
350-
await client.setSpeed(id, speed);
351-
res.json({ status: "success", device: id, speed });
352-
} catch (e) {
353-
res.status(500).json({ error: e.message });
354-
}
355-
});
356-
357-
// TODO: Add endpoints for profiles, zones, I2C tools, save/load profiles, etc.
358-
359-
app.listen(3000, async () => {
36015
try {
36116
await client.connect();
362-
console.log("OpenRGB REST API server running at http://localhost:3000");
363-
} catch (e) {
364-
console.error("Failed to connect to OpenRGB server:", e);
365-
process.exit(1);
17+
const devices = await client.getAllControllerData();
18+
res.status(200).json(devices);
19+
} catch (err) {
20+
res.status(500).json({ error: err.message || "Failed to fetch devices" });
36621
}
36722
});
23+
24+
app.listen(3000, () => console.log("OpenRGB REST API running at http://localhost:3000"));

0 commit comments

Comments
 (0)