diff --git a/mock-server/.gitignore b/mock-server/.gitignore new file mode 100644 index 0000000..3f00ede --- /dev/null +++ b/mock-server/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +*.log +.DS_Store +.env diff --git a/mock-server/README.md b/mock-server/README.md new file mode 100644 index 0000000..1ad2d98 --- /dev/null +++ b/mock-server/README.md @@ -0,0 +1,179 @@ +# AquaMaster Mock Server + +Mock ESP32 Server und MQTT-Broker für lokales Testing ohne Hardware. + +## Übersicht + +Dieses Projekt simuliert: +- **MQTT-Broker** (Port 1883 TCP, Port 9001 WebSocket) - Lokaler MQTT-Broker für Kommunikation +- **Mock ESP32 Server** (Port 80) - Simuliert alle ESP32 API-Endpunkte und WebSocket +- **Web Debug UI** - Browser-basierte Oberfläche zum Testen von API und MQTT + +## Voraussetzungen + +- Node.js 16+ (LTS empfohlen) +- npm + +## Installation + +```bash +cd mock-server +npm install +``` + +## Verwendung + +### Option 1: Beide Server zusammen starten + +```bash +npm start +# oder +node start_all.js +``` + +### Option 2: Server einzeln starten + +**Terminal 1 - MQTT Broker:** +```bash +npm run mqtt +# oder +node mqtt_broker.js +``` + +**Terminal 2 - Mock ESP32 Server:** +```bash +npm run server +# oder +node mock_esp32_server.js +``` + +### Web Debug UI öffnen + +Nach dem Start der Server: +1. Öffne einen Browser +2. Navigiere zu: `http://localhost:80` +3. Die Debug-Oberfläche sollte sichtbar sein + +## Features + +### MQTT Broker +- Läuft auf Port 1883 (TCP) und Port 9001 (WebSocket) +- Unterstützt alle relevanten Topics: + - `aquacross/button/#` + - `aquacross/button/rfid/#` + - `aquacross/battery/#` + - `heartbeat/alive/#` + - `aquacross/competition/#` + - `sync/time` + - `aquacross/lanes/#` +- Loggt alle Nachrichten für Debugging + +### Mock ESP32 Server +- Simuliert alle API-Endpunkte aus der ESP32-Firmware +- WebSocket-Support für Live-Updates +- MQTT-Client, der sich mit dem Broker verbindet +- Timer-Logik (Individual/Wettkampf-Modi) +- Button-Konfigurationen und Learning-Mode + +### Web Debug UI +- **API Testing Tab**: Teste alle API-Endpunkte +- **MQTT Testing Tab**: Publish/Subscribe MQTT-Nachrichten +- **Debug Endpoints Tab**: Direkte Timer-Kontrolle + +## API-Endpunkte + +Alle Endpunkte sind unter `http://localhost:80/api/...` verfügbar: + +- `GET /api/data` - Timer-Daten abrufen +- `POST /api/reset-best` - Beste Zeiten zurücksetzen +- `POST /api/unlearn-button` - Button-Zuordnungen löschen +- `GET /api/debug/start1` - Lane 1 starten (Debug) +- `GET /api/debug/stop1` - Lane 1 stoppen (Debug) +- `GET /api/debug/start2` - Lane 2 starten (Debug) +- `GET /api/debug/stop2` - Lane 2 stoppen (Debug) +- ... und viele mehr (siehe `../API.md`) + +## MQTT Topics + +### Button Topics +- `aquacross/button/{MAC}` - Button-Press Nachrichten + ```json + {"type": 2, "timestamp": 1234567890} + ``` + - `type: 2` = Start-Button + - `type: 1` = Stop-Button + +### RFID Topics +- `aquacross/button/rfid/{MAC}` - RFID-Read Nachrichten + ```json + {"uid": "TEST123456"} + ``` + +### Battery Topics +- `aquacross/battery/{MAC}` - Batteriestand + ```json + {"voltage": 3600} + ``` + +### Heartbeat Topics +- `heartbeat/alive/{MAC}` - Heartbeat-Nachrichten + ```json + {"timestamp": 1234567890} + ``` + +### Competition Topics +- `aquacross/competition/toMaster` - Wettkampf-Start + ``` + "start" + ``` + +### Time Sync +- `sync/time` - Zeit-Synchronisation (vom Server alle 5 Sekunden) + +## Troubleshooting + +### Port bereits belegt +Falls Port 80 oder 1883 bereits belegt sind: +- Windows: Port 80 benötigt Admin-Rechte, verwende einen anderen Port +- Linux/Mac: Port 80 benötigt sudo, verwende einen anderen Port + +Um Ports zu ändern, editiere: +- `mqtt_broker.js` - Zeile mit `const port = 1883;` +- `mock_esp32_server.js` - Zeile mit `const PORT = 80;` + +### MQTT-Verbindung fehlgeschlagen +- Stelle sicher, dass der MQTT-Broker läuft +- Prüfe, ob Port 1883 (TCP) oder 9001 (WebSocket) erreichbar ist +- Browser benötigen WebSocket-Verbindung (Port 9001) + +### WebSocket-Verbindung fehlgeschlagen +- Stelle sicher, dass der Mock ESP32 Server läuft +- Prüfe Browser-Konsole auf Fehler +- Socket.io sollte automatisch geladen werden + +## Projektstruktur + +``` +mock-server/ +├── package.json # Node.js Dependencies +├── README.md # Diese Datei +├── .gitignore # Git ignore +├── mqtt_broker.js # MQTT-Broker +├── mock_esp32_server.js # Mock ESP32 Server +├── start_all.js # Startet beide Server +└── debug_server/ + ├── index.html # Web Debug UI + ├── debug.js # JavaScript-Logik + └── debug.css # Styling +``` + +## Hinweise + +- Der Mock-Server speichert keinen persistenten State (alles im Speicher) +- Nach Neustart sind alle Einstellungen zurückgesetzt +- Für Produktionstests sollte der echte ESP32 verwendet werden +- Dieser Mock-Server ist nur für Entwicklung und Testing gedacht + +## Lizenz + +MIT diff --git a/mock-server/debug_server/debug.css b/mock-server/debug_server/debug.css new file mode 100644 index 0000000..4615ce5 --- /dev/null +++ b/mock-server/debug_server/debug.css @@ -0,0 +1,273 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: #f5f5f5; + color: #333; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +header { + background: #2c3e50; + color: white; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; +} + +header h1 { + margin-bottom: 15px; +} + +.status-bar { + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.status-indicator { + padding: 5px 10px; + background: rgba(255, 255, 255, 0.2); + border-radius: 4px; + font-size: 0.9em; +} + +.status-indicator.connected { + background: #27ae60; +} + +.status-indicator.disconnected { + background: #e74c3c; +} + +.tabs { + display: flex; + gap: 10px; + margin-bottom: 20px; + border-bottom: 2px solid #ddd; +} + +.tab-button { + padding: 12px 24px; + background: transparent; + border: none; + border-bottom: 3px solid transparent; + cursor: pointer; + font-size: 16px; + color: #666; + transition: all 0.3s; +} + +.tab-button:hover { + color: #2c3e50; + background: #f0f0f0; +} + +.tab-button.active { + color: #2c3e50; + border-bottom-color: #3498db; + font-weight: bold; +} + +.tab-content { + display: none; + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.tab-content.active { + display: block; +} + +.section { + margin-bottom: 30px; +} + +.section h2 { + margin-bottom: 15px; + color: #2c3e50; + border-bottom: 2px solid #ecf0f1; + padding-bottom: 10px; +} + +.section h3 { + margin-top: 20px; + margin-bottom: 10px; + color: #34495e; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #555; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + font-family: inherit; +} + +.form-group textarea { + resize: vertical; + font-family: 'Courier New', monospace; +} + +.btn { + padding: 10px 20px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.3s; + margin-right: 10px; + margin-bottom: 10px; +} + +.btn-primary { + background: #3498db; + color: white; +} + +.btn-primary:hover { + background: #2980b9; +} + +.btn-secondary { + background: #95a5a6; + color: white; +} + +.btn-secondary:hover { + background: #7f8c8d; +} + +.btn-small { + padding: 5px 10px; + font-size: 12px; +} + +.button-group { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 15px; +} + +.response-section { + margin-top: 20px; + padding: 15px; + background: #f8f9fa; + border-radius: 4px; +} + +.response-section pre { + background: #2c3e50; + color: #ecf0f1; + padding: 15px; + border-radius: 4px; + overflow-x: auto; + font-size: 13px; + max-height: 400px; + overflow-y: auto; +} + +.quick-actions { + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #ddd; +} + +.messages-section { + margin-top: 30px; +} + +.messages-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.messages-container { + background: #2c3e50; + color: #ecf0f1; + padding: 15px; + border-radius: 4px; + max-height: 500px; + overflow-y: auto; + font-family: 'Courier New', monospace; + font-size: 12px; +} + +.message-item { + padding: 8px; + margin-bottom: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + border-left: 3px solid #3498db; +} + +.message-item .timestamp { + color: #95a5a6; + font-size: 11px; + margin-bottom: 4px; +} + +.message-item .topic { + color: #3498db; + font-weight: bold; + margin-bottom: 4px; +} + +.message-item .payload { + color: #ecf0f1; + word-break: break-all; +} + +@media (max-width: 768px) { + .container { + padding: 10px; + } + + .tabs { + flex-wrap: wrap; + } + + .tab-button { + flex: 1; + min-width: 100px; + } + + .button-group { + flex-direction: column; + } + + .btn { + width: 100%; + margin-right: 0; + } +} diff --git a/mock-server/debug_server/debug.js b/mock-server/debug_server/debug.js new file mode 100644 index 0000000..4f2e80e --- /dev/null +++ b/mock-server/debug_server/debug.js @@ -0,0 +1,378 @@ +// Configuration +const API_BASE_URL = 'http://localhost:80'; +const MQTT_BROKER_URL = 'ws://localhost:9001/mqtt'; // WebSocket port for MQTT +const WS_URL = 'http://localhost:80'; + +// State +let mqttClient = null; +let wsClient = null; +let subscribedTopics = new Set(); + +// Initialize +document.addEventListener('DOMContentLoaded', init); + +function init() { + setupTabs(); + setupAPI(); + setupMQTT(); + setupWebSocket(); + setupDebug(); + setupQuickActions(); +} + +// Tab Management +function setupTabs() { + const tabButtons = document.querySelectorAll('.tab-button'); + const tabContents = document.querySelectorAll('.tab-content'); + + tabButtons.forEach(button => { + button.addEventListener('click', () => { + const tabName = button.dataset.tab; + + // Remove active class from all + tabButtons.forEach(btn => btn.classList.remove('active')); + tabContents.forEach(content => content.classList.remove('active')); + + // Add active class to selected + button.classList.add('active'); + document.getElementById(`${tabName}-tab`).classList.add('active'); + }); + }); +} + +// API Setup +function setupAPI() { + const endpointSelect = document.getElementById('api-endpoint'); + const paramsTextarea = document.getElementById('api-params'); + const sendBtn = document.getElementById('api-send-btn'); + const responsePre = document.getElementById('api-response'); + + sendBtn.addEventListener('click', async () => { + const endpoint = endpointSelect.value; + const [method, path] = endpoint.split(' '); + const params = paramsTextarea.value.trim(); + + try { + let options = { + method: method, + headers: {} + }; + + if (method === 'POST' && params) { + // Try to parse as JSON, otherwise use as form data + try { + const jsonData = JSON.parse(params); + options.headers['Content-Type'] = 'application/json'; + options.body = JSON.stringify(jsonData); + } catch { + // Not JSON, use form data + const formData = new URLSearchParams(); + const pairs = params.split('&'); + pairs.forEach(pair => { + const [key, value] = pair.split('='); + if (key && value) { + formData.append(key, decodeURIComponent(value)); + } + }); + options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; + options.body = formData.toString(); + } + } + + const response = await fetch(`${API_BASE_URL}${path}`, options); + const text = await response.text(); + + let formatted; + try { + formatted = JSON.stringify(JSON.parse(text), null, 2); + } catch { + formatted = text; + } + + responsePre.textContent = formatted; + + } catch (error) { + responsePre.textContent = `Error: ${error.message}`; + } + }); +} + +// MQTT Setup +function setupMQTT() { + const topicInput = document.getElementById('mqtt-topic'); + const payloadTextarea = document.getElementById('mqtt-payload'); + const publishBtn = document.getElementById('mqtt-publish-btn'); + const subscribeBtn = document.getElementById('mqtt-subscribe-btn'); + const unsubscribeBtn = document.getElementById('mqtt-unsubscribe-btn'); + const subscribeTopicInput = document.getElementById('mqtt-subscribe-topic'); + const messagesContainer = document.getElementById('mqtt-messages'); + const clearMessagesBtn = document.getElementById('clear-messages-btn'); + + // Connect to MQTT broker + try { + mqttClient = mqtt.connect(MQTT_BROKER_URL, { + clientId: 'debug-ui-' + Math.random().toString(16).substr(2, 8) + }); + + mqttClient.on('connect', () => { + console.log('MQTT connected'); + updateStatus('mqtt-status', 'MQTT: Connected', true); + }); + + mqttClient.on('error', (error) => { + console.error('MQTT error:', error); + updateStatus('mqtt-status', 'MQTT: Error', false); + }); + + mqttClient.on('close', () => { + console.log('MQTT disconnected'); + updateStatus('mqtt-status', 'MQTT: Disconnected', false); + }); + + mqttClient.on('message', (topic, message) => { + addMessage(topic, message.toString()); + }); + } catch (error) { + console.error('Failed to connect to MQTT:', error); + updateStatus('mqtt-status', 'MQTT: Connection Failed', false); + } + + publishBtn.addEventListener('click', () => { + const topic = topicInput.value.trim(); + let payload = payloadTextarea.value.trim(); + + if (!topic) { + alert('Please enter a topic'); + return; + } + + // Try to parse as JSON, otherwise use as-is + try { + const jsonData = JSON.parse(payload); + payload = JSON.stringify(jsonData); + } catch { + // Not JSON, use as-is + } + + if (mqttClient && mqttClient.connected) { + mqttClient.publish(topic, payload, (err) => { + if (err) { + console.error('Publish error:', err); + alert('Failed to publish: ' + err.message); + } else { + console.log('Published to', topic); + } + }); + } else { + alert('MQTT not connected'); + } + }); + + subscribeBtn.addEventListener('click', () => { + const topic = subscribeTopicInput.value.trim(); + if (!topic) { + alert('Please enter a topic pattern'); + return; + } + + if (mqttClient && mqttClient.connected) { + mqttClient.subscribe(topic, (err) => { + if (err) { + console.error('Subscribe error:', err); + alert('Failed to subscribe: ' + err.message); + } else { + subscribedTopics.add(topic); + console.log('Subscribed to', topic); + } + }); + } else { + alert('MQTT not connected'); + } + }); + + unsubscribeBtn.addEventListener('click', () => { + if (mqttClient && mqttClient.connected) { + subscribedTopics.forEach(topic => { + mqttClient.unsubscribe(topic); + }); + subscribedTopics.clear(); + console.log('Unsubscribed from all topics'); + } + }); + + clearMessagesBtn.addEventListener('click', () => { + messagesContainer.innerHTML = ''; + }); +} + +function addMessage(topic, payload) { + const messagesContainer = document.getElementById('mqtt-messages'); + const messageDiv = document.createElement('div'); + messageDiv.className = 'message-item'; + + const timestamp = new Date().toLocaleTimeString(); + let formattedPayload = payload; + try { + formattedPayload = JSON.stringify(JSON.parse(payload), null, 2); + } catch {} + + messageDiv.innerHTML = ` +
${timestamp}
+
${topic}
+
${formattedPayload}
+ `; + + messagesContainer.appendChild(messageDiv); + + // Auto-scroll + if (document.getElementById('auto-scroll').checked) { + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } +} + +// WebSocket Setup +function setupWebSocket() { + if (typeof io !== 'undefined') { + try { + wsClient = io(SOCKET_IO_URL); + + wsClient.on('connect', () => { + console.log('WebSocket connected'); + updateStatus('ws-status', 'WebSocket: Connected', true); + }); + + wsClient.on('disconnect', () => { + console.log('WebSocket disconnected'); + updateStatus('ws-status', 'WebSocket: Disconnected', false); + }); + + wsClient.on('update', (data) => { + console.log('WebSocket update:', data); + // Could display in a separate section + }); + } catch (error) { + console.error('Failed to connect WebSocket:', error); + updateStatus('ws-status', 'WebSocket: Error', false); + } + } else { + console.warn('Socket.io not loaded'); + updateStatus('ws-status', 'WebSocket: Library Not Loaded', false); + } +} + +// Debug Endpoints Setup +function setupDebug() { + const debugButtons = document.querySelectorAll('[data-debug]'); + const responsePre = document.getElementById('debug-response'); + + debugButtons.forEach(button => { + button.addEventListener('click', async () => { + const action = button.dataset.debug; + const endpoint = `/api/debug/${action}`; + + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`); + const text = await response.text(); + responsePre.textContent = text; + } catch (error) { + responsePre.textContent = `Error: ${error.message}`; + } + }); + }); +} + +// Quick Actions Setup +function setupQuickActions() { + const quickActionButtons = document.querySelectorAll('[data-action]'); + + quickActionButtons.forEach(button => { + button.addEventListener('click', () => { + const action = button.dataset.action; + const topicInput = document.getElementById('mqtt-topic'); + const payloadTextarea = document.getElementById('mqtt-payload'); + + switch (action) { + case 'button-start1': + topicInput.value = 'aquacross/button/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + type: 2, + timestamp: Date.now() + }, null, 2); + break; + case 'button-stop1': + topicInput.value = 'aquacross/button/00:00:00:00:00:03'; + payloadTextarea.value = JSON.stringify({ + type: 1, + timestamp: Date.now() + }, null, 2); + break; + case 'button-start2': + topicInput.value = 'aquacross/button/00:00:00:00:00:02'; + payloadTextarea.value = JSON.stringify({ + type: 2, + timestamp: Date.now() + }, null, 2); + break; + case 'button-stop2': + topicInput.value = 'aquacross/button/00:00:00:00:00:04'; + payloadTextarea.value = JSON.stringify({ + type: 1, + timestamp: Date.now() + }, null, 2); + break; + case 'rfid-read': + topicInput.value = 'aquacross/button/rfid/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + uid: 'TEST123456' + }, null, 2); + break; + case 'battery-update': + topicInput.value = 'aquacross/battery/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + voltage: 3600 + }, null, 2); + break; + case 'heartbeat': + topicInput.value = 'heartbeat/alive/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + timestamp: Date.now() + }, null, 2); + break; + case 'button-available': + topicInput.value = 'aquacross/button/status/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + available: true, + sleep: false, + timestamp: Date.now() + }, null, 2); + break; + case 'button-sleep': + topicInput.value = 'aquacross/button/status/00:00:00:00:00:01'; + payloadTextarea.value = JSON.stringify({ + available: false, + sleep: true, + timestamp: Date.now() + }, null, 2); + break; + } + + // Auto-publish + document.getElementById('mqtt-publish-btn').click(); + }); + }); +} + +// Helper Functions +function updateStatus(elementId, text, connected) { + const element = document.getElementById(elementId); + element.textContent = text; + element.classList.remove('connected', 'disconnected'); + element.classList.add(connected ? 'connected' : 'disconnected'); +} + +// Initialize on load +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); +} else { + init(); +} diff --git a/mock-server/debug_server/index.html b/mock-server/debug_server/index.html new file mode 100644 index 0000000..d33444b --- /dev/null +++ b/mock-server/debug_server/index.html @@ -0,0 +1,139 @@ + + + + + + AquaMaster Debug Server + + + +
+
+

