// 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(); }