Files
mapper/backend/index.js
2026-03-10 20:48:09 +00:00

189 lines
5.2 KiB
JavaScript

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}`);
});