slm integration added
This commit is contained in:
105
templates/partials/slm_controls.html
Normal file
105
templates/partials/slm_controls.html
Normal file
@@ -0,0 +1,105 @@
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<!-- Status Bar -->
|
||||
<div class="mb-6 p-4 rounded-lg {% if is_measuring %}bg-green-50 dark:bg-green-900/20{% else %}bg-gray-50 dark:bg-gray-900{% endif %}">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Measurement Status</div>
|
||||
<div class="text-2xl font-bold {% if is_measuring %}text-green-600 dark:text-green-400{% else %}text-gray-600 dark:text-gray-400{% endif %}">
|
||||
{% if measurement_state %}
|
||||
{{ measurement_state }}
|
||||
{% if is_measuring %}
|
||||
<span class="inline-block w-3 h-3 bg-green-500 rounded-full ml-2 animate-pulse"></span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Unknown
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Battery</div>
|
||||
<div class="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{{ battery_level or '--' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Control Buttons -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<button hx-post="/api/slmm/{{ unit_id }}/start"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="htmx.trigger('#slm-controls', 'refresh')"
|
||||
class="px-4 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center justify-center gap-2
|
||||
{% if is_measuring %}opacity-50 cursor-not-allowed{% endif %}"
|
||||
{% if is_measuring %}disabled{% endif %}>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Start
|
||||
</button>
|
||||
|
||||
<button hx-post="/api/slmm/{{ unit_id }}/stop"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="htmx.trigger('#slm-controls', 'refresh')"
|
||||
class="px-4 py-3 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors flex items-center justify-center gap-2
|
||||
{% if not is_measuring %}opacity-50 cursor-not-allowed{% endif %}"
|
||||
{% if not is_measuring %}disabled{% endif %}>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"></path>
|
||||
</svg>
|
||||
Stop
|
||||
</button>
|
||||
|
||||
<button hx-post="/api/slmm/{{ unit_id }}/pause"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="htmx.trigger('#slm-controls', 'refresh')"
|
||||
class="px-4 py-3 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 transition-colors flex items-center justify-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Pause
|
||||
</button>
|
||||
|
||||
<button hx-post="/api/slmm/{{ unit_id }}/reset"
|
||||
hx-swap="none"
|
||||
hx-confirm="Are you sure you want to reset the measurement data?"
|
||||
hx-on::after-request="htmx.trigger('#slm-controls', 'refresh')"
|
||||
class="px-4 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors flex items-center justify-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button hx-get="/api/slmm/{{ unit_id }}/live"
|
||||
hx-swap="none"
|
||||
hx-indicator="#live-spinner"
|
||||
class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors flex items-center justify-center gap-2">
|
||||
<svg id="live-spinner" class="htmx-indicator w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
|
||||
</svg>
|
||||
Get Live Data
|
||||
</button>
|
||||
|
||||
<button hx-post="/api/slmm/{{ unit_id }}/store"
|
||||
hx-swap="none"
|
||||
class="px-4 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 transition-colors flex items-center justify-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"></path>
|
||||
</svg>
|
||||
Store Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="slm-controls" hx-get="/slm/partials/{{ unit_id }}/controls" hx-trigger="refresh" hx-swap="outerHTML"></div>
|
||||
@@ -124,6 +124,7 @@
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
<option value="seismograph">Seismograph</option>
|
||||
<option value="modem">Modem</option>
|
||||
<option value="sound_level_meter">Sound Level Meter</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
@@ -186,6 +187,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sound Level Meter-specific fields -->
|
||||
<div id="slmFields" class="hidden space-y-4 border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||
<p class="text-sm font-semibold text-gray-700 dark:text-gray-300">Sound Level Meter Information</p>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">SLM Model</label>
|
||||
<input type="text" name="slm_model" placeholder="NL-43"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Host/IP Address</label>
|
||||
<input type="text" name="slm_host" placeholder="192.168.1.100"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">TCP Port</label>
|
||||
<input type="number" name="slm_tcp_port" placeholder="2255"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Serial Number</label>
|
||||
<input type="text" name="slm_serial_number" placeholder="SN123456"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Frequency Weighting</label>
|
||||
<select name="slm_frequency_weighting"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
<option value="A">A-weighting</option>
|
||||
<option value="C">C-weighting</option>
|
||||
<option value="Z">Z-weighting (Flat)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Time Weighting</label>
|
||||
<select name="slm_time_weighting"
|
||||
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
|
||||
<option value="F">F (Fast)</option>
|
||||
<option value="S">S (Slow)</option>
|
||||
<option value="I">I (Impulse)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" name="deployed" id="deployedCheckbox" value="true" checked onchange="toggleModemPairing()"
|
||||
@@ -388,14 +432,21 @@
|
||||
const deviceType = document.getElementById('deviceTypeSelect').value;
|
||||
const seismoFields = document.getElementById('seismographFields');
|
||||
const modemFields = document.getElementById('modemFields');
|
||||
const slmFields = document.getElementById('slmFields');
|
||||
|
||||
if (deviceType === 'seismograph') {
|
||||
seismoFields.classList.remove('hidden');
|
||||
modemFields.classList.add('hidden');
|
||||
slmFields.classList.add('hidden');
|
||||
toggleModemPairing(); // Check if modem pairing should be shown
|
||||
} else {
|
||||
} else if (deviceType === 'modem') {
|
||||
seismoFields.classList.add('hidden');
|
||||
modemFields.classList.remove('hidden');
|
||||
slmFields.classList.add('hidden');
|
||||
} else if (deviceType === 'sound_level_meter') {
|
||||
seismoFields.classList.add('hidden');
|
||||
modemFields.classList.add('hidden');
|
||||
slmFields.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
195
templates/slm_detail.html
Normal file
195
templates/slm_detail.html
Normal file
@@ -0,0 +1,195 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ unit_id }} - Sound Level Meter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mb-6">
|
||||
<a href="/roster" class="text-seismo-orange hover:text-seismo-orange-dark flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
Back to Roster
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white flex items-center">
|
||||
<svg class="w-8 h-8 mr-3 text-seismo-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3">
|
||||
</path>
|
||||
</svg>
|
||||
{{ unit_id }}
|
||||
</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
||||
{{ unit.slm_model or 'NL-43' }} Sound Level Meter
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<span class="px-3 py-1 rounded-full text-sm font-medium
|
||||
{% if unit.deployed %}bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200
|
||||
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200{% endif %}">
|
||||
{% if unit.deployed %}Deployed{% else %}Benched{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Control Panel -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Control Panel</h2>
|
||||
<div hx-get="/slm/partials/{{ unit_id }}/controls"
|
||||
hx-trigger="load, every 5s"
|
||||
hx-swap="innerHTML">
|
||||
<div class="text-center py-8 text-gray-500">Loading controls...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Real-time Data Stream -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Real-time Measurements</h2>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div id="slm-stream-container">
|
||||
<div class="text-center py-8">
|
||||
<button onclick="startStream()"
|
||||
id="stream-start-btn"
|
||||
class="px-6 py-3 bg-seismo-orange text-white rounded-lg hover:bg-seismo-orange-dark transition-colors">
|
||||
Start Real-time Stream
|
||||
</button>
|
||||
<p class="text-sm text-gray-500 mt-2">Click to begin streaming live measurement data</p>
|
||||
</div>
|
||||
<div id="stream-data" class="hidden">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Lp (Instant)</div>
|
||||
<div id="stream-lp" class="text-3xl font-bold text-gray-900 dark:text-white">--</div>
|
||||
<div class="text-xs text-gray-500">dB</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Leq (Average)</div>
|
||||
<div id="stream-leq" class="text-3xl font-bold text-blue-600 dark:text-blue-400">--</div>
|
||||
<div class="text-xs text-gray-500">dB</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Lmax</div>
|
||||
<div id="stream-lmax" class="text-3xl font-bold text-red-600 dark:text-red-400">--</div>
|
||||
<div class="text-xs text-gray-500">dB</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Lmin</div>
|
||||
<div id="stream-lmin" class="text-3xl font-bold text-green-600 dark:text-green-400">--</div>
|
||||
<div class="text-xs text-gray-500">dB</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-xs text-gray-500">
|
||||
<span class="inline-block w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></span>
|
||||
Streaming
|
||||
</div>
|
||||
<button onclick="stopStream()"
|
||||
class="px-4 py-2 bg-red-600 text-white text-sm rounded-lg hover:bg-red-700 transition-colors">
|
||||
Stop Stream
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Information -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Device Information</h2>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Model</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_model or 'NL-43' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Serial Number</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_serial_number or 'N/A' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Host</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_host or 'Not configured' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">TCP Port</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_tcp_port or 'N/A' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Frequency Weighting</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_frequency_weighting or 'A' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Time Weighting</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.slm_time_weighting or 'F (Fast)' }}</div>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Location</div>
|
||||
<div class="text-lg font-medium text-gray-900 dark:text-white">{{ unit.address or unit.location or 'Not specified' }}</div>
|
||||
</div>
|
||||
{% if unit.note %}
|
||||
<div class="md:col-span-2">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Notes</div>
|
||||
<div class="text-gray-900 dark:text-white">{{ unit.note }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
|
||||
function startStream() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/api/slmm/{{ unit_id }}/stream`;
|
||||
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
document.getElementById('stream-start-btn').classList.add('hidden');
|
||||
document.getElementById('stream-data').classList.remove('hidden');
|
||||
console.log('WebSocket connected');
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
|
||||
if (data.error) {
|
||||
console.error('Stream error:', data.error);
|
||||
stopStream();
|
||||
alert('Error: ' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update values
|
||||
document.getElementById('stream-lp').textContent = data.lp || '--';
|
||||
document.getElementById('stream-leq').textContent = data.leq || '--';
|
||||
document.getElementById('stream-lmax').textContent = data.lmax || '--';
|
||||
document.getElementById('stream-lmin').textContent = data.lmin || '--';
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
stopStream();
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket closed');
|
||||
};
|
||||
}
|
||||
|
||||
function stopStream() {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
ws = null;
|
||||
}
|
||||
document.getElementById('stream-start-btn').classList.remove('hidden');
|
||||
document.getElementById('stream-data').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user