AquaMaster Debug Server

+
+ MQTT: Disconnected + WebSocket: Disconnected + API: Ready +
+
+ + + + +
+
+

API Endpoint Testing

+
+ + +
+
+ + +
+ +
+

Response:

+

+                
+
+
+ + +
+
+

MQTT Publish

+
+ + +
+
+ + +
+ + +
+

Quick Actions:

+
+ + + + + + + + + +
+
+
+ +
+

MQTT Subscribe

+
+ + +
+ + + +
+

Received Messages:

+
+ + +
+
+
+
+
+ + +
+
+

Debug Endpoints

+

Direct access to debug endpoints for timer control:

+
+ + + + +
+
+

Last Response:

+

+                
+
+
+
+ + + + + + diff --git a/mock-server/mock_esp32_server.js b/mock-server/mock_esp32_server.js new file mode 100644 index 0000000..69c8b3b --- /dev/null +++ b/mock-server/mock_esp32_server.js @@ -0,0 +1,718 @@ +const express = require('express'); +const http = require('http'); +const socketIo = require('socket.io'); +const mqtt = require('mqtt'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const path = require('path'); + +const app = express(); +const server = http.createServer(app); +const io = socketIo(server, { + cors: { + origin: "*", + methods: ["GET", "POST"] + } +}); + +const PORT = 80; +const MQTT_BROKER = 'mqtt://localhost:1883'; + +// Middleware +app.use(cors()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); +app.use(express.static(path.join(__dirname, 'debug_server'))); + +// State - simuliert ESP32 Datenstrukturen +const state = { + timerData1: { + startTime: 0, + localStartTime: 0, + finishedSince: 0, + endTime: 0, + bestTime: 0, + isRunning: false, + isReady: true, + isArmed: false, + RFIDUID: "" + }, + timerData2: { + startTime: 0, + localStartTime: 0, + finishedSince: 0, + endTime: 0, + bestTime: 0, + isRunning: false, + isReady: true, + isArmed: false, + RFIDUID: "" + }, + buttonConfigs: { + start1: { mac: [0, 0, 0, 0, 0, 0], isAssigned: false, voltage: 0, lastHeartbeat: 0, heartbeatActive: false }, + stop1: { mac: [0, 0, 0, 0, 0, 0], isAssigned: false, voltage: 0, lastHeartbeat: 0, heartbeatActive: false }, + start2: { mac: [0, 0, 0, 0, 0, 0], isAssigned: false, voltage: 0, lastHeartbeat: 0, heartbeatActive: false }, + stop2: { mac: [0, 0, 0, 0, 0, 0], isAssigned: false, voltage: 0, lastHeartbeat: 0, heartbeatActive: false } + }, + learningMode: false, + learningStep: 0, + maxTimeBeforeReset: 300000, + maxTimeDisplay: 20000, + minTimeForLeaderboard: 5000, + masterlocation: "", + gamemode: 0, // 0=Individual, 1=Wettkampf + startCompetition: false, + laneConfigType: 0, + lane1DifficultyType: 0, + lane2DifficultyType: 0, + localTimes: [], + wifi: { + ssid: "", + password: "" + }, + start1FoundLocally: false, + start2FoundLocally: false, + start1UID: "", + start2UID: "" +}; + +// Helper: millis() - simuliert Arduino millis() +function millis() { + return Date.now(); +} + +// Helper: getTimerDataJSON() - simuliert getTimerDataJSON() +function getTimerDataJSON() { + const currentTime = millis(); + const data = {}; + + // Lane 1 + if (state.timerData1.isRunning) { + data.time1 = (currentTime - state.timerData1.localStartTime) / 1000.0; + data.status1 = "running"; + } else if (state.timerData1.endTime > 0) { + data.time1 = (state.timerData1.endTime - state.timerData1.startTime) / 1000.0; + data.status1 = "finished"; + } else if (state.timerData1.isArmed) { + data.time1 = 0; + data.status1 = "armed"; + } else { + data.time1 = 0; + data.status1 = "ready"; + } + + // Lane 2 + if (state.timerData2.isRunning) { + data.time2 = (currentTime - state.timerData2.localStartTime) / 1000.0; + data.status2 = "running"; + } else if (state.timerData2.endTime > 0) { + data.time2 = (state.timerData2.endTime - state.timerData2.startTime) / 1000.0; + data.status2 = "finished"; + } else if (state.timerData2.isArmed) { + data.time2 = 0; + data.status2 = "armed"; + } else { + data.time2 = 0; + data.status2 = "ready"; + } + + // Best times + data.best1 = state.timerData1.bestTime / 1000.0; + data.best2 = state.timerData2.bestTime / 1000.0; + + // Learning mode + data.learningMode = state.learningMode; + if (state.learningMode) { + const buttons = ["Start Bahn 1", "Stop Bahn 1", "Start Bahn 2", "Stop Bahn 2"]; + data.learningButton = buttons[state.learningStep]; + } + + return JSON.stringify(data); +} + +// Timer-Logik: IndividualMode +function individualMode(action, press, lane, timestamp = 0) { + const ts = timestamp > 0 ? timestamp : millis(); + + if (action === "start" && press === 2 && lane === 1) { + if (!state.timerData1.isRunning && state.timerData1.isReady) { + state.timerData1.isReady = false; + state.timerData1.startTime = ts; + state.timerData1.localStartTime = millis(); + state.timerData1.isRunning = true; + state.timerData1.endTime = 0; + state.timerData1.isArmed = false; + publishLaneStatus(1, "running"); + console.log("Bahn 1 gestartet"); + } + } + + if (action === "stop" && press === 1 && lane === 1) { + if (state.timerData1.isRunning) { + state.timerData1.endTime = ts; + state.timerData1.finishedSince = millis(); + state.timerData1.isRunning = false; + const currentTime = state.timerData1.endTime - state.timerData1.startTime; + + if (state.timerData1.bestTime === 0 || currentTime < state.timerData1.bestTime) { + state.timerData1.bestTime = currentTime; + } + publishLaneStatus(1, "stopped"); + console.log(`Bahn 1 gestoppt - Zeit: ${(currentTime / 1000.0).toFixed(2)}s`); + } + } + + if (action === "start" && press === 2 && lane === 2) { + if (!state.timerData2.isRunning && state.timerData2.isReady) { + state.timerData2.isReady = false; + state.timerData2.startTime = ts; + state.timerData2.localStartTime = millis(); + state.timerData2.isRunning = true; + state.timerData2.endTime = 0; + state.timerData2.isArmed = false; + publishLaneStatus(2, "running"); + console.log("Bahn 2 gestartet"); + } + } + + if (action === "stop" && press === 1 && lane === 2) { + if (state.timerData2.isRunning) { + state.timerData2.endTime = ts; + state.timerData2.finishedSince = millis(); + state.timerData2.isRunning = false; + const currentTime = state.timerData2.endTime - state.timerData2.startTime; + + if (state.timerData2.bestTime === 0 || currentTime < state.timerData2.bestTime) { + state.timerData2.bestTime = currentTime; + } + publishLaneStatus(2, "stopped"); + console.log(`Bahn 2 gestoppt - Zeit: ${(currentTime / 1000.0).toFixed(2)}s`); + } + } +} + +// Helper: publishLaneStatus +function publishLaneStatus(lane, status) { + if (mqttClient && mqttClient.connected) { + const topic = `aquacross/lanes/lane${lane}`; + const message = JSON.stringify({ lane, status }); + mqttClient.publish(topic, message); + } +} + +// Helper: pushUpdateToFrontend +function pushUpdateToFrontend(message) { + io.emit('update', message); +} + +// MQTT Client Setup +let mqttClient = null; +let mqttReconnectInterval = null; + +function connectMQTT() { + // Don't reconnect if already connected or connecting + if (mqttClient && (mqttClient.connected || mqttClient.connecting)) { + return; + } + + // Clear any existing reconnect interval + if (mqttReconnectInterval) { + clearInterval(mqttReconnectInterval); + mqttReconnectInterval = null; + } + + // Close existing client if any + if (mqttClient) { + mqttClient.end(true); + } + + console.log('[MQTT] Attempting to connect to broker at', MQTT_BROKER); + mqttClient = mqtt.connect(MQTT_BROKER, { + reconnectPeriod: 5000, + connectTimeout: 10000, + clientId: 'mock-esp32-' + Math.random().toString(16).substr(2, 8) + }); + + mqttClient.on('connect', () => { + console.log('[MQTT] Connected to broker'); + + // Subscribe to all relevant topics + mqttClient.subscribe('aquacross/button/#', (err) => { + if (!err) console.log('[MQTT] Subscribed to aquacross/button/#'); + }); + mqttClient.subscribe('aquacross/button/rfid/#', (err) => { + if (!err) console.log('[MQTT] Subscribed to aquacross/button/rfid/#'); + }); + mqttClient.subscribe('aquacross/battery/#', (err) => { + if (!err) console.log('[MQTT] Subscribed to aquacross/battery/#'); + }); + mqttClient.subscribe('heartbeat/alive/#', (err) => { + if (!err) console.log('[MQTT] Subscribed to heartbeat/alive/#'); + }); + mqttClient.subscribe('aquacross/competition/toMaster', (err) => { + if (!err) console.log('[MQTT] Subscribed to aquacross/competition/toMaster'); + }); + mqttClient.subscribe('aquacross/button/status/#', (err) => { + if (!err) console.log('[MQTT] Subscribed to aquacross/button/status/#'); + }); + }); + + mqttClient.on('message', (topic, message) => { + const payload = message.toString(); + console.log(`[MQTT] Received on ${topic}: ${payload}`); + + // Handle different topic types + if (topic.startsWith('aquacross/button/rfid/')) { + handleRFIDTopic(topic, payload); + } else if (topic.startsWith('aquacross/button/status/')) { + handleButtonStatusTopic(topic, payload); + } else if (topic.startsWith('aquacross/button/')) { + handleButtonTopic(topic, payload); + } else if (topic.startsWith('aquacross/battery/')) { + handleBatteryTopic(topic, payload); + } else if (topic.startsWith('heartbeat/alive/')) { + handleHeartbeatTopic(topic, payload); + } else if (topic === 'aquacross/competition/toMaster') { + if (payload === 'start') { + state.startCompetition = true; + runCompetition(); + } + } + }); + + mqttClient.on('error', (err) => { + console.error('[MQTT] Error:', err.message || err); + if (err.code === 'ECONNREFUSED') { + console.log('[MQTT] Broker not available at', MQTT_BROKER, '- will retry automatically'); + } + }); + + mqttClient.on('close', () => { + console.log('[MQTT] Connection closed'); + }); + + mqttClient.on('offline', () => { + console.log('[MQTT] Client offline, will reconnect automatically...'); + }); + + mqttClient.on('reconnect', () => { + console.log('[MQTT] Reconnecting to broker...'); + }); +} + +// MQTT Topic Handlers +function handleButtonTopic(topic, payload) { + try { + const buttonId = topic.replace('aquacross/button/', ''); + const data = JSON.parse(payload); + const pressType = data.type || 0; + const timestamp = data.timestamp || millis(); + + console.log(`Button Press: ${buttonId}, Type: ${pressType}, Timestamp: ${timestamp}`); + + // Simulate button assignment check (simplified) + // In real implementation, would check MAC addresses + if (state.learningMode) { + // Handle learning mode + return; + } + + // Trigger action based on button (simplified - would check MAC in real implementation) + if (pressType === 2) { + // Start button + if (buttonId.includes('start1') || buttonId.includes('00:00:00:00:00:01')) { + individualMode("start", 2, 1, timestamp); + } else if (buttonId.includes('start2') || buttonId.includes('00:00:00:00:00:02')) { + individualMode("start", 2, 2, timestamp); + } + } else if (pressType === 1) { + // Stop button + if (buttonId.includes('stop1') || buttonId.includes('00:00:00:00:00:03')) { + individualMode("stop", 1, 1, timestamp); + } else if (buttonId.includes('stop2') || buttonId.includes('00:00:00:00:00:04')) { + individualMode("stop", 1, 2, timestamp); + } + } + } catch (err) { + console.error('Error handling button topic:', err); + } +} + +function handleRFIDTopic(topic, payload) { + try { + const buttonId = topic.replace('aquacross/button/rfid/', ''); + const data = JSON.parse(payload); + const uid = data.uid || ''; + + console.log(`RFID Read: ${buttonId}, UID: ${uid}`); + + // Send to frontend + const message = JSON.stringify({ + name: uid, + lane: buttonId.includes('start1') ? 'start1' : 'start2' + }); + pushUpdateToFrontend(message); + } catch (err) { + console.error('Error handling RFID topic:', err); + } +} + +function handleBatteryTopic(topic, payload) { + try { + const buttonId = topic.replace('aquacross/battery/', ''); + const data = JSON.parse(payload); + const voltage = data.voltage || 0; + + console.log(`Battery: ${buttonId}, Voltage: ${voltage}`); + + // Update button config if known + // Send to frontend + const message = JSON.stringify({ + button: buttonId, + mac: buttonId, + batteryLevel: Math.round((voltage - 3200) / 50) // Simple calculation + }); + pushUpdateToFrontend(message); + } catch (err) { + console.error('Error handling battery topic:', err); + } +} + +function handleHeartbeatTopic(topic, payload) { + try { + const buttonId = topic.replace('heartbeat/alive/', ''); + console.log(`Heartbeat: ${buttonId}`); + + // Update button heartbeat + // Send to frontend + const message = JSON.stringify({ + button: buttonId, + mac: buttonId, + active: true + }); + pushUpdateToFrontend(message); + } catch (err) { + console.error('Error handling heartbeat topic:', err); + } +} + +function handleButtonStatusTopic(topic, payload) { + try { + const buttonId = topic.replace('aquacross/button/status/', ''); + const data = JSON.parse(payload); + const available = data.available !== false; + const sleep = data.sleep === true; + + console.log(`Button Status: ${buttonId}, Available: ${available}, Sleep: ${sleep}`); + + // Send to frontend + const message = JSON.stringify({ + button: buttonId, + mac: buttonId, + available: available, + sleep: sleep, + timestamp: data.timestamp || Date.now() + }); + pushUpdateToFrontend(message); + } catch (err) { + console.error('Error handling button status topic:', err); + } +} + +function runCompetition() { + if (state.timerData1.isArmed && state.timerData2.isArmed && state.startCompetition) { + const startNow = millis(); + + state.timerData1.isReady = false; + state.timerData1.startTime = startNow; + state.timerData1.localStartTime = millis(); + state.timerData1.isRunning = true; + state.timerData1.endTime = 0; + state.timerData1.isArmed = false; + publishLaneStatus(1, "running"); + + state.timerData2.isReady = false; + state.timerData2.startTime = startNow; + state.timerData2.localStartTime = millis(); + state.timerData2.isRunning = true; + state.timerData2.endTime = 0; + state.timerData2.isArmed = false; + publishLaneStatus(2, "running"); + + console.log("Competition started"); + } +} + +// API Routes +app.get('/api/data', (req, res) => { + res.json(JSON.parse(getTimerDataJSON())); +}); + +app.post('/api/reset-best', (req, res) => { + state.timerData1.bestTime = 0; + state.timerData2.bestTime = 0; + state.localTimes = []; + res.json({ success: true }); +}); + +app.post('/api/unlearn-button', (req, res) => { + state.buttonConfigs.start1.isAssigned = false; + state.buttonConfigs.stop1.isAssigned = false; + state.buttonConfigs.start2.isAssigned = false; + state.buttonConfigs.stop2.isAssigned = false; + res.json({ success: true }); +}); + +app.post('/api/set-max-time', (req, res) => { + if (req.body.maxTime) { + state.maxTimeBeforeReset = parseInt(req.body.maxTime) * 1000; + } + if (req.body.maxTimeDisplay) { + state.maxTimeDisplay = parseInt(req.body.maxTimeDisplay) * 1000; + } + if (req.body.minTimeForLeaderboard) { + state.minTimeForLeaderboard = parseInt(req.body.minTimeForLeaderboard) * 1000; + } + res.json({ success: true }); +}); + +app.get('/api/get-settings', (req, res) => { + res.json({ + maxTime: state.maxTimeBeforeReset / 1000, + maxTimeDisplay: state.maxTimeDisplay / 1000, + minTimeForLeaderboard: state.minTimeForLeaderboard / 1000 + }); +}); + +app.post('/api/start-learning', (req, res) => { + state.learningMode = true; + state.learningStep = 0; + res.json({ success: true }); +}); + +app.post('/api/stop-learning', (req, res) => { + state.learningMode = false; + state.learningStep = 0; + res.json({ success: true }); +}); + +app.get('/api/learn/status', (req, res) => { + res.json({ + active: state.learningMode, + step: state.learningStep + }); +}); + +app.get('/api/buttons/status', (req, res) => { + res.json({ + lane1Start: state.buttonConfigs.start1.isAssigned, + lane1StartVoltage: state.buttonConfigs.start1.voltage, + lane1Stop: state.buttonConfigs.stop1.isAssigned, + lane1StopVoltage: state.buttonConfigs.stop1.voltage, + lane2Start: state.buttonConfigs.start2.isAssigned, + lane2StartVoltage: state.buttonConfigs.start2.voltage, + lane2Stop: state.buttonConfigs.stop2.isAssigned, + lane2StopVoltage: state.buttonConfigs.stop2.voltage + }); +}); + +app.get('/api/info', (req, res) => { + const connected = [ + state.buttonConfigs.start1.isAssigned, + state.buttonConfigs.stop1.isAssigned, + state.buttonConfigs.start2.isAssigned, + state.buttonConfigs.stop2.isAssigned + ].filter(Boolean).length; + + res.json({ + ip: "127.0.0.1", + ipSTA: "127.0.0.1", + channel: 1, + mac: "AA:BB:CC:DD:EE:FF", + freeMemory: 1024 * 1024, + connectedButtons: connected, + isOnline: true, + valid: "Ja", + tier: 1 + }); +}); + +app.post('/api/set-wifi', (req, res) => { + if (req.body.ssid) { + state.wifi.ssid = req.body.ssid; + state.wifi.password = req.body.password || ""; + res.json({ success: true }); + } else { + res.status(400).json({ success: false, error: "SSID fehlt" }); + } +}); + +app.get('/api/get-wifi', (req, res) => { + res.json({ + ssid: state.wifi.ssid, + password: state.wifi.password + }); +}); + +app.post('/api/set-location', (req, res) => { + if (req.body.name) { + state.masterlocation = req.body.name; + } + res.json({ success: true }); +}); + +app.get('/api/get-location', (req, res) => { + res.json({ + locationid: state.masterlocation + }); +}); + +app.get('/api/updateButtons', (req, res) => { + if (mqttClient && mqttClient.connected) { + mqttClient.publish('aquacross/update/flag', '1'); + } + res.json({ success: true }); +}); + +app.post('/api/set-mode', (req, res) => { + if (req.body.mode) { + state.gamemode = req.body.mode === "individual" ? 0 : 1; + res.json({ success: true }); + } else { + res.status(400).json({ success: false, error: "Modus fehlt" }); + } +}); + +app.get('/api/get-mode', (req, res) => { + res.json({ + mode: state.gamemode === 0 ? "individual" : "wettkampf" + }); +}); + +app.post('/api/set-lane-config', (req, res) => { + if (req.body.type) { + state.laneConfigType = req.body.type === "identical" ? 0 : 1; + if (state.laneConfigType === 1) { + if (req.body.lane1Difficulty) { + state.lane1DifficultyType = req.body.lane1Difficulty === "light" ? 0 : 1; + } + if (req.body.lane2Difficulty) { + state.lane2DifficultyType = req.body.lane2Difficulty === "light" ? 0 : 1; + } + } + res.json({ success: true }); + } else { + res.status(400).json({ success: false, error: "Lane type missing" }); + } +}); + +app.get('/api/get-lane-config', (req, res) => { + const config = { + type: state.laneConfigType === 0 ? "identical" : "different" + }; + if (state.laneConfigType === 1) { + config.lane1Difficulty = state.lane1DifficultyType === 0 ? "light" : "heavy"; + config.lane2Difficulty = state.lane2DifficultyType === 0 ? "light" : "heavy"; + } + res.json(config); +}); + +// Debug Endpoints +app.get('/api/debug/start1', (req, res) => { + individualMode("start", 2, 1, millis()); + res.send("handleStart1() called"); +}); + +app.get('/api/debug/stop1', (req, res) => { + individualMode("stop", 1, 1, millis()); + res.send("handleStop1() called"); +}); + +app.get('/api/debug/start2', (req, res) => { + individualMode("start", 2, 2, millis()); + res.send("handleStart2() called"); +}); + +app.get('/api/debug/stop2', (req, res) => { + individualMode("stop", 1, 2, millis()); + res.send("handleStop2() called"); +}); + +// WebSocket Setup +io.on('connection', (socket) => { + console.log(`[WebSocket] Client connected: ${socket.id}`); + + socket.on('disconnect', () => { + console.log(`[WebSocket] Client disconnected: ${socket.id}`); + }); +}); + +// Time sync - publish every 5 seconds +setInterval(() => { + if (mqttClient && mqttClient.connected) { + mqttClient.publish('sync/time', millis().toString()); + } +}, 5000); + +// Auto-reset check +setInterval(() => { + const currentTime = millis(); + + if (state.gamemode === 0) { + // Individual mode + if (!state.timerData1.isRunning && state.timerData1.endTime > 0 && + state.timerData1.finishedSince > 0) { + if (currentTime - state.timerData1.finishedSince > state.maxTimeDisplay) { + state.timerData1.startTime = 0; + state.timerData1.endTime = 0; + state.timerData1.finishedSince = 0; + state.timerData1.isReady = true; + publishLaneStatus(1, "ready"); + } + } + if (!state.timerData2.isRunning && state.timerData2.endTime > 0 && + state.timerData2.finishedSince > 0) { + if (currentTime - state.timerData2.finishedSince > state.maxTimeDisplay) { + state.timerData2.startTime = 0; + state.timerData2.endTime = 0; + state.timerData2.finishedSince = 0; + state.timerData2.isReady = true; + publishLaneStatus(2, "ready"); + } + } + } +}, 1000); + +// Start server +server.listen(PORT, () => { + console.log(`[Server] Mock ESP32 Server running on port ${PORT}`); + console.log(`[Server] Web UI available at http://localhost:${PORT}`); + + // Wait a moment before trying to connect to MQTT broker + // This gives the broker time to start if both are started together + setTimeout(() => { + console.log('[MQTT] Attempting initial connection to broker...'); + connectMQTT(); + }, 2000); + + // Also set up a periodic check (backup retry mechanism) + // Note: mqtt.js already has auto-reconnect, this is just a backup + mqttReconnectInterval = setInterval(() => { + if (!mqttClient || (!mqttClient.connected && !mqttClient.connecting)) { + console.log('[MQTT] Connection check: Not connected, attempting reconnect...'); + connectMQTT(); + } + }, 15000); // Check every 15 seconds if not connected +}); + +// Graceful shutdown +process.on('SIGINT', () => { + console.log('\n[Server] Shutting down...'); + if (mqttClient) { + mqttClient.end(); + } + server.close(() => { + console.log('[Server] Server closed'); + process.exit(0); + }); +}); diff --git a/mock-server/mqtt_broker.js b/mock-server/mqtt_broker.js new file mode 100644 index 0000000..215e109 --- /dev/null +++ b/mock-server/mqtt_broker.js @@ -0,0 +1,108 @@ +const aedes = require('aedes')(); +const net = require('net'); +const ws = require('ws'); +const http = require('http'); +const port = 1883; +const wsPort = 9001; + +// TCP Server for MQTT +const server = net.createServer(aedes.handle); + +// Logging für alle Nachrichten +aedes.on('publish', (packet, client) => { + if (client) { + console.log(`[MQTT] Client ${client.id} published to topic: ${packet.topic}`); + console.log(`[MQTT] Payload: ${packet.payload.toString()}`); + } else { + console.log(`[MQTT] Published to topic: ${packet.topic}`); + console.log(`[MQTT] Payload: ${packet.payload.toString()}`); + } +}); + +// Client-Verbindungen +aedes.on('client', (client) => { + console.log(`[MQTT] Client connected: ${client.id}`); +}); + +aedes.on('clientDisconnect', (client) => { + console.log(`[MQTT] Client disconnected: ${client.id}`); +}); + +// Fehlerbehandlung +aedes.on('clientError', (client, err) => { + console.error(`[MQTT] Client error for ${client.id}:`, err); +}); + +// WebSocket Server for browser connections +const httpServer = http.createServer(); +const wsServer = new ws.Server({ + server: httpServer, + path: '/mqtt' +}); + +wsServer.on('connection', (socket, req) => { + // Create a proper stream adapter for Aedes + const { Duplex } = require('stream'); + + const stream = new Duplex({ + write(chunk, encoding, callback) { + if (socket.readyState === ws.OPEN) { + socket.send(chunk); + callback(); + } else { + callback(new Error('WebSocket is not open')); + } + }, + read() { + // No-op: we push data when we receive it + } + }); + + // Handle incoming WebSocket messages + socket.on('message', (data) => { + stream.push(data); + }); + + socket.on('error', (err) => { + console.error('[MQTT] WebSocket error:', err); + stream.destroy(err); + }); + + socket.on('close', () => { + console.log('[MQTT] WebSocket client disconnected'); + stream.push(null); // End the stream + }); + + // Handle stream errors + stream.on('error', (err) => { + console.error('[MQTT] Stream error:', err); + if (socket.readyState === ws.OPEN) { + socket.close(); + } + }); + + // Pass the stream to Aedes + aedes.handle(stream); +}); + +server.listen(port, () => { + console.log(`[MQTT] TCP Broker started and listening on port ${port}`); + console.log(`[MQTT] Ready to accept TCP connections`); +}); + +httpServer.listen(wsPort, () => { + console.log(`[MQTT] WebSocket Broker started and listening on port ${wsPort}`); + console.log(`[MQTT] Ready to accept WebSocket connections at ws://localhost:${wsPort}/mqtt`); +}); + +// Graceful shutdown +process.on('SIGINT', () => { + console.log('\n[MQTT] Shutting down broker...'); + server.close(() => { + console.log('[MQTT] TCP server closed'); + }); + httpServer.close(() => { + console.log('[MQTT] WebSocket server closed'); + process.exit(0); + }); +}); diff --git a/mock-server/package-lock.json b/mock-server/package-lock.json new file mode 100644 index 0000000..4a2e739 --- /dev/null +++ b/mock-server/package-lock.json @@ -0,0 +1,1922 @@ +{ + "name": "aquamaster-mock-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aquamaster-mock-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "aedes": "^0.50.0", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "express": "^4.18.2", + "mqtt": "^5.3.1", + "socket.io": "^4.6.1", + "ws": "^8.14.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "25.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", + "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/readable-stream": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aedes": { + "version": "0.50.1", + "resolved": "https://registry.npmjs.org/aedes/-/aedes-0.50.1.tgz", + "integrity": "sha512-S1P+COZYSDVYND8G+b7Vy+xoENix57QOJL8pDlwBYvr6GIiaJZFNvOy7GvUxPDxb0EBrsiPSfHHJc6aySK9Wfg==", + "license": "MIT", + "dependencies": { + "aedes-packet": "^3.0.0", + "aedes-persistence": "^9.1.2", + "end-of-stream": "^1.4.4", + "fastfall": "^1.5.1", + "fastparallel": "^2.4.1", + "fastseries": "^2.0.0", + "hyperid": "^3.1.1", + "mqemitter": "^5.0.0", + "mqtt-packet": "^9.0.0", + "retimer": "^3.0.0", + "reusify": "^1.0.4", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/aedes-packet": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aedes-packet/-/aedes-packet-3.0.0.tgz", + "integrity": "sha512-swASey0BxGs4/npZGWoiVDmnEyPvVFIRY6l2LVKL4rbiW8IhcIGDLfnb20Qo8U20itXlitAKPQ3MVTEbOGG5ZA==", + "license": "MIT", + "dependencies": { + "mqtt-packet": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/aedes-packet/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/aedes-packet/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/aedes-packet/node_modules/mqtt-packet": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-7.1.2.tgz", + "integrity": "sha512-FFZbcZ2omsf4c5TxEQfcX9hI+JzDpDKPT46OmeIBpVA7+t32ey25UNqlqNXTmeZOr5BLsSIERpQQLsFWJS94SQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.2", + "debug": "^4.1.1", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/aedes-packet/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/aedes-packet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/aedes-persistence": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/aedes-persistence/-/aedes-persistence-9.1.2.tgz", + "integrity": "sha512-2Wlr5pwIK0eQOkiTwb8ZF6C20s8UPUlnsJ4kXYePZ3JlQl0NbBA176mzM8wY294BJ5wybpNc9P5XEQxqadRNcQ==", + "license": "MIT", + "dependencies": { + "aedes-packet": "^3.0.0", + "qlobber": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bl": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz", + "integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.0", + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^4.2.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/broker-factory": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz", + "integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", + "tslib": "^2.8.1", + "worker-factory": "^7.0.48" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/commist": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", + "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-unique-numbers": { + "version": "9.0.26", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz", + "integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, + "node_modules/fastfall": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", + "integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==", + "license": "MIT", + "dependencies": { + "reusify": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fastparallel": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", + "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4", + "xtend": "^4.0.2" + } + }, + "node_modules/fastseries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-2.0.0.tgz", + "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==", + "license": "ISC" + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/hyperid": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.3.0.tgz", + "integrity": "sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "uuid": "^8.3.2", + "uuid-parse": "^1.1.0" + } + }, + "node_modules/hyperid/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/js-sdsl": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", + "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mqemitter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mqemitter/-/mqemitter-5.0.0.tgz", + "integrity": "sha512-rqNRQhGgl0W/NV+Zrx0rpAUTZcSlAtivCVUmXBUPcFYt+AeDEpoJgy5eKlFWJP6xnatONL59WIFdV0W6niOMhw==", + "license": "ISC", + "dependencies": { + "fastparallel": "^2.3.0", + "qlobber": "^7.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mqtt": { + "version": "5.14.1", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.14.1.tgz", + "integrity": "sha512-NxkPxE70Uq3Ph7goefQa7ggSsVzHrayCD0OyxlJgITN/EbzlZN+JEPmaAZdxP1LsIT5FamDyILoQTF72W7Nnbw==", + "license": "MIT", + "dependencies": { + "@types/readable-stream": "^4.0.21", + "@types/ws": "^8.18.1", + "commist": "^3.2.0", + "concat-stream": "^2.0.0", + "debug": "^4.4.1", + "help-me": "^5.0.0", + "lru-cache": "^10.4.3", + "minimist": "^1.2.8", + "mqtt-packet": "^9.0.2", + "number-allocator": "^1.0.14", + "readable-stream": "^4.7.0", + "rfdc": "^1.4.1", + "socks": "^2.8.6", + "split2": "^4.2.0", + "worker-timers": "^8.0.23", + "ws": "^8.18.3" + }, + "bin": { + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/mqtt-packet": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz", + "integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==", + "license": "MIT", + "dependencies": { + "bl": "^6.0.8", + "debug": "^4.3.4", + "process-nextick-args": "^2.0.1" + } + }, + "node_modules/mqtt-packet/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mqtt-packet/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mqtt/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mqtt/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/number-allocator": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz", + "integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "js-sdsl": "4.3.0" + } + }, + "node_modules/number-allocator/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/number-allocator/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qlobber": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/qlobber/-/qlobber-7.0.1.tgz", + "integrity": "sha512-FsFg9lMuMEFNKmTO9nV7tlyPhx8BmskPPjH2akWycuYVTtWaVwhW5yCHLJQ6Q+3mvw5cFX2vMfW2l9z2SiYAbg==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/retimer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", + "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuid-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", + "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/worker-factory": { + "version": "7.0.48", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz", + "integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", + "tslib": "^2.8.1" + } + }, + "node_modules/worker-timers": { + "version": "8.0.29", + "resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.29.tgz", + "integrity": "sha512-9jk0MWHhWAZ2xlJPXr45oe5UF/opdpfZrY0HtyPizWuJ+ce1M3IYk/4IIdGct3kn9Ncfs+tkZt3w1tU6KW2Fsg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1", + "worker-timers-broker": "^8.0.15", + "worker-timers-worker": "^9.0.13" + } + }, + "node_modules/worker-timers-broker": { + "version": "8.0.15", + "resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.15.tgz", + "integrity": "sha512-Te+EiVUMzG5TtHdmaBZvBrZSFNauym6ImDaCAnzQUxvjnw+oGjMT2idmAOgDy30vOZMLejd0bcsc90Axu6XPWA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "broker-factory": "^3.1.13", + "fast-unique-numbers": "^9.0.26", + "tslib": "^2.8.1", + "worker-timers-worker": "^9.0.13" + } + }, + "node_modules/worker-timers-worker": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.13.tgz", + "integrity": "sha512-qjn18szGb1kjcmh2traAdki1eiIS5ikFo+L90nfMOvSRpuDw1hAcR1nzkP2+Hkdqz5thIRnfuWx7QSpsEUsA6Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1", + "worker-factory": "^7.0.48" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/mock-server/package.json b/mock-server/package.json new file mode 100644 index 0000000..d6bd65a --- /dev/null +++ b/mock-server/package.json @@ -0,0 +1,28 @@ +{ + "name": "aquamaster-mock-server", + "version": "1.0.0", + "description": "Mock ESP32 Server and MQTT Broker for testing AquaMaster without hardware", + "main": "start_all.js", + "scripts": { + "start": "node start_all.js", + "mqtt": "node mqtt_broker.js", + "server": "node mock_esp32_server.js" + }, + "keywords": [ + "mqtt", + "esp32", + "mock", + "testing" + ], + "author": "", + "license": "MIT", + "dependencies": { + "aedes": "^0.50.0", + "express": "^4.18.2", + "socket.io": "^4.6.1", + "mqtt": "^5.3.1", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "ws": "^8.14.2" + } +} diff --git a/mock-server/start_all.js b/mock-server/start_all.js new file mode 100644 index 0000000..2dde6d2 --- /dev/null +++ b/mock-server/start_all.js @@ -0,0 +1,47 @@ +const { spawn } = require('child_process'); +const path = require('path'); + +console.log('Starting AquaMaster Mock Server...\n'); + +// Start MQTT Broker +console.log('[1/2] Starting MQTT Broker...'); +const mqttBroker = spawn('node', [path.join(__dirname, 'mqtt_broker.js')], { + stdio: 'inherit', + cwd: __dirname +}); + +mqttBroker.on('error', (err) => { + console.error('Failed to start MQTT Broker:', err); + process.exit(1); +}); + +// Wait a bit longer for MQTT broker to fully start +setTimeout(() => { + // Start Mock ESP32 Server + console.log('[2/2] Starting Mock ESP32 Server...'); + const mockServer = spawn('node', [path.join(__dirname, 'mock_esp32_server.js')], { + stdio: 'inherit', + cwd: __dirname + }); + + mockServer.on('error', (err) => { + console.error('Failed to start Mock ESP32 Server:', err); + mqttBroker.kill(); + process.exit(1); + }); + + // Handle shutdown + const shutdown = () => { + console.log('\nShutting down servers...'); + if (mqttBroker && !mqttBroker.killed) { + mqttBroker.kill(); + } + if (mockServer && !mockServer.killed) { + mockServer.kill(); + } + process.exit(0); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); +}, 3000); // Increased wait time to 3 seconds