const express = require('express'); const { Client } = require('node-osc'); const http = require('http'); const { Server } = require('socket.io'); const { exec } = require('child_process'); const fs = require('fs'); const path = require('path'); const multer = require('multer'); const app = express(); app.use(express.json()); // Allow parsing JSON body app.use(express.static(path.join(__dirname, '../frontend/dist'))); const si = require('systeminformation'); // System Monitoring API app.get('/system/stats', async (req, res) => { try { const cpu = await si.currentLoad(); const mem = await si.mem(); const temp = await si.cpuTemperature(); res.json({ cpu: cpu.currentLoad.toFixed(1), mem: { total: (mem.total / 1024 / 1024).toFixed(0), used: (mem.used / 1024 / 1024).toFixed(0), free: (mem.free / 1024 / 1024).toFixed(0) }, temp: temp.main }); } catch (err) { res.status(500).json({ error: 'Failed to fetch system stats' }); } }); // Media Directory Configuration const MEDIA_DIR = process.env.MEDIA_DIR || '/home/pi/media'; if (!fs.existsSync(MEDIA_DIR)) { fs.mkdirSync(MEDIA_DIR, { recursive: true }); } // Multer Setup for File Uploads const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, MEDIA_DIR); }, filename: (req, file, cb) => { cb(null, file.originalname); } }); const upload = multer({ storage: storage }); // Media API Endpoints app.get('/media', (req, res) => { fs.readdir(MEDIA_DIR, (err, files) => { if (err) return res.status(500).json({ error: 'Failed to read media directory' }); res.json({ files }); }); }); app.post('/media/upload', upload.single('file'), (req, res) => { if (!req.file) return res.status(400).json({ error: 'No file uploaded' }); res.json({ status: 'uploaded', file: req.file.filename }); }); app.delete('/media/:filename', (req, res) => { const filePath = path.join(MEDIA_DIR, req.params.filename); fs.unlink(filePath, (err) => { if (err) return res.status(500).json({ error: 'Failed to delete file' }); res.json({ status: 'deleted' }); }); }); app.post('/media/assign', (req, res) => { const { surfaceIndex, filename } = req.body; oscClient.send('/ofxPiMapper/source/set', surfaceIndex, filename); res.json({ status: 'assigned', surfaceIndex, filename }); }); // Networking API app.post('/network/ap', (req, res) => { exec('bash scripts/switch-to-ap.sh', (err, stdout) => { if (err) return res.status(500).json({ error: err.message }); res.json({ status: 'switching to ap', output: stdout }); }); }); app.post('/network/client', (req, res) => { exec('bash scripts/switch-to-client.sh', (err, stdout) => { if (err) return res.status(500).json({ error: err.message }); res.json({ status: 'switching to client', output: stdout }); }); }); app.get('/network/scan', (req, res) => { // Mock scan for WiFi networks res.json({ networks: [ { ssid: 'Venue-WiFi', strength: -60, encrypted: true }, { ssid: 'Artist-Hotspot', strength: -45, encrypted: true }, { ssid: 'Free-Internet-Coffee', strength: -80, encrypted: false } ] }); }); // Process management for ofxPiMapper app.post('/mapper/start', (req, res) => { exec('ofxPiMapper -f', (error) => { if (error) { console.error(`Error starting ofxPiMapper: ${error.message}`); return res.status(500).json({ error: 'Failed to start mapper' }); } }); res.json({ status: 'starting' }); }); app.post('/mapper/stop', (req, res) => { exec('pkill ofxPiMapper', (error) => { if (error) { console.error(`Error stopping ofxPiMapper: ${error.message}`); return res.status(500).json({ error: 'Failed to stop mapper' }); } res.json({ status: 'stopped' }); }); }); app.post('/mapper/restart', (req, res) => { exec('pkill ofxPiMapper', () => { setTimeout(() => { exec('ofxPiMapper -f'); res.json({ status: 'restarting' }); }, 1000); }); }); const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*", } }); // OSC Client for ofxPiMapper const oscClient = new Client('127.0.0.1', 9999); io.on('connection', (socket) => { console.log('Client connected:', socket.id); // Vertex Movement: Sends (surfaceIndex, vertexIndex, x, y) socket.on('vertex:move', (data) => { const { surfaceIndex, vertexIndex, x, y } = data; oscClient.send('/ofxPiMapper/vertex/move', surfaceIndex, vertexIndex, x, y); }); // Surface Selection socket.on('surface:select', (data) => { const { surfaceIndex } = data; oscClient.send('/ofxPiMapper/surface/select', surfaceIndex); }); // Add Surface (Quad or Triangle) socket.on('surface:add', (data) => { const { type } = data; // 'quad' or 'triangle' oscClient.send('/ofxPiMapper/surface/add', type); }); // Delete Surface socket.on('surface:delete', (data) => { const { surfaceIndex } = data; oscClient.send('/ofxPiMapper/surface/delete', surfaceIndex); }); socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); }); }); const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { res.send('MPVJ Backend is running.'); }); server.listen(PORT, () => { console.log(`Backend listening on port ${PORT}`); });