diff --git a/apps/backend/src/zigbee/ws.ts b/apps/backend/src/zigbee/ws.ts index dc4654c..cb9bab5 100644 --- a/apps/backend/src/zigbee/ws.ts +++ b/apps/backend/src/zigbee/ws.ts @@ -1,4 +1,4 @@ -import type { JrpcRequest, JrpcResponse } from "@eva/jrpc" +import { type JrpcRequest, type JrpcResponse, newJrpcRequestId } from "@eva/jrpc" import { ALL_ZIGBEE_DEVICE_NAMES, type ZigbeeDeviceName, type ZigbeeDeviceState } from "@eva/zigbee" import type { WSContext } from "hono/ws" import type { DeviceMessageListener, ZigbeeController } from "./controller" @@ -8,15 +8,17 @@ export class WebSocketHandler { constructor(private readonly controller: ZigbeeController) {} - handleWebsocketOpen(event: Event, ws: WSContext) { + handleWebsocketOpen(_event: Event, ws: WSContext) { for (const device of ALL_ZIGBEE_DEVICE_NAMES) { const l: DeviceMessageListener = (msg) => { - const state = msg as ZigbeeDeviceState + const state = msg as ZigbeeDeviceState const request: JrpcRequest<"showDeviceState"> = { - id: crypto.randomUUID(), + id: newJrpcRequestId(), jsonrpc: "2.0", method: "showDeviceState", - params: { deviceName: device, state }, + params: { deviceName: device, state } as { + [K in ZigbeeDeviceName]: { deviceName: K; state: ZigbeeDeviceState } + }[ZigbeeDeviceName], } ws.send(JSON.stringify(request)) } diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx index 75cda88..c0f3ef3 100644 --- a/apps/dashboard/src/App.tsx +++ b/apps/dashboard/src/App.tsx @@ -1,4 +1,4 @@ -import type { JrpcRequest, JrpcResponse } from "@eva/jrpc" +import { type JrpcRequest, type JrpcResponse, newJrpcRequestId } from "@eva/jrpc" import { ZIGBEE_DEVICE, type ZigbeeDeviceName } from "@eva/zigbee" import { useQuery } from "@tanstack/react-query" import { useDrag } from "@use-gesture/react" @@ -77,8 +77,8 @@ function App() { return } - const request: JrpcRequest<"setDeviceState"> = { - id: crypto.randomUUID(), + const req: JrpcRequest<"setDeviceState"> = { + id: newJrpcRequestId(), jsonrpc: "2.0", method: "setDeviceState", params: { @@ -90,8 +90,7 @@ function App() { }, } - ws.send(JSON.stringify(request)) - console.log("Sent brightness change:", { deviceName, brightness }) + ws.send(JSON.stringify(req)) } return ( @@ -535,7 +534,7 @@ function SystemTile({ labels: Array.from({ length: 20 }, (_, index) => index), datasets: [ { - data: Array.from({ length: 20 }, (_, i) => null), + data: Array.from({ length: 20 }, (_, __) => null), fill: true, backgroundColor: fillGradient, borderColor: "#2dd4bf", diff --git a/bun.lock b/bun.lock index f65114f..e35286f 100644 --- a/bun.lock +++ b/bun.lock @@ -54,6 +54,7 @@ "name": "@eva/jrpc", "dependencies": { "@eva/zigbee": "workspace:*", + "nanoid": "^5.1.6", }, "devDependencies": { "@types/bun": "latest", @@ -473,7 +474,7 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], "node-releases": ["node-releases@2.0.26", "", {}, "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA=="], @@ -637,6 +638,8 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/packages/jrpc/index.ts b/packages/jrpc/index.ts index a930d53..c58e98a 100644 --- a/packages/jrpc/index.ts +++ b/packages/jrpc/index.ts @@ -1,49 +1,40 @@ import type { ZigbeeDeviceName, ZigbeeDeviceStates } from "@eva/zigbee" +import { nanoid } from "nanoid" + +export type JrpcRequestId = string & { __brand: "JrpcRequestId" } export type JrpcSchema = { - subscribeToDevice: { - Params: { - deviceName: ZigbeeDeviceName - } - Response: true - } - setDeviceState: { - Params: { - deviceName: ZigbeeDeviceName - state: unknown - } - Response: true - } - showDeviceState: { - Params: { - [key in ZigbeeDeviceName]: { - deviceName: key - state: ZigbeeDeviceStates[key] - } - }[ZigbeeDeviceName] - Response: true - } + subscribeToDevice(p: { deviceName: ZigbeeDeviceName }): true + unsubscribeFromDevice(p: { deviceName: ZigbeeDeviceName }): true + setDeviceState(p: { deviceName: ZigbeeDeviceName; state: unknown }): true + showDeviceState( + p: { [K in ZigbeeDeviceName]: { deviceName: K; state: ZigbeeDeviceStates[K] } }[DeviceName], + ): ZigbeeDeviceStates[ZigbeeDeviceName] } export type JrpcRequest = { [M in keyof JrpcSchema]: { - id: string + id: JrpcRequestId jsonrpc: "2.0" method: M - params: JrpcSchema[M]["Params"] + params: Parameters[0] } }[Method] export type JrpcResponse = { [M in keyof JrpcSchema]: | { - id: string + id: JrpcRequestId jsonrpc: "2.0" - result: JrpcSchema[M]["Response"] + result: ReturnType } | { - id: string + id: JrpcRequestId jsonrpc: "2.0" error: string } }[Method] + +export function newJrpcRequestId(): JrpcRequestId { + return nanoid() +} diff --git a/packages/jrpc/package.json b/packages/jrpc/package.json index 7ffbe77..23e7d6f 100644 --- a/packages/jrpc/package.json +++ b/packages/jrpc/package.json @@ -3,7 +3,8 @@ "module": "index.ts", "type": "module", "dependencies": { - "@eva/zigbee": "workspace:*" + "@eva/zigbee": "workspace:*", + "nanoid": "^5.1.6" }, "devDependencies": { "@types/bun": "latest" diff --git a/packages/zigbee/index.ts b/packages/zigbee/index.ts index a2e4d81..64f2ca0 100644 --- a/packages/zigbee/index.ts +++ b/packages/zigbee/index.ts @@ -4,7 +4,6 @@ export const ZIGBEE_DEVICE = { deskLamp: "desk_lamp", livingRoomFloorLamp: "living_room_floor_lamp", } as const -export type ZigbeeDeviceName = (typeof ZIGBEE_DEVICE)[keyof typeof ZIGBEE_DEVICE] export type ZigbeeDeviceStates = { [ZIGBEE_DEVICE.deskLamp]: { @@ -26,6 +25,8 @@ export type ZigbeeDeviceStates = { } } -export const ALL_ZIGBEE_DEVICE_NAMES: ZigbeeDeviceName[] = Object.values(ZIGBEE_DEVICE) +export type ZigbeeDeviceName = keyof ZigbeeDeviceStates -export type ZigbeeDeviceState = ZigbeeDeviceStates[keyof ZigbeeDeviceStates] +export type ZigbeeDeviceState = ZigbeeDeviceStates[DeviceName] + +export const ALL_ZIGBEE_DEVICE_NAMES: ZigbeeDeviceName[] = Object.values(ZIGBEE_DEVICE)