211 lines
8.5 KiB
HTML
211 lines
8.5 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>HAMeter Setup</title>
|
|
<link rel="stylesheet" href="{{ url_for('main.static', filename='style.css') }}">
|
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('main.static', filename='favicon.svg') }}">
|
|
</head>
|
|
<body class="setup-body">
|
|
<div class="setup-container">
|
|
<div class="setup-card" id="step-1">
|
|
<div class="setup-logo">HAMeter</div>
|
|
<h2>Welcome</h2>
|
|
<p>HAMeter reads utility meters via SDR and publishes readings to Home Assistant over MQTT.</p>
|
|
<p>Let's configure your connection.</p>
|
|
<button class="btn btn-primary btn-lg" onclick="showStep(2)">Get Started</button>
|
|
</div>
|
|
|
|
<div class="setup-card hidden" id="step-2">
|
|
<h2>MQTT Broker</h2>
|
|
<p>Enter your MQTT broker connection details.</p>
|
|
<form id="mqtt-form" onsubmit="return false;">
|
|
<div class="form-group">
|
|
<label for="mqtt-host">Host <span class="required">*</span></label>
|
|
<input type="text" id="mqtt-host" placeholder="192.168.1.74" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="mqtt-port">Port</label>
|
|
<input type="number" id="mqtt-port" value="1883">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="mqtt-user">Username</label>
|
|
<input type="text" id="mqtt-user" placeholder="Optional">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="mqtt-password">Password</label>
|
|
<input type="password" id="mqtt-password" placeholder="Optional">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="testMqtt()">Test Connection</button>
|
|
<button type="button" class="btn btn-primary" onclick="mqttNext()">Next</button>
|
|
</div>
|
|
<div class="test-result hidden" id="mqtt-test-result"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="setup-card hidden" id="step-3">
|
|
<h2>Add a Meter</h2>
|
|
<p>If you know your meter's radio ID and protocol, enter them below. Otherwise, you can use Discovery after setup.</p>
|
|
<form id="meter-form" onsubmit="return false;">
|
|
<div class="form-group">
|
|
<label for="meter-id">Meter ID (ERT Serial Number)</label>
|
|
<input type="number" id="meter-id" placeholder="e.g. 23040293">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-protocol">Protocol</label>
|
|
<select id="meter-protocol">
|
|
{% for p in protocols %}
|
|
<option value="{{ p }}">{{ p }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-name">Name</label>
|
|
<input type="text" id="meter-name" placeholder="e.g. Electric Meter">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="meter-device-class">Type</label>
|
|
<select id="meter-device-class" onchange="updateMeterDefaults()">
|
|
<option value="">-- Select --</option>
|
|
<option value="energy">Energy (Electric)</option>
|
|
<option value="gas">Gas</option>
|
|
<option value="water">Water</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-actions">
|
|
<a href="#" class="btn btn-link" onclick="skipMeter()">Skip — I'll use Discovery later</a>
|
|
<button type="button" class="btn btn-primary" onclick="saveMeterAndFinish()">Save & Start</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="setup-card hidden" id="step-4">
|
|
<div class="setup-success">✓</div>
|
|
<h2>Setup Complete</h2>
|
|
<p>HAMeter is starting up. Redirecting to dashboard...</p>
|
|
<div class="spinner"></div>
|
|
</div>
|
|
|
|
<div class="setup-error hidden" id="setup-error">
|
|
<p class="error-text"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function showStep(n) {
|
|
document.querySelectorAll('.setup-card').forEach(el => el.classList.add('hidden'));
|
|
document.getElementById('step-' + n).classList.remove('hidden');
|
|
}
|
|
|
|
async function testMqtt() {
|
|
const result = document.getElementById('mqtt-test-result');
|
|
result.classList.remove('hidden');
|
|
result.textContent = 'Testing...';
|
|
result.className = 'test-result';
|
|
|
|
try {
|
|
const resp = await fetch('/api/config/mqtt/test', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
host: document.getElementById('mqtt-host').value,
|
|
port: parseInt(document.getElementById('mqtt-port').value),
|
|
user: document.getElementById('mqtt-user').value,
|
|
password: document.getElementById('mqtt-password').value,
|
|
}),
|
|
});
|
|
const data = await resp.json();
|
|
result.textContent = data.message;
|
|
result.className = 'test-result ' + (data.ok ? 'test-success' : 'test-error');
|
|
} catch (e) {
|
|
result.textContent = 'Request failed: ' + e.message;
|
|
result.className = 'test-result test-error';
|
|
}
|
|
}
|
|
|
|
function mqttNext() {
|
|
const host = document.getElementById('mqtt-host').value.trim();
|
|
if (!host) {
|
|
showError('MQTT host is required');
|
|
return;
|
|
}
|
|
showStep(3);
|
|
}
|
|
|
|
function updateMeterDefaults() {
|
|
const dc = document.getElementById('meter-device-class').value;
|
|
if (!dc) return;
|
|
fetch('/api/meter_defaults/' + dc)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
// Auto-fill name if empty
|
|
const nameEl = document.getElementById('meter-name');
|
|
if (!nameEl.value) {
|
|
const names = {energy: 'Electric Meter', gas: 'Gas Meter', water: 'Water Meter'};
|
|
nameEl.value = names[dc] || '';
|
|
}
|
|
});
|
|
}
|
|
|
|
async function saveMeterAndFinish() {
|
|
await doSetup(true);
|
|
}
|
|
|
|
async function skipMeter() {
|
|
await doSetup(false);
|
|
}
|
|
|
|
async function doSetup(includeMeter) {
|
|
const payload = {
|
|
mqtt: {
|
|
host: document.getElementById('mqtt-host').value.trim(),
|
|
port: parseInt(document.getElementById('mqtt-port').value),
|
|
user: document.getElementById('mqtt-user').value,
|
|
password: document.getElementById('mqtt-password').value,
|
|
},
|
|
};
|
|
|
|
if (includeMeter) {
|
|
const meterId = document.getElementById('meter-id').value;
|
|
if (!meterId) {
|
|
showError('Meter ID is required');
|
|
return;
|
|
}
|
|
payload.meter = {
|
|
id: parseInt(meterId),
|
|
protocol: document.getElementById('meter-protocol').value,
|
|
name: document.getElementById('meter-name').value || 'Meter 1',
|
|
device_class: document.getElementById('meter-device-class').value,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const resp = await fetch('/api/setup', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const data = await resp.json();
|
|
if (data.ok) {
|
|
showStep(4);
|
|
setTimeout(() => window.location.href = '/dashboard', 3000);
|
|
} else {
|
|
showError(data.error || 'Setup failed');
|
|
}
|
|
} catch (e) {
|
|
showError('Request failed: ' + e.message);
|
|
}
|
|
}
|
|
|
|
function showError(msg) {
|
|
const el = document.getElementById('setup-error');
|
|
el.classList.remove('hidden');
|
|
el.querySelector('.error-text').textContent = msg;
|
|
setTimeout(() => el.classList.add('hidden'), 5000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|