diff --git a/apps/backend/package.json b/apps/backend/package.json index abda10b..e519fec 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -1,18 +1,21 @@ { - "name": "backend", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "bun run --watch src/index.ts", - "build": "bun build src/index.ts --outdir dist --target bun", - "start": "bun run dist/index.js", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "hono": "^4.6.14" - }, - "devDependencies": { - "@types/bun": "latest", - "typescript": "^5.6.3" - } + "name": "@eva/backend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "bun run --watch src/index.ts", + "build": "bun build src/index.ts --outdir dist --target bun", + "start": "bun run dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "hono": "^4.6.14", + "mqtt": "^5.14.1", + "@eva/jrpc": "workspace:*", + "@eva/zigbee": "workspace:*" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.6.3" + } } diff --git a/apps/backend/src/env.d.ts b/apps/backend/src/env.d.ts index 73750d5..f53998a 100644 --- a/apps/backend/src/env.d.ts +++ b/apps/backend/src/env.d.ts @@ -8,5 +8,9 @@ declare namespace NodeJS { BESZEL_HOST?: string BESZEL_EMAIL?: string BESZEL_PASSWORD?: string + MQTT_HOST: string + MQTT_PORT: number + MQTT_USERNAME: string + MQTT_PASSWORD: string } } diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index 1ade2bc..4ab9d7d 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -1,10 +1,19 @@ import { Hono } from "hono" +import { serveStatic, websocket } from "hono/bun" import { cors } from "hono/cors" import { logger } from "hono/logger" -import { serveStatic } from "hono/bun" -import weather from "./weather" -import tfl from "./tfl" import beszel from "./beszel" +import { createMqttClient } from "./mqtt" +import tfl from "./tfl" +import weather from "./weather" +import zigbee from "./zigbee/routes" + +const mqtt = await createMqttClient({ + host: process.env.MQTT_HOST, + port: process.env.MQTT_PORT, + username: process.env.MQTT_USERNAME, + password: process.env.MQTT_PASSWORD, +}) const app = new Hono() @@ -24,6 +33,9 @@ app.route("/api/tfl", tfl) // Mount Beszel routes app.route("/api/beszel", beszel) +// Mount Zigbee routes +app.route("/api/zigbee", zigbee(mqtt)) + // Serve static files from dashboard build app.use("/*", serveStatic({ root: "../dashboard/dist" })) @@ -33,4 +45,5 @@ app.get("*", serveStatic({ path: "../dashboard/dist/index.html" })) export default { port: 8000, fetch: app.fetch, + websocket, } diff --git a/apps/backend/src/jrpc.ts b/apps/backend/src/jrpc.ts new file mode 100644 index 0000000..981d5f9 --- /dev/null +++ b/apps/backend/src/jrpc.ts @@ -0,0 +1,6 @@ +export type JrpcRequest = { + jsonrpc: "2.0" + method: string + params: unknown + id: number +} diff --git a/apps/backend/src/mqtt.ts b/apps/backend/src/mqtt.ts new file mode 100644 index 0000000..b6a422e --- /dev/null +++ b/apps/backend/src/mqtt.ts @@ -0,0 +1,15 @@ +import mqtt from "mqtt" + +export async function createMqttClient({ + host, + port, + username, + password, +}: { host: string; port: number; username: string; password: string }) { + return await mqtt.connectAsync({ + host, + port, + username, + password, + }) +} diff --git a/apps/backend/src/zigbee.ts b/apps/backend/src/zigbee.ts new file mode 100644 index 0000000..535b23d --- /dev/null +++ b/apps/backend/src/zigbee.ts @@ -0,0 +1 @@ +const BASE_TOPIC = "nexus" diff --git a/apps/backend/src/zigbee/controller.ts b/apps/backend/src/zigbee/controller.ts new file mode 100644 index 0000000..03dd9a7 --- /dev/null +++ b/apps/backend/src/zigbee/controller.ts @@ -0,0 +1,50 @@ +import type { ZigbeeDeviceName } from "@eva/zigbee" +import type { MqttClient } from "mqtt" + +export type DeviceMessageListener = (msg: unknown) => void + +export class ZigbeeController { + private deviceListeners: Map = new Map() + + constructor( + private readonly baseTopic: string, + private readonly mqtt: MqttClient, + ) { + this.mqtt.on("message", (topic, message) => { + const [baseTopic, deviceName] = topic.split("/") + if (baseTopic !== this.baseTopic) { + return + } + const listeners = this.deviceListeners.get(deviceName) + if (listeners) { + for (const listener of listeners) { + listener(JSON.parse(message.toString())) + } + } + }) + } + + async subscribeToDevice(deviceName: ZigbeeDeviceName, listener: DeviceMessageListener): Promise { + await this.mqtt.publishAsync(`${this.baseTopic}/${deviceName}/get`, JSON.stringify({ state: {} })) + await this.mqtt.subscribeAsync(`${this.baseTopic}/${deviceName}`) + if (!this.deviceListeners.has(deviceName)) { + this.deviceListeners.set(deviceName, []) + } + this.deviceListeners.get(deviceName)?.push(listener) + } + + async unsubscribeFromDevice(deviceName: ZigbeeDeviceName, listener: DeviceMessageListener): Promise { + await this.mqtt.unsubscribeAsync(`${this.baseTopic}/${deviceName}`) + const listeners = this.deviceListeners.get(deviceName) + if (listeners) { + listeners.splice(listeners.indexOf(listener), 1) + if (listeners.length === 0) { + this.deviceListeners.delete(deviceName) + } + } + } + + async setDeviceState(deviceName: ZigbeeDeviceName, state: unknown): Promise { + await this.mqtt.publishAsync(`${this.baseTopic}/${deviceName}/set`, JSON.stringify(state)) + } +} diff --git a/apps/backend/src/zigbee/middleware.ts b/apps/backend/src/zigbee/middleware.ts new file mode 100644 index 0000000..056d9bd --- /dev/null +++ b/apps/backend/src/zigbee/middleware.ts @@ -0,0 +1,18 @@ +import { ZIGBEE_BASE_TOPIC } from "@eva/zigbee" +import { createMiddleware } from "hono/factory" +import type { MqttClient } from "mqtt" +import { ZigbeeController } from "./controller" + +export function zigbeeController(mqtt: MqttClient) { + const controller = new ZigbeeController(ZIGBEE_BASE_TOPIC, mqtt) + return createMiddleware((c, next) => { + c.set("zigbeeController", controller) + return next() + }) +} + +export type ZigbeeContext = { + Variables: { + zigbeeController: ZigbeeController + } +} diff --git a/apps/backend/src/zigbee/routes.ts b/apps/backend/src/zigbee/routes.ts new file mode 100644 index 0000000..a3bd468 --- /dev/null +++ b/apps/backend/src/zigbee/routes.ts @@ -0,0 +1,36 @@ +import { Hono } from "hono" +import { upgradeWebSocket } from "hono/bun" +import type { WSContext } from "hono/ws" +import type { MqttClient } from "mqtt" +import type { ZigbeeController } from "./controller" +import { type ZigbeeContext, zigbeeController } from "./middleware" +import { WebSocketHandler } from "./ws" + +export function zigbee(mqtt: MqttClient) { + const h = new Hono() + + h.use("*", zigbeeController(mqtt)) + + h.get( + "/", + upgradeWebSocket((c) => { + const controller = c.get("zigbeeController") as ZigbeeController + const wsHandler = new WebSocketHandler(controller) + return { + onOpen: (event, ws) => { + wsHandler.handleWebsocketOpen(event, ws) + }, + onMessage: (event, ws) => { + wsHandler.handleWebsocketMessage(event, ws) + }, + onClose: (event, ws) => { + wsHandler.handleWebsocketClose(ws) + }, + } + }), + ) + + return h +} + +export default zigbee diff --git a/apps/backend/src/zigbee/ws.ts b/apps/backend/src/zigbee/ws.ts new file mode 100644 index 0000000..dc4654c --- /dev/null +++ b/apps/backend/src/zigbee/ws.ts @@ -0,0 +1,54 @@ +import type { JrpcRequest, JrpcResponse } 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" + +export class WebSocketHandler { + private deviceListeners: Map = new Map() + + constructor(private readonly controller: ZigbeeController) {} + + handleWebsocketOpen(event: Event, ws: WSContext) { + for (const device of ALL_ZIGBEE_DEVICE_NAMES) { + const l: DeviceMessageListener = (msg) => { + const state = msg as ZigbeeDeviceState + const request: JrpcRequest<"showDeviceState"> = { + id: crypto.randomUUID(), + jsonrpc: "2.0", + method: "showDeviceState", + params: { deviceName: device, state }, + } + ws.send(JSON.stringify(request)) + } + this.controller.subscribeToDevice(device, l) + this.deviceListeners.set(device, l) + } + } + + async handleWebsocketMessage(event: MessageEvent, ws: WSContext) { + const message = JSON.parse(event.data) as JrpcRequest | JrpcResponse + if ("method" in message) { + await this.handleRequest(message, ws) + } + } + + handleWebsocketClose(_ws: WSContext) { + for (const [device, listener] of this.deviceListeners.entries()) { + this.controller.unsubscribeFromDevice(device, listener) + } + } + + private async handleRequest(message: JrpcRequest, ws: WSContext) { + switch (message.method) { + case "setDeviceState": { + await this.controller.setDeviceState(message.params.deviceName, message.params.state) + const response: JrpcResponse<"setDeviceState"> = { + id: message.id, + jsonrpc: "2.0", + result: true, + } + ws.send(JSON.stringify(response)) + } + } + } +} diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example index e7da9eb..3ca3c63 100644 --- a/apps/dashboard/.env.example +++ b/apps/dashboard/.env.example @@ -1,3 +1,3 @@ -VITE_API_URL=http://localhost:3000 +VITE_API_HOST=localhost:3000 VITE_DEFAULT_LATITUDE=37.7749 VITE_DEFAULT_LONGITUDE=-122.4194 diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 1c087e1..9665495 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -1,29 +1,32 @@ { - "name": "dashboard", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@tanstack/react-query": "^5.62.7", - "chart.js": "^4.5.1", - "jotai": "^2.10.3", - "lucide-react": "^0.546.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", - "typescript": "^5.6.3", - "vite": "^6.0.1" - } + "name": "@eva/dashboard", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@eva/jrpc": "workspace:*", + "@eva/zigbee": "workspace:*", + "@tanstack/react-query": "^5.62.7", + "@use-gesture/react": "^10.3.1", + "chart.js": "^4.5.1", + "jotai": "^2.10.3", + "lucide-react": "^0.546.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^6.0.1" + } } diff --git a/apps/dashboard/src/App.tsx b/apps/dashboard/src/App.tsx index 6a51fbb..a6c0811 100644 --- a/apps/dashboard/src/App.tsx +++ b/apps/dashboard/src/App.tsx @@ -1,5 +1,9 @@ +import type { JrpcRequest, JrpcResponse } from "@eva/jrpc" +import { ZIGBEE_DEVICE, type ZigbeeDeviceName } from "@eva/zigbee" import { useQuery } from "@tanstack/react-query" +import { useDrag } from "@use-gesture/react" import Chart from "chart.js/auto" +import { atom, useAtomValue, useSetAtom, useStore } from "jotai" import { useEffect, useLayoutEffect, useRef, useState } from "react" import { beszelSystemsQuery } from "./beszel" import cn from "./components/lib/cn" @@ -13,16 +17,84 @@ import { weatherDescriptionQuery, } from "./weather" +const brightnessAtoms = atom({ + [ZIGBEE_DEVICE.deskLamp]: atom(0), + [ZIGBEE_DEVICE.livingRoomFloorLamp]: atom(0), +}) + +const intermediateBrightnessAtoms = atom({ + [ZIGBEE_DEVICE.deskLamp]: atom(-1), + [ZIGBEE_DEVICE.livingRoomFloorLamp]: atom(-1), +}) + function App() { + const websocket = useRef(new WebSocket(`ws://${import.meta.env.VITE_API_HOST}/api/zigbee`)) + + const store = useStore() + + useEffect(() => { + websocket.current.onmessage = (event) => { + const data = JSON.parse(event.data) as JrpcRequest | JrpcResponse + if ("method" in data) { + switch (data.method) { + case "showDeviceState": { + const { deviceName, state } = data.params + const brightnessAtom = store.get(brightnessAtoms)[deviceName] + store.set(brightnessAtom, Math.round((state.brightness / 254) * 100)) + } + } + } + } + return () => { + if (websocket.current.readyState === WebSocket.OPEN) { + websocket.current.close() + } + } + }, [store]) + + function setBrightness(deviceName: ZigbeeDeviceName, brightness: number) { + const request: JrpcRequest<"setDeviceState"> = { + id: crypto.randomUUID(), + jsonrpc: "2.0", + method: "setDeviceState", + params: { + deviceName, + state: + brightness === 0 + ? { state: "OFF", brightness: 0 } + : { state: "ON", brightness: Math.round((brightness / 100) * 254) }, + }, + } + websocket.current.send(JSON.stringify(request)) + } + return ( -
+
+ + + - - + + { + setBrightness(ZIGBEE_DEVICE.livingRoomFloorLamp, brightness) + }} + /> + { + setBrightness(ZIGBEE_DEVICE.deskLamp, brightness) + }} + /> + +
) @@ -520,4 +592,184 @@ function SystemTile({ ) } +function LightControlTile({ + deviceName, + className, + onRequestBrightnessChange, +}: { deviceName: ZigbeeDeviceName; className?: string; onRequestBrightnessChange: (brightness: number) => void }) { + const BAR_COUNT = 44 + + const currentBrightness = useAtomValue(useAtomValue(brightnessAtoms)[deviceName]) + const initialHighlightIndexStart = Math.floor((1 - currentBrightness / 100) * BAR_COUNT) + const touchContainerRef = useRef(null) + const barRefs = useRef<(HTMLDivElement | null)[]>(Array.from({ length: BAR_COUNT }, () => null)) + const setIntermediateBrightness = useSetAtom(useAtomValue(intermediateBrightnessAtoms)[deviceName]) + const store = useStore() + + const bind = useDrag(({ xy: [x], first, last }) => { + if (!touchContainerRef.current) return + + if (!first) { + touchContainerRef.current.dataset.active = "true" + } + + if (last) { + delete touchContainerRef.current.dataset.active + for (let i = 0; i < BAR_COUNT; i++) { + const bar = barRefs.current[i] + if (!bar) continue + + if (bar.dataset.touched === "true") { + bar.dataset.thumb = "true" + } + + bar.dataset.touched = "false" + delete bar.dataset.touchProximity + } + + const intermediateBrightness = store.get(store.get(intermediateBrightnessAtoms)[deviceName]) + if (intermediateBrightness !== -1) { + onRequestBrightnessChange(intermediateBrightness) + setIntermediateBrightness(-1) + } + } else { + let touchedIndex = -1 + for (let i = 0; i < BAR_COUNT; i++) { + const bar = barRefs.current[i] + if (!bar) continue + + const barRect = bar.getBoundingClientRect() + + delete bar.dataset.thumb + + if (x > barRect.left - 2 && x < barRect.right + 2) { + touchedIndex = i + + bar.dataset.touched = "true" + bar.dataset.highlighted = "true" + delete bar.dataset.touchProximity + + const brightness = 1 - i / BAR_COUNT + setIntermediateBrightness(Math.round(brightness * 100)) + + if (barRefs.current[i - 1]) { + barRefs.current[i - 1]!.dataset.touchProximity = "close" + } + if (barRefs.current[i - 2]) { + barRefs.current[i - 2]!.dataset.touchProximity = "medium" + } + if (barRefs.current[i - 3]) { + barRefs.current[i - 3]!.dataset.touchProximity = "far" + } + } else if (barRect.left < x) { + bar.dataset.touched = "false" + bar.dataset.highlighted = "true" + if (touchedIndex >= 0) { + const diff = i - touchedIndex + if (diff === 1) { + bar.dataset.touchProximity = "close" + } else if (diff === 2) { + bar.dataset.touchProximity = "medium" + } else if (diff === 3) { + bar.dataset.touchProximity = "far" + } else { + delete bar.dataset.touchProximity + } + } else { + delete bar.dataset.touchProximity + } + } else if (barRect.right > x) { + bar.dataset.highlighted = "false" + bar.dataset.touched = "false" + if (touchedIndex >= 0) { + const diff = i - touchedIndex + if (diff === 1) { + bar.dataset.touchProximity = "close" + } else if (diff === 2) { + bar.dataset.touchProximity = "medium" + } else if (diff === 3) { + bar.dataset.touchProximity = "far" + } else { + delete bar.dataset.touchProximity + } + } else { + delete bar.dataset.touchProximity + } + } else { + bar.dataset.touched = "false" + bar.dataset.highlighted = "false" + delete bar.dataset.touchProximity + } + } + + if (touchedIndex === -1) { + const firstElement = barRefs.current[barRefs.current.length - 1] + const lastElement = barRefs.current[0] + if (lastElement && x > lastElement.getBoundingClientRect().right) { + lastElement.dataset.thumb = "true" + setIntermediateBrightness(100) + } else if (firstElement && x < firstElement.getBoundingClientRect().left) { + setIntermediateBrightness(0) + } + } + } + }) + + return ( + +
+ {Array.from({ length: BAR_COUNT }).map((_, index) => { + const highlighted = index >= initialHighlightIndexStart + return ( +
{ + barRefs.current[index] = ref + }} + // biome-ignore lint/suspicious/noArrayIndexKey: + key={index} + className="transition-all group-data-[active=true]:transition-none w-[2px] h-[2px] bg-neutral-400 rounded-full data-[highlighted=true]:h-2 data-[touch-proximity=close]:h-6 data-[touch-proximity=medium]:h-4 data-[touch-proximity=far]:h-2 data-[highlighted=true]:bg-teal-500 data-[touched=true]:h-8 data-[touched=true]:w-1 data-[thumb=true]:h-8" + /> + ) + })} +
+
+

Desk light

+ +
+ + ) +} + +function BrightnessLevelLabel({ deviceName }: { deviceName: ZigbeeDeviceName }) { + const currentBrightness = useAtomValue(useAtomValue(brightnessAtoms)[deviceName]) + const intermediateBrightness = useAtomValue(useAtomValue(intermediateBrightnessAtoms)[deviceName]) + + const brightness = intermediateBrightness === -1 ? currentBrightness : intermediateBrightness + + let label: string + if (brightness === 0) { + label = "OFF" + } else { + label = `${brightness}%` + } + + return ( +

+ {label} +

+ ) +} + export default App diff --git a/apps/dashboard/src/beszel.ts b/apps/dashboard/src/beszel.ts index 9f4a1c3..446439a 100644 --- a/apps/dashboard/src/beszel.ts +++ b/apps/dashboard/src/beszel.ts @@ -5,7 +5,7 @@ import { queryOptions } from "@tanstack/react-query" -const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000" +const API_BASE_URL = `http://${import.meta.env.VITE_API_HOST || "localhost:8000"}` // System Info export interface BeszelSystemInfo { diff --git a/apps/dashboard/src/env.d.ts b/apps/dashboard/src/env.d.ts index 9be35e0..0d9c615 100644 --- a/apps/dashboard/src/env.d.ts +++ b/apps/dashboard/src/env.d.ts @@ -1,7 +1,7 @@ /// interface ImportMetaEnv { - readonly VITE_API_URL: string; + readonly VITE_API_HOST: string; readonly VITE_DEFAULT_LATITUDE: string; readonly VITE_DEFAULT_LONGITUDE: string; } diff --git a/apps/dashboard/src/index.css b/apps/dashboard/src/index.css index 634141e..20c0049 100644 --- a/apps/dashboard/src/index.css +++ b/apps/dashboard/src/index.css @@ -3,10 +3,10 @@ @tailwind utilities; body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", + "Droid Sans", "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overscroll-behavior: none; } diff --git a/apps/dashboard/src/tfl.ts b/apps/dashboard/src/tfl.ts index 26bad37..dc3a5eb 100644 --- a/apps/dashboard/src/tfl.ts +++ b/apps/dashboard/src/tfl.ts @@ -5,7 +5,7 @@ import { queryOptions } from "@tanstack/react-query" -const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000" +const API_BASE_URL = `http://${import.meta.env.VITE_API_HOST || "localhost:8000"}` // Disruption Summary export interface DisruptionSummary { diff --git a/apps/dashboard/src/weather.ts b/apps/dashboard/src/weather.ts index 774f833..1db4ca6 100644 --- a/apps/dashboard/src/weather.ts +++ b/apps/dashboard/src/weather.ts @@ -26,7 +26,7 @@ import { Wind, } from "lucide-react" -const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:3000" +const API_BASE_URL = `http://${import.meta.env.VITE_API_HOST || "localhost:3000"}` export const DEFAULT_LATITUDE = Number(import.meta.env.VITE_DEFAULT_LATITUDE) || 37.7749 export const DEFAULT_LONGITUDE = Number(import.meta.env.VITE_DEFAULT_LONGITUDE) || -122.4194 diff --git a/bun.lock b/bun.lock index eb3b3bb..f65114f 100644 --- a/bun.lock +++ b/bun.lock @@ -12,10 +12,13 @@ }, }, "apps/backend": { - "name": "backend", + "name": "@eva/backend", "version": "1.0.0", "dependencies": { + "@eva/jrpc": "workspace:*", + "@eva/zigbee": "workspace:*", "hono": "^4.6.14", + "mqtt": "^5.14.1", }, "devDependencies": { "@types/bun": "latest", @@ -23,10 +26,13 @@ }, }, "apps/dashboard": { - "name": "dashboard", + "name": "@eva/dashboard", "version": "1.0.0", "dependencies": { + "@eva/jrpc": "workspace:*", + "@eva/zigbee": "workspace:*", "@tanstack/react-query": "^5.62.7", + "@use-gesture/react": "^10.3.1", "chart.js": "^4.5.1", "jotai": "^2.10.3", "lucide-react": "^0.546.0", @@ -44,6 +50,27 @@ "vite": "^6.0.1", }, }, + "packages/jrpc": { + "name": "@eva/jrpc", + "dependencies": { + "@eva/zigbee": "workspace:*", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/zigbee": { + "name": "@eva/zigbee", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -80,6 +107,8 @@ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -156,6 +185,14 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], + "@eva/backend": ["@eva/backend@workspace:apps/backend"], + + "@eva/dashboard": ["@eva/dashboard@workspace:apps/dashboard"], + + "@eva/jrpc": ["@eva/jrpc@workspace:packages/jrpc"], + + "@eva/zigbee": ["@eva/zigbee@workspace:packages/zigbee"], + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -248,8 +285,18 @@ "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@types/readable-stream": ["@types/readable-stream@4.0.22", "", { "dependencies": { "@types/node": "*" } }, "sha512-/FFhJpfCLAPwAcN3mFycNUa77ddnr8jTgF5VmSNetaemWB2cIlfCA9t0YTM3JAT0wOcv8D4tjPo7pkDhK3EJIg=="], + + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + + "@use-gesture/core": ["@use-gesture/core@10.3.1", "", {}, "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw=="], + + "@use-gesture/react": ["@use-gesture/react@10.3.1", "", { "dependencies": { "@use-gesture/core": "10.3.1" }, "peerDependencies": { "react": ">= 16.8.0" } }, "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -262,20 +309,28 @@ "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], - "backend": ["backend@workspace:apps/backend"], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ=="], "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], + "bl": ["bl@6.1.4", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-ZV/9asSuknOExbM/zPPA8z00lc1ihPKWaStHkkQrxHNeYx+yY+TmF+v80dpv2G0mv3HVXBu7ryoAsxbFFhf4eg=="], + "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "broker-factory": ["broker-factory@3.1.10", "", { "dependencies": { "@babel/runtime": "^7.28.4", "fast-unique-numbers": "^9.0.24", "tslib": "^2.8.1", "worker-factory": "^7.0.46" } }, "sha512-BzqK5GYFhvVFvO13uzPN0SCiOsOQuhMUbsGvTXDJMA2/N4GvIlFdxEuueE+60Zk841bBU5G3+fl2cqYEo0wgGg=="], + "browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], @@ -294,6 +349,10 @@ "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "commist": ["commist@3.2.0", "", {}, "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw=="], + + "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -302,8 +361,6 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "dashboard": ["dashboard@workspace:apps/dashboard"], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], @@ -320,8 +377,14 @@ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-unique-numbers": ["fast-unique-numbers@9.0.24", "", { "dependencies": { "@babel/runtime": "^7.28.4", "tslib": "^2.8.1" } }, "sha512-Dv0BYn4waOWse94j16rsZ5w/0zoaCa74O3q6IZjMqaXbtT92Q+Sb6pPk+phGzD8Xh+nueQmSRI3tSCaHKidzKw=="], + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -344,8 +407,16 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], + "hono": ["hono@4.10.2", "", {}, "sha512-p6fyzl+mQo6uhESLxbF5WlBOAJMDh36PljwlKtP5V1v09NxlqGru3ShK+4wKhSuhuYf8qxMmrivHOa/M7q0sMg=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ip-address": ["ip-address@10.0.1", "", {}, "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], @@ -366,6 +437,8 @@ "jotai": ["jotai@2.15.0", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-nbp/6jN2Ftxgw0VwoVnOg0m5qYM1rVcfvij+MZx99Z5IK13eGve9FJoCwGv+17JvVthTjhSmNtT5e1coJnr6aw=="], + "js-sdsl": ["js-sdsl@4.3.0", "", {}, "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], @@ -378,7 +451,7 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="], @@ -388,8 +461,14 @@ "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "mqtt": ["mqtt@5.14.1", "", { "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", "commist": "^3.2.0", "concat-stream": "^2.0.0", "debug": "^4.4.1", "help-me": "^5.0.0", "lru-cache": "^10.4.3", "minimist": "^1.2.8", "mqtt-packet": "^9.0.2", "number-allocator": "^1.0.14", "readable-stream": "^4.7.0", "rfdc": "^1.4.1", "socks": "^2.8.6", "split2": "^4.2.0", "worker-timers": "^8.0.23", "ws": "^8.18.3" }, "bin": { "mqtt_pub": "build/bin/pub.js", "mqtt_sub": "build/bin/sub.js", "mqtt": "build/bin/mqtt.js" } }, "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw=="], + + "mqtt-packet": ["mqtt-packet@9.0.2", "", { "dependencies": { "bl": "^6.0.8", "debug": "^4.3.4", "process-nextick-args": "^2.0.1" } }, "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "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=="], @@ -402,6 +481,8 @@ "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], + "number-allocator": ["number-allocator@1.0.14", "", { "dependencies": { "debug": "^4.3.1", "js-sdsl": "4.3.0" } }, "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], @@ -436,6 +517,10 @@ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], @@ -446,16 +531,22 @@ "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -466,12 +557,20 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -494,6 +593,10 @@ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -506,22 +609,34 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "worker-factory": ["worker-factory@7.0.46", "", { "dependencies": { "@babel/runtime": "^7.28.4", "fast-unique-numbers": "^9.0.24", "tslib": "^2.8.1" } }, "sha512-Sr1hq2FMgNa04UVhYQacsw+i58BtMimzDb4+CqYphZ97OfefRpURu0UZ+JxMr/H36VVJBfuVkxTK7MytsanC3w=="], + + "worker-timers": ["worker-timers@8.0.25", "", { "dependencies": { "@babel/runtime": "^7.28.4", "tslib": "^2.8.1", "worker-timers-broker": "^8.0.11", "worker-timers-worker": "^9.0.11" } }, "sha512-X7Z5dmM6PlrEnaadtFQOyXHGD/IysPA3HZzaC2koqsU1VI+RvyGmjiiLiUBQixK8PH5R7ilkOzZupWskNRaXmA=="], + + "worker-timers-broker": ["worker-timers-broker@8.0.11", "", { "dependencies": { "@babel/runtime": "^7.28.4", "broker-factory": "^3.1.10", "fast-unique-numbers": "^9.0.24", "tslib": "^2.8.1", "worker-timers-worker": "^9.0.11" } }, "sha512-uwhxKru8BI9m2tsogxr2fB6POZ8LB2xH+Pu3R0mvQnAZLPgLD6K3IX4LNKPTEgTJ/j5VsuQPB+gLI1NBNKkPlg=="], + + "worker-timers-worker": ["worker-timers-worker@9.0.11", "", { "dependencies": { "@babel/runtime": "^7.28.4", "tslib": "^2.8.1", "worker-factory": "^7.0.46" } }, "sha512-pArb5xtgHWImYpXhjg1OFv7JFG0ubmccb73TFoXHXjG830fFj+16N57q9YeBnZX52dn+itRrMoJZ9HaZBVzDaA=="], + "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "concat-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "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/package.json b/package.json index b4f2d98..4b52ba1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "monorepo", "version": "1.0.0", "private": true, - "workspaces": ["apps/*"], + "workspaces": ["apps/*", "packages/*"], "scripts": { "dev": "bun run --elide-lines 0 --filter './apps/*' dev", "build": "bun run --filter '*' build", diff --git a/packages/jrpc/.gitignore b/packages/jrpc/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/jrpc/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/jrpc/README.md b/packages/jrpc/README.md new file mode 100644 index 0000000..236f5bf --- /dev/null +++ b/packages/jrpc/README.md @@ -0,0 +1,15 @@ +# @eva/jrpc + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.1. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/jrpc/index.ts b/packages/jrpc/index.ts new file mode 100644 index 0000000..a930d53 --- /dev/null +++ b/packages/jrpc/index.ts @@ -0,0 +1,49 @@ +import type { ZigbeeDeviceName, ZigbeeDeviceStates } from "@eva/zigbee" + +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 + } +} + +export type JrpcRequest = { + [M in keyof JrpcSchema]: { + id: string + jsonrpc: "2.0" + method: M + params: JrpcSchema[M]["Params"] + } +}[Method] + +export type JrpcResponse = { + [M in keyof JrpcSchema]: + | { + id: string + jsonrpc: "2.0" + result: JrpcSchema[M]["Response"] + } + | { + id: string + jsonrpc: "2.0" + error: string + } +}[Method] diff --git a/packages/jrpc/package.json b/packages/jrpc/package.json new file mode 100644 index 0000000..7ffbe77 --- /dev/null +++ b/packages/jrpc/package.json @@ -0,0 +1,14 @@ +{ + "name": "@eva/jrpc", + "module": "index.ts", + "type": "module", + "dependencies": { + "@eva/zigbee": "workspace:*" + }, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/packages/jrpc/tsconfig.json b/packages/jrpc/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/packages/jrpc/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/packages/zigbee/.gitignore b/packages/zigbee/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/zigbee/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/zigbee/README.md b/packages/zigbee/README.md new file mode 100644 index 0000000..99edbf4 --- /dev/null +++ b/packages/zigbee/README.md @@ -0,0 +1,15 @@ +# @eva/zigbee + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.1. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/zigbee/index.ts b/packages/zigbee/index.ts new file mode 100644 index 0000000..a2e4d81 --- /dev/null +++ b/packages/zigbee/index.ts @@ -0,0 +1,31 @@ +export const ZIGBEE_BASE_TOPIC = "nexus" + +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]: { + state: "ON" | "OFF" + brightness: number + } + [ZIGBEE_DEVICE.livingRoomFloorLamp]: { + brightness: number + level_config: { + on_level: "previous" + } + linkquality: number + state: "ON" | "OFF" + update: { + installed_version: number + latest_version: number + state: "available" | "idle" + } + } +} + +export const ALL_ZIGBEE_DEVICE_NAMES: ZigbeeDeviceName[] = Object.values(ZIGBEE_DEVICE) + +export type ZigbeeDeviceState = ZigbeeDeviceStates[keyof ZigbeeDeviceStates] diff --git a/packages/zigbee/package.json b/packages/zigbee/package.json new file mode 100644 index 0000000..96bffbb --- /dev/null +++ b/packages/zigbee/package.json @@ -0,0 +1,11 @@ +{ + "name": "@eva/zigbee", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/packages/zigbee/tsconfig.json b/packages/zigbee/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/packages/zigbee/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}