1 min read
AutomatisierungSmart Home Dashboard Development
Smart Home Dashboard mit React und WebSocket. Real-time Updates, Device Control und responsive Design für die Heimautomatisierung.
Smart Home DashboardReactWebSocketHome AutomationReal-time UIDevice Control

Smart Home Dashboard Development
Meta-Description: Smart Home Dashboard mit React und WebSocket. Real-time Updates, Device Control und responsive Design für die Heimautomatisierung.
Keywords: Smart Home Dashboard, React, WebSocket, Home Automation, Real-time UI, Device Control, IoT Dashboard
Einführung
Ein Smart Home Dashboard ist die zentrale Steuerung für alle vernetzten Geräte. Mit React, WebSocket und modernem UI-Design bauen wir ein responsives Dashboard für Echtzeit-Monitoring und Gerätesteuerung.
Dashboard Architecture
┌─────────────────────────────────────────────────────────────┐
│ SMART HOME DASHBOARD ARCHITECTURE │
├─────────────────────────────────────────────────────────────┤
│ │
│ Frontend: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ React + Next.js │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Device │ │ Room │ │ Scenes │ │ │
│ │ │ Cards │ │ View │ │ Manager │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Energy │ │ Climate │ │ Security │ │ │
│ │ │ Monitor │ │ Control │ │ Panel │ │ │
│ │ └───────────┘ └───────────┘ └───────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ WebSocket / REST │
│ │ │
│ Backend: ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ API Gateway / BFF │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────┬───────────┴───────────┬────────────┐ │
│ │ MQTT │ Home Assistant │ Database │ │
│ │ Broker │ API │ (States) │ │
│ └────────────┴───────────────────────┴────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘React Dashboard Components
// components/dashboard/DeviceCard.tsx
'use client';
import { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Switch } from '@/components/ui/switch';
import { Slider } from '@/components/ui/slider';
import { cn } from '@/lib/utils';
interface DeviceCardProps {
device: {
id: string;
name: string;
type: 'light' | 'switch' | 'sensor' | 'thermostat';
state: 'on' | 'off';
brightness?: number;
temperature?: number;
humidity?: number;
setpoint?: number;
};
onStateChange: (id: string, state: Partial<typeof device>) => void;
}
export function DeviceCard({ device, onStateChange }: DeviceCardProps) {
const [isLoading, setIsLoading] = useState(false);
const handleToggle = async (checked: boolean) => {
setIsLoading(true);
await onStateChange(device.id, { state: checked ? 'on' : 'off' });
setIsLoading(false);
};
const handleBrightness = async (value: number[]) => {
await onStateChange(device.id, { brightness: value[0] });
};
const handleSetpoint = async (value: number[]) => {
await onStateChange(device.id, { setpoint: value[0] });
};
return (
<Card className={cn(
'transition-all duration-200',
device.state === 'on' && 'ring-2 ring-primary'
)}>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{device.name}</CardTitle>
{(device.type === 'light' || device.type === 'switch') && (
<Switch
checked={device.state === 'on'}
onCheckedChange={handleToggle}
disabled={isLoading}
/>
)}
</CardHeader>
<CardContent>
{/* Light with Brightness */}
{device.type === 'light' && device.brightness !== undefined && (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Brightness</span>
<span>{device.brightness}%</span>
</div>
<Slider
value={[device.brightness]}
onValueChange={handleBrightness}
max={100}
step={1}
disabled={device.state === 'off'}
/>
</div>
)}
{/* Sensor Readings */}
{device.type === 'sensor' && (
<div className="grid grid-cols-2 gap-4">
{device.temperature !== undefined && (
<div className="text-center">
<div className="text-2xl font-bold">{device.temperature}°C</div>
<div className="text-xs text-muted-foreground">Temperature</div>
</div>
)}
{device.humidity !== undefined && (
<div className="text-center">
<div className="text-2xl font-bold">{device.humidity}%</div>
<div className="text-xs text-muted-foreground">Humidity</div>
</div>
)}
</div>
)}
{/* Thermostat */}
{device.type === 'thermostat' && (
<div className="space-y-4">
<div className="text-center">
<div className="text-3xl font-bold">{device.temperature}°C</div>
<div className="text-xs text-muted-foreground">Current</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Setpoint</span>
<span>{device.setpoint}°C</span>
</div>
<Slider
value={[device.setpoint || 20]}
onValueChange={handleSetpoint}
min={16}
max={28}
step={0.5}
/>
</div>
</div>
)}
</CardContent>
</Card>
);
}Room View
// components/dashboard/RoomView.tsx
'use client';
import { useState, useEffect } from 'react';
import { DeviceCard } from './DeviceCard';
import { useSmartHome } from '@/hooks/useSmartHome';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Home,
Sofa,
Bed,
UtensilsCrossed,
Bath,
TreeDeciduous
} from 'lucide-react';
const roomIcons: Record<string, any> = {
all: Home,
living_room: Sofa,
bedroom: Bed,
kitchen: UtensilsCrossed,
bathroom: Bath,
outdoor: TreeDeciduous
};
export function RoomView() {
const { devices, rooms, updateDevice, isConnected } = useSmartHome();
const [activeRoom, setActiveRoom] = useState('all');
const filteredDevices = activeRoom === 'all'
? devices
: devices.filter(d => d.room === activeRoom);
return (
<div className="space-y-6">
{/* Connection Status */}
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${
isConnected ? 'bg-green-500' : 'bg-red-500'
}`} />
<span className="text-sm text-muted-foreground">
{isConnected ? 'Connected' : 'Disconnected'}
</span>
</div>
{/* Room Tabs */}
<Tabs value={activeRoom} onValueChange={setActiveRoom}>
<TabsList className="flex-wrap h-auto">
<TabsTrigger value="all" className="gap-2">
<Home className="w-4 h-4" />
All
</TabsTrigger>
{rooms.map(room => {
const Icon = roomIcons[room.id] || Home;
return (
<TabsTrigger key={room.id} value={room.id} className="gap-2">
<Icon className="w-4 h-4" />
{room.name}
</TabsTrigger>
);
})}
</TabsList>
<TabsContent value={activeRoom} className="mt-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{filteredDevices.map(device => (
<DeviceCard
key={device.id}
device={device}
onStateChange={updateDevice}
/>
))}
</div>
{filteredDevices.length === 0 && (
<div className="text-center py-12 text-muted-foreground">
No devices in this room
</div>
)}
</TabsContent>
</Tabs>
</div>
);
}WebSocket Hook
// hooks/useSmartHome.ts
'use client';
import { useState, useEffect, useCallback, useRef } from 'react';
interface Device {
id: string;
name: string;
type: 'light' | 'switch' | 'sensor' | 'thermostat';
room: string;
state: 'on' | 'off';
brightness?: number;
temperature?: number;
humidity?: number;
setpoint?: number;
lastUpdated: string;
}
interface Room {
id: string;
name: string;
}
interface SmartHomeState {
devices: Device[];
rooms: Room[];
isConnected: boolean;
}
export function useSmartHome() {
const [state, setState] = useState<SmartHomeState>({
devices: [],
rooms: [],
isConnected: false
});
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
const connect = useCallback(() => {
const wsUrl = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:3001';
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connected');
setState(prev => ({ ...prev, isConnected: true }));
// Request initial state
ws.send(JSON.stringify({ type: 'get_state' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'initial_state':
setState(prev => ({
...prev,
devices: message.devices,
rooms: message.rooms
}));
break;
case 'device_update':
setState(prev => ({
...prev,
devices: prev.devices.map(d =>
d.id === message.device.id
? { ...d, ...message.device, lastUpdated: new Date().toISOString() }
: d
)
}));
break;
case 'device_added':
setState(prev => ({
...prev,
devices: [...prev.devices, message.device]
}));
break;
case 'device_removed':
setState(prev => ({
...prev,
devices: prev.devices.filter(d => d.id !== message.deviceId)
}));
break;
}
};
ws.onclose = () => {
console.log('WebSocket disconnected');
setState(prev => ({ ...prev, isConnected: false }));
// Reconnect after 5 seconds
reconnectTimeoutRef.current = setTimeout(connect, 5000);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
wsRef.current = ws;
}, []);
useEffect(() => {
connect();
return () => {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
wsRef.current?.close();
};
}, [connect]);
const updateDevice = useCallback(async (
deviceId: string,
updates: Partial<Device>
) => {
// Optimistic Update
setState(prev => ({
...prev,
devices: prev.devices.map(d =>
d.id === deviceId ? { ...d, ...updates } : d
)
}));
// Send to server
wsRef.current?.send(JSON.stringify({
type: 'update_device',
deviceId,
updates
}));
}, []);
const executeScene = useCallback((sceneId: string) => {
wsRef.current?.send(JSON.stringify({
type: 'execute_scene',
sceneId
}));
}, []);
return {
...state,
updateDevice,
executeScene
};
}Scene Manager
// components/dashboard/SceneManager.tsx
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Sun,
Moon,
Film,
Coffee,
PartyPopper,
Power
} from 'lucide-react';
interface Scene {
id: string;
name: string;
icon: string;
description: string;
}
const defaultScenes: Scene[] = [
{ id: 'morning', name: 'Good Morning', icon: 'sun', description: 'Wake up routine' },
{ id: 'movie', name: 'Movie Time', icon: 'film', description: 'Dim lights for movies' },
{ id: 'goodnight', name: 'Good Night', icon: 'moon', description: 'All lights off' },
{ id: 'party', name: 'Party Mode', icon: 'party', description: 'Colorful lights' },
{ id: 'away', name: 'Away', icon: 'power', description: 'Energy saving mode' },
{ id: 'coffee', name: 'Coffee Break', icon: 'coffee', description: 'Cozy atmosphere' }
];
const iconMap: Record<string, any> = {
sun: Sun,
moon: Moon,
film: Film,
coffee: Coffee,
party: PartyPopper,
power: Power
};
interface SceneManagerProps {
onExecute: (sceneId: string) => void;
}
export function SceneManager({ onExecute }: SceneManagerProps) {
const [activeScene, setActiveScene] = useState<string | null>(null);
const [isExecuting, setIsExecuting] = useState(false);
const handleExecute = async (sceneId: string) => {
setIsExecuting(true);
setActiveScene(sceneId);
await onExecute(sceneId);
setTimeout(() => {
setIsExecuting(false);
}, 1000);
};
return (
<Card>
<CardHeader>
<CardTitle>Scenes</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
{defaultScenes.map(scene => {
const Icon = iconMap[scene.icon] || Sun;
const isActive = activeScene === scene.id;
return (
<Button
key={scene.id}
variant={isActive ? 'default' : 'outline'}
className="h-auto py-4 flex flex-col gap-2"
onClick={() => handleExecute(scene.id)}
disabled={isExecuting}
>
<Icon className="w-6 h-6" />
<span className="text-sm font-medium">{scene.name}</span>
</Button>
);
})}
</div>
</CardContent>
</Card>
);
}Climate Control
// components/dashboard/ClimateControl.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Thermometer, Droplets, Wind, Plus, Minus } from 'lucide-react';
interface ClimateControlProps {
temperature: number;
humidity: number;
setpoint: number;
mode: 'heat' | 'cool' | 'auto' | 'off';
onSetpointChange: (value: number) => void;
onModeChange: (mode: string) => void;
}
export function ClimateControl({
temperature,
humidity,
setpoint,
mode,
onSetpointChange,
onModeChange
}: ClimateControlProps) {
const adjustSetpoint = (delta: number) => {
const newValue = Math.max(16, Math.min(28, setpoint + delta));
onSetpointChange(newValue);
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Thermometer className="w-5 h-5" />
Climate
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Current Values */}
<div className="grid grid-cols-2 gap-4">
<div className="text-center p-4 bg-muted rounded-lg">
<Thermometer className="w-6 h-6 mx-auto mb-2 text-orange-500" />
<div className="text-3xl font-bold">{temperature}°C</div>
<div className="text-sm text-muted-foreground">Current</div>
</div>
<div className="text-center p-4 bg-muted rounded-lg">
<Droplets className="w-6 h-6 mx-auto mb-2 text-blue-500" />
<div className="text-3xl font-bold">{humidity}%</div>
<div className="text-sm text-muted-foreground">Humidity</div>
</div>
</div>
{/* Setpoint Control */}
<div className="flex items-center justify-center gap-6">
<Button
variant="outline"
size="icon"
className="w-12 h-12 rounded-full"
onClick={() => adjustSetpoint(-0.5)}
>
<Minus className="w-6 h-6" />
</Button>
<div className="text-center">
<div className="text-5xl font-bold">{setpoint}°C</div>
<div className="text-sm text-muted-foreground">Target</div>
</div>
<Button
variant="outline"
size="icon"
className="w-12 h-12 rounded-full"
onClick={() => adjustSetpoint(0.5)}
>
<Plus className="w-6 h-6" />
</Button>
</div>
{/* Mode Selection */}
<div className="flex gap-2">
{['heat', 'cool', 'auto', 'off'].map(m => (
<Button
key={m}
variant={mode === m ? 'default' : 'outline'}
className="flex-1"
onClick={() => onModeChange(m)}
>
{m.charAt(0).toUpperCase() + m.slice(1)}
</Button>
))}
</div>
</CardContent>
</Card>
);
}Energy Monitor
// components/dashboard/EnergyMonitor.tsx
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Zap, TrendingDown, TrendingUp } from 'lucide-react';
interface EnergyData {
currentPower: number; // Watts
todayUsage: number; // kWh
monthlyUsage: number; // kWh
monthlyBudget: number; // kWh
trend: 'up' | 'down' | 'stable';
trendPercent: number;
}
interface EnergyMonitorProps {
data: EnergyData;
}
export function EnergyMonitor({ data }: EnergyMonitorProps) {
const budgetUsage = (data.monthlyUsage / data.monthlyBudget) * 100;
const TrendIcon = data.trend === 'up' ? TrendingUp : TrendingDown;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-yellow-500" />
Energy
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Current Power */}
<div className="text-center">
<div className="text-4xl font-bold">
{data.currentPower.toLocaleString()} W
</div>
<div className="text-sm text-muted-foreground">Current Power</div>
</div>
{/* Usage Stats */}
<div className="grid grid-cols-2 gap-4">
<div className="text-center p-3 bg-muted rounded-lg">
<div className="text-xl font-semibold">
{data.todayUsage.toFixed(1)} kWh
</div>
<div className="text-xs text-muted-foreground">Today</div>
</div>
<div className="text-center p-3 bg-muted rounded-lg">
<div className="text-xl font-semibold">
{data.monthlyUsage.toFixed(0)} kWh
</div>
<div className="text-xs text-muted-foreground">This Month</div>
</div>
</div>
{/* Budget Progress */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Monthly Budget</span>
<span>{budgetUsage.toFixed(0)}%</span>
</div>
<Progress value={budgetUsage} className="h-2" />
<div className="text-xs text-muted-foreground text-right">
{data.monthlyUsage.toFixed(0)} / {data.monthlyBudget} kWh
</div>
</div>
{/* Trend */}
<div className="flex items-center justify-center gap-2">
<TrendIcon className={`w-5 h-5 ${
data.trend === 'up' ? 'text-red-500' : 'text-green-500'
}`} />
<span className={`font-medium ${
data.trend === 'up' ? 'text-red-500' : 'text-green-500'
}`}>
{data.trend === 'up' ? '+' : '-'}{data.trendPercent}%
</span>
<span className="text-sm text-muted-foreground">vs last week</span>
</div>
</CardContent>
</Card>
);
}Main Dashboard Layout
// app/dashboard/page.tsx
'use client';
import { RoomView } from '@/components/dashboard/RoomView';
import { SceneManager } from '@/components/dashboard/SceneManager';
import { ClimateControl } from '@/components/dashboard/ClimateControl';
import { EnergyMonitor } from '@/components/dashboard/EnergyMonitor';
import { useSmartHome } from '@/hooks/useSmartHome';
export default function DashboardPage() {
const { devices, executeScene, updateDevice } = useSmartHome();
// Aggregate climate data
const climateDevice = devices.find(d => d.type === 'thermostat');
// Mock energy data (in production: from API)
const energyData = {
currentPower: 1234,
todayUsage: 12.5,
monthlyUsage: 245,
monthlyBudget: 300,
trend: 'down' as const,
trendPercent: 8
};
return (
<div className="min-h-screen bg-background p-6">
<header className="mb-8">
<h1 className="text-3xl font-bold">Smart Home</h1>
<p className="text-muted-foreground">Welcome back!</p>
</header>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main Area - Devices */}
<div className="lg:col-span-2 space-y-6">
<RoomView />
</div>
{/* Sidebar */}
<div className="space-y-6">
<SceneManager onExecute={executeScene} />
{climateDevice && (
<ClimateControl
temperature={climateDevice.temperature || 21}
humidity={50}
setpoint={climateDevice.setpoint || 21}
mode="auto"
onSetpointChange={(value) =>
updateDevice(climateDevice.id, { setpoint: value })
}
onModeChange={(mode) =>
console.log('Mode:', mode)
}
/>
)}
<EnergyMonitor data={energyData} />
</div>
</div>
</div>
);
}Fazit
Smart Home Dashboard mit React bietet:
- Real-time Updates: WebSocket für Live-Daten
- Intuitive UI: Room-basierte Navigation
- Responsive Design: Desktop & Mobile
- Szenen & Automation: One-Click Control
Das perfekte Interface für Smart Home Control.
Bildprompts
- "Smart home dashboard on tablet, dark mode with glowing device cards"
- "Room view with connected devices, modern UI design"
- "Energy monitoring graph with real-time power consumption"