import { type JrpcRequest, type JrpcResponse, newJrpcRequestId } from "@eva/jrpc" import { ZIGBEE_DEVICE, type ZigbeeDeviceName } from "@eva/zigbee" import { useQuery } from "@tanstack/react-query" import { useDrag } from "@use-gesture/react" 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" import { StatusSeverity, TubeLine, formatLineName, tflDisruptionsQuery } from "./tfl" import { DEFAULT_LATITUDE, DEFAULT_LONGITUDE, currentWeatherQuery, dailyForecastQuery, getWeatherIcon, 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 wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:" const wsHost = import.meta.env.VITE_API_HOST || window.location.host const websocket = useRef(new WebSocket(`${wsProtocol}//${wsHost}/api/zigbee`)) const store = useStore() useEffect(() => { const ws = websocket.current ws.onopen = () => { console.log("WebSocket connected") } ws.onerror = (error) => { console.error("WebSocket error:", error) } ws.onclose = () => { console.log("WebSocket disconnected") } ws.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 (ws.readyState === WebSocket.OPEN) { ws.close() } } }, [store]) function setBrightness(deviceName: ZigbeeDeviceName, brightness: number) { const ws = websocket.current if (ws.readyState !== WebSocket.OPEN) { console.warn("WebSocket is not open. Current state:", ws.readyState) return } const req: JrpcRequest<"setDeviceState"> = { id: newJrpcRequestId(), jsonrpc: "2.0", method: "setDeviceState", params: { deviceName, state: brightness === 0 ? { state: "OFF", brightness: 0 } : { state: "ON", brightness: Math.round((brightness / 100) * 254) }, }, } ws.send(JSON.stringify(req)) } return (
{formattedDate}
{formattedTime}
Loading weather
Error loading weather
{error?.message ?? "Unknown error"}
H:{highTemp}°
L:{lowTemp}°
{temperature}°
{weatherDescriptionContent}
Loading tube status
Error loading from TfL
{errorTFL?.message}
No TfL data available
{lineName}
{reason}
> ) } function SystemTile({ className, systemName, displayName, }: { className?: string; systemName: string; displayName: string }) { const { data } = useQuery({ ...beszelSystemsQuery(), refetchInterval: 1000, refetchIntervalInBackground: true, }) const chartRef = useRefNo system status available
{displayName}
CPU
{beszelSystemsData.info.cpu.toFixed(0).padStart(3, "0")}
RAM
{beszelSystemsData.info.ram.toFixed(0).padStart(3, "0")}
DSK
{beszelSystemsData.info.disk.toFixed(0).padStart(3, "0")}
Desk light
{label}
) } export default App