Add desktop simulator

This commit is contained in:
root
2026-06-02 23:56:36 -06:00
parent f61011a285
commit a7d007d1c5
4 changed files with 291 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
from flask import Flask, jsonify, render_template, request
import random
import time
app = Flask(__name__)
state = {
"relays": {
"starlink": False,
"fridge": True
},
"wifi_override_until": 0
}
def fake_status():
starlink_on = state["relays"]["starlink"]
wifi_override_active = time.time() < state["wifi_override_until"]
return {
"timestamp": int(time.time()),
"battery": {
"soc": random.randint(76, 84),
"voltage": round(random.uniform(13.0, 13.4), 2),
"current": round(random.uniform(-8.0, -3.0), 1),
"remaining_ah": round(random.uniform(76.0, 84.0), 1),
"runtime_hours": round(random.uniform(9.5, 14.0), 1),
"temperature_f": round(random.uniform(70.0, 82.0), 1)
},
"temps": {
"fridge_zone_1": round(random.uniform(34.0, 38.0), 1),
"fridge_zone_2": round(random.uniform(8.0, 16.0), 1),
"rear_seat": round(random.uniform(72.0, 86.0), 1),
"outside": round(random.uniform(80.0, 96.0), 1)
},
"relays": state["relays"],
"network": {
"wifi_enabled": starlink_on or wifi_override_active,
"wifi_override_active": wifi_override_active,
"rs485_connected": True,
"starlink_enabled": starlink_on
}
}
@app.route("/")
def index():
return render_template("index.html")
@app.route("/status")
def status():
return jsonify(fake_status())
@app.route("/battery")
def battery():
return jsonify(fake_status()["battery"])
@app.route("/temps")
def temps():
return jsonify(fake_status()["temps"])
@app.route("/relays")
def relays():
return jsonify(state["relays"])
@app.route("/relay/<name>", methods=["POST"])
def set_relay(name):
if name not in state["relays"]:
return jsonify({"success": False, "error": "Unknown relay"}), 404
data = request.get_json(force=True)
state["relays"][name] = bool(data.get("state", False))
return jsonify({
"success": True,
name: state["relays"][name]
})
@app.route("/network")
def network():
return jsonify(fake_status()["network"])
@app.route("/network/wifi", methods=["POST"])
def enable_wifi():
data = request.get_json(force=True)
minutes = int(data.get("minutes", 10))
state["wifi_override_until"] = time.time() + minutes * 60
return jsonify({
"success": True,
"expires_minutes": minutes
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
+1
View File
@@ -0,0 +1 @@
flask
+69
View File
@@ -0,0 +1,69 @@
body {
margin: 0;
font-family: Arial, sans-serif;
background: #111;
color: #eee;
}
main {
max-width: 900px;
margin: 0 auto;
padding: 24px;
}
h1 {
text-align: center;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
}
.card {
background: #222;
border: 1px solid #333;
border-radius: 12px;
padding: 18px;
text-align: center;
}
.card h2 {
font-size: 1rem;
margin: 0 0 12px;
color: #aaa;
}
.card p {
font-size: 2rem;
margin: 0;
}
.controls {
margin-top: 24px;
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
button {
font-size: 1rem;
padding: 14px 20px;
border: none;
border-radius: 10px;
background: #444;
color: white;
cursor: pointer;
}
button:hover {
background: #666;
}
.status {
margin-top: 24px;
text-align: center;
color: #aaa;
}
+114
View File
@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<title>Xterra Dashboard Simulator</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main>
<h1>Xterra Dashboard</h1>
<section class="grid">
<div class="card">
<h2>Battery</h2>
<p><span id="soc">--</span>%</p>
<small><span id="voltage">--</span>V / <span id="current">--</span>A</small>
</div>
<div class="card">
<h2>Runtime</h2>
<p><span id="runtime">--</span> hr</p>
<small><span id="remaining">--</span> Ah remaining</small>
</div>
<div class="card">
<h2>Fridge Zone 1</h2>
<p><span id="fridge1">--</span>°F</p>
</div>
<div class="card">
<h2>Fridge Zone 2</h2>
<p><span id="fridge2">--</span>°F</p>
</div>
<div class="card">
<h2>Rear Seat</h2>
<p><span id="rear">--</span>°F</p>
</div>
<div class="card">
<h2>Outside</h2>
<p><span id="outside">--</span>°F</p>
</div>
</section>
<section class="controls">
<button onclick="toggleRelay('starlink')" id="starlinkBtn">Starlink</button>
<button onclick="toggleRelay('fridge')" id="fridgeBtn">Fridge</button>
<button onclick="enableWifi()">Enable WiFi 10 min</button>
</section>
<section class="status">
<p>RS-485: <span id="rs485">--</span></p>
<p>WiFi: <span id="wifi">--</span></p>
</section>
</main>
<script>
let relayState = {};
async function loadStatus() {
const res = await fetch('/status');
const data = await res.json();
document.getElementById('soc').textContent = data.battery.soc;
document.getElementById('voltage').textContent = data.battery.voltage;
document.getElementById('current').textContent = data.battery.current;
document.getElementById('runtime').textContent = data.battery.runtime_hours;
document.getElementById('remaining').textContent = data.battery.remaining_ah;
document.getElementById('fridge1').textContent = data.temps.fridge_zone_1;
document.getElementById('fridge2').textContent = data.temps.fridge_zone_2;
document.getElementById('rear').textContent = data.temps.rear_seat;
document.getElementById('outside').textContent = data.temps.outside;
relayState = data.relays;
document.getElementById('starlinkBtn').textContent =
relayState.starlink ? 'Starlink: ON' : 'Starlink: OFF';
document.getElementById('fridgeBtn').textContent =
relayState.fridge ? 'Fridge: ON' : 'Fridge: OFF';
document.getElementById('rs485').textContent =
data.network.rs485_connected ? 'Connected' : 'Disconnected';
document.getElementById('wifi').textContent =
data.network.wifi_enabled ? 'Enabled' : 'Disabled';
}
async function toggleRelay(name) {
await fetch(`/relay/${name}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({state: !relayState[name]})
});
loadStatus();
}
async function enableWifi() {
await fetch('/network/wifi', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({minutes: 10})
});
loadStatus();
}
loadStatus();
setInterval(loadStatus, 2000);
</script>
</body>
</html>