feat: initial zigbee control implementation
Some checks failed
Build and Publish Docker Image / build-and-push (push) Failing after 27s

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2025-10-28 01:11:57 +00:00
parent 2e63609129
commit b55d99dd9e
30 changed files with 901 additions and 70 deletions

View File

@@ -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"
}
}

View File

@@ -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
}
}

View File

@@ -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,
}

6
apps/backend/src/jrpc.ts Normal file
View File

@@ -0,0 +1,6 @@
export type JrpcRequest = {
jsonrpc: "2.0"
method: string
params: unknown
id: number
}

15
apps/backend/src/mqtt.ts Normal file
View File

@@ -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,
})
}

View File

@@ -0,0 +1 @@
const BASE_TOPIC = "nexus"

View File

@@ -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<string, DeviceMessageListener[]> = 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<void> {
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<void> {
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<void> {
await this.mqtt.publishAsync(`${this.baseTopic}/${deviceName}/set`, JSON.stringify(state))
}
}

View File

@@ -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
}
}

View File

@@ -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<ZigbeeContext>()
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

View File

@@ -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<ZigbeeDeviceName, DeviceMessageListener> = 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))
}
}
}
}

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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 (
<div className="h-screen bg-neutral-300 dark:bg-neutral-800 p-2">
<div className="h-screen bg-neutral-300 dark:bg-neutral-800 p-2 select-none">
<div className="w-full h-full grid grid-cols-4 grid-rows-5 gap-2 bg-neutral-300 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300">
<DateTimeTile />
<WeatherTile />
<TFLTile className="row-start-1 row-span-1" />
<SystemTile className="row-start-2 row-span-1" systemName="helian" displayName="Helian" />
<SystemTile className="row-start-2 row-span-1" systemName="akira" displayName="Akira" />
<TFLTile className="row-start-1 row-span-1" />
<Tile className="row-start-3 col-span-2 row-span-3" />
<LightControlTile
className="row-start-3 col-start-3 col-span-1"
deviceName={ZIGBEE_DEVICE.livingRoomFloorLamp}
onRequestBrightnessChange={(brightness) => {
setBrightness(ZIGBEE_DEVICE.livingRoomFloorLamp, brightness)
}}
/>
<LightControlTile
className="row-start-3 col-start-4 col-span-1"
deviceName={ZIGBEE_DEVICE.deskLamp}
onRequestBrightnessChange={(brightness) => {
setBrightness(ZIGBEE_DEVICE.deskLamp, brightness)
}}
/>
<Tile className="row-start-4 col-span-2 row-span-3" />
</div>
</div>
)
@@ -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<HTMLDivElement | null>(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 (
<Tile className={cn("h-full flex flex-col justify-start items-start", className)}>
<div
{...bind()}
ref={touchContainerRef}
className="group flex-1 flex flex-row-reverse justify-center items-center touch-none gap-x-1 w-full translate-y-6"
>
{Array.from({ length: BAR_COUNT }).map((_, index) => {
const highlighted = index >= initialHighlightIndexStart
return (
<div
data-highlighted={highlighted}
data-thumb={index === initialHighlightIndexStart}
data-touched={false}
ref={(ref) => {
barRefs.current[index] = ref
}}
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
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"
/>
)
})}
</div>
<div className="px-4 pb-2 w-full flex flex-row items-center justify-center space-x-2">
<p className="tracking-tigher uppercase">Desk light</p>
<BrightnessLevelLabel deviceName={deviceName} />
</div>
</Tile>
)
}
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 (
<p
className={cn(
"flex-1 text-right font-bold font-mono tracking-tigher",
brightness === 0 ? "text-neutral-400" : "text-teal-400",
)}
>
{label}
</p>
)
}
export default App

View File

@@ -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 {

View File

@@ -1,7 +1,7 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_API_HOST: string;
readonly VITE_DEFAULT_LATITUDE: string;
readonly VITE_DEFAULT_LONGITUDE: string;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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

133
bun.lock
View File

@@ -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=="],

View File

@@ -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",

34
packages/jrpc/.gitignore vendored Normal file
View File

@@ -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

15
packages/jrpc/README.md Normal file
View File

@@ -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.

49
packages/jrpc/index.ts Normal file
View File

@@ -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<Method extends keyof JrpcSchema = keyof JrpcSchema> = {
[M in keyof JrpcSchema]: {
id: string
jsonrpc: "2.0"
method: M
params: JrpcSchema[M]["Params"]
}
}[Method]
export type JrpcResponse<Method extends keyof JrpcSchema = keyof JrpcSchema> = {
[M in keyof JrpcSchema]:
| {
id: string
jsonrpc: "2.0"
result: JrpcSchema[M]["Response"]
}
| {
id: string
jsonrpc: "2.0"
error: string
}
}[Method]

View File

@@ -0,0 +1,14 @@
{
"name": "@eva/jrpc",
"module": "index.ts",
"type": "module",
"dependencies": {
"@eva/zigbee": "workspace:*"
},
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}

View File

@@ -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
}
}

34
packages/zigbee/.gitignore vendored Normal file
View File

@@ -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

15
packages/zigbee/README.md Normal file
View File

@@ -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.

31
packages/zigbee/index.ts Normal file
View File

@@ -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]

View File

@@ -0,0 +1,11 @@
{
"name": "@eva/zigbee",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}

View File

@@ -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
}
}