Skip to main content
Glama
app.ts10.2 kB
import express from 'express'; import { DeviceRegistry } from './services/device-registry.js'; import { KasaDevice } from './devices/kasa.js'; import { Device, DeviceControlRequest, ApiResponse } from '../../shared/src/types.js'; const app = express(); app.use(express.json()); // Initialize device registry const registry = new DeviceRegistry(); // CORS for development app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); next(); }); // Helper function to create API response function createResponse<T>(success: boolean, data?: T, error?: string): ApiResponse<T> { return { success, data, error, timestamp: new Date().toISOString() }; } // Helper function to get device controller function getDeviceController(device: Device) { switch (device.brand) { case 'kasa': return new KasaDevice(device.ip); case 'tuya': // TODO: Implement Tuya controller throw new Error('Tuya devices not yet supported'); default: throw new Error(`Unsupported device brand: ${device.brand}`); } } // Routes // Health check app.get('/health', (req, res) => { res.json(createResponse(true, { status: 'healthy', timestamp: new Date().toISOString() })); }); // Get all devices app.get('/api/devices', async (req, res) => { try { const { room, type, brand, online } = req.query; let devices = registry.getAllDevices(); // Apply filters if (room) { devices = devices.filter(d => d.room?.toLowerCase() === (room as string).toLowerCase()); } if (type) { devices = devices.filter(d => d.type === type); } if (brand) { devices = devices.filter(d => d.brand === brand); } if (online !== undefined) { const isOnline = online === 'true'; devices = devices.filter(d => d.online === isOnline); } res.json(createResponse(true, devices)); } catch (error) { res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Get device by ID app.get('/api/devices/:id', async (req, res) => { try { const device = registry.getDevice(req.params.id); if (!device) { return res.status(404).json(createResponse(false, undefined, 'Device not found')); } // Try to get current state try { const controller = getDeviceController(device); const state = await controller.getState(); device.state = state; device.online = true; registry.updateDevice(device.id, { state, online: true }); } catch { device.online = false; registry.setDeviceOnlineStatus(device.id, false); } res.json(createResponse(true, device)); } catch (error) { res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Search devices app.get('/api/devices/search/:query', async (req, res) => { try { const devices = registry.searchDevices(req.params.query); res.json(createResponse(true, devices)); } catch (error) { res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Control device app.post('/api/devices/:id/control', async (req, res) => { try { const device = registry.getDevice(req.params.id); if (!device) { return res.status(404).json(createResponse(false, undefined, 'Device not found')); } const controlRequest: DeviceControlRequest = req.body; const controller = getDeviceController(device); // Execute control command await controller.control(controlRequest); // Get updated state const state = await controller.getState(); registry.updateDeviceState(device.id, state); registry.setDeviceOnlineStatus(device.id, true); res.json(createResponse(true, { device: device.name, action: controlRequest.action, state })); } catch (error) { registry.setDeviceOnlineStatus(req.params.id, false); res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Set device power app.post('/api/devices/:id/power', async (req, res) => { try { const device = registry.getDevice(req.params.id); if (!device) { return res.status(404).json(createResponse(false, undefined, 'Device not found')); } const { state } = req.body; if (!state || !['on', 'off'].includes(state)) { return res.status(400).json(createResponse(false, undefined, 'State must be "on" or "off"')); } const controller = getDeviceController(device); if (state === 'on') { await controller.turnOn(); } else { await controller.turnOff(); } const currentState = await controller.getState(); registry.updateDeviceState(device.id, currentState); registry.setDeviceOnlineStatus(device.id, true); res.json(createResponse(true, { device: device.name, state, currentState })); } catch (error) { registry.setDeviceOnlineStatus(req.params.id, false); res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Set device brightness app.post('/api/devices/:id/brightness', async (req, res) => { try { const device = registry.getDevice(req.params.id); if (!device) { return res.status(404).json(createResponse(false, undefined, 'Device not found')); } if (!device.capabilities.includes('brightness')) { return res.status(400).json(createResponse(false, undefined, 'Device does not support brightness control')); } const { level } = req.body; if (typeof level !== 'number' || level < 0 || level > 100) { return res.status(400).json(createResponse(false, undefined, 'Brightness level must be between 0 and 100')); } const controller = getDeviceController(device); await controller.setBrightness(level); const currentState = await controller.getState(); registry.updateDeviceState(device.id, currentState); registry.setDeviceOnlineStatus(device.id, true); res.json(createResponse(true, { device: device.name, brightness: level, currentState })); } catch (error) { registry.setDeviceOnlineStatus(req.params.id, false); res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Set device color app.post('/api/devices/:id/color', async (req, res) => { try { const device = registry.getDevice(req.params.id); if (!device) { return res.status(404).json(createResponse(false, undefined, 'Device not found')); } if (!device.capabilities.includes('color')) { return res.status(400).json(createResponse(false, undefined, 'Device does not support color control')); } const { r, g, b, brightness } = req.body; if (typeof r !== 'number' || typeof g !== 'number' || typeof b !== 'number' || r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) { return res.status(400).json(createResponse(false, undefined, 'RGB values must be between 0 and 255')); } if (brightness !== undefined && (typeof brightness !== 'number' || brightness < 0 || brightness > 100)) { return res.status(400).json(createResponse(false, undefined, 'Brightness must be between 0 and 100')); } const controller = getDeviceController(device); await controller.setRGBColor(r, g, b, brightness); const currentState = await controller.getState(); registry.updateDeviceState(device.id, currentState); registry.setDeviceOnlineStatus(device.id, true); res.json(createResponse(true, { device: device.name, color: { r, g, b }, brightness, currentState })); } catch (error) { registry.setDeviceOnlineStatus(req.params.id, false); res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Get registry statistics app.get('/api/registry/stats', (req, res) => { try { const stats = registry.getStats(); res.json(createResponse(true, stats)); } catch (error) { res.status(500).json(createResponse(false, undefined, error instanceof Error ? error.message : String(error))); } }); // Discovery endpoints (placeholder for now) app.post('/api/discovery/scan', async (req, res) => { // TODO: Implement network scanning res.json(createResponse(true, { message: 'Network scanning not yet implemented', count: 0, devices: [] })); }); // Scene endpoints (placeholder for now) app.get('/api/scenes', (req, res) => { res.json(createResponse(true, [ { id: 'work', name: 'Work Mode', description: 'Bright white lighting for productivity' }, { id: 'relax', name: 'Relax Mode', description: 'Warm dim lighting for relaxation' }, { id: 'movie', name: 'Movie Mode', description: 'Dark ambient lighting' }, { id: 'party', name: 'Party Mode', description: 'Colorful dynamic lighting' } ])); }); app.post('/api/scenes/:name/activate', (req, res) => { // TODO: Implement scene activation res.json(createResponse(true, { scene: req.params.name, message: 'Scene activation not yet implemented' })); }); // Error handling middleware app.use((error: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { console.error('Unhandled error:', error); res.status(500).json(createResponse(false, undefined, 'Internal server error')); }); // 404 handler app.use((req, res) => { res.status(404).json(createResponse(false, undefined, 'Endpoint not found')); }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`🏠 Smart Home API server running on port ${PORT}`); console.log(`📊 Loaded ${registry.getAllDevices().length} devices from registry`); console.log(`🔗 API available at http://localhost:${PORT}`); console.log(`❤️ Health check: http://localhost:${PORT}/health`); }); export default app;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vmm/homer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server