183 lines
5.4 KiB
HTML
183 lines
5.4 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
<style>
|
|
.container {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
}
|
|
.plots {
|
|
flex: 1;
|
|
}
|
|
.pid-panel {
|
|
margin-left: 20px;
|
|
padding: 10px;
|
|
border: 1px solid #aaa;
|
|
border-radius: 8px;
|
|
width: 220px;
|
|
background-color: #f9f9f9;
|
|
}
|
|
.pid-panel label {
|
|
display: block;
|
|
margin-top: 8px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h2>Valve Controller</h2>
|
|
|
|
<button onclick="connectValve()">Connect</button>
|
|
<br><br>
|
|
|
|
<label>Setpoint:
|
|
<input type="number" id="setpointInput" value="0" step="0.1">
|
|
<button onclick="setSetpoint()">Send</button>
|
|
</label>
|
|
<label style="margin-left: 15px;">
|
|
<input type="checkbox" id="setpointEnable"> Enable
|
|
</label>
|
|
<br><br>
|
|
|
|
<label>Speed:
|
|
<input type="number" id="speedInput" value="0" step="0.01">
|
|
<button onclick="setSpeed()">Send</button>
|
|
</label>
|
|
<br><br>
|
|
|
|
<button onclick="startPRBS()">PRBS</button>
|
|
|
|
|
|
<button onclick="startRecording()">Start Recording</button>
|
|
<button onclick="stopRecording()">Stop Recording</button>
|
|
<br><br>
|
|
|
|
<div class="container">
|
|
<!-- Left: plots -->
|
|
<div class="plots">
|
|
<div id="plot_pos" style="width:100%;height:300px;"></div>
|
|
<div id="plot_speed" style="width:100%;height:300px;"></div>
|
|
</div>
|
|
|
|
<!-- Right: PID menu -->
|
|
<div class="pid-panel">
|
|
<h3>PID Coefficients</h3>
|
|
<label>Kp:
|
|
<input type="number" id="kpInput" value="10.0" step="0.01">
|
|
</label>
|
|
<label>Ki:
|
|
<input type="number" id="kiInput" value="0.05" step="0.01">
|
|
</label>
|
|
<label>Tolerance:
|
|
<input type="number" id="kdInput" value="0.005" step="0.01">
|
|
</label>
|
|
<br>
|
|
<button onclick="sendPID()">Send PID</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let layout_pos = {
|
|
title: 'Live Valve Position',
|
|
xaxis: { title: 'Time (s)' },
|
|
yaxis: { title: 'Position / Setpoint ' }
|
|
};
|
|
|
|
Plotly.newPlot('plot_pos', [
|
|
{ x: [], y: [], name: 'Position', mode: 'lines', line: { color: 'blue' } },
|
|
{ x: [], y: [], name: 'Setpoint', mode: 'lines', line: { color: 'red', dash: 'dash' } },
|
|
], layout_pos);
|
|
|
|
let layout_speed = {
|
|
title: 'Live Valve Speed',
|
|
xaxis: { title: 'Time (s)' },
|
|
yaxis: { title: 'Setpoint / Speed' }
|
|
};
|
|
|
|
Plotly.newPlot('plot_speed', [
|
|
{ x: [], y: [], name: 'Speed', mode: 'lines', line: { color: 'orange' } },
|
|
{ x: [], y: [], name: 'Speed_sp', mode: 'lines', line: { color: 'green', dash: 'dash' } },
|
|
{ x: [], y: [], name: 'Kp_term', mode: 'lines' },
|
|
{ x: [], y: [], name: 'Ki_term', mode: 'lines' },
|
|
{ x: [], y: [], name: 'Kd_term', mode: 'lines' }
|
|
|
|
], layout_speed);
|
|
|
|
async function connectValve() {
|
|
const res = await fetch('/connect', { method: 'POST' });
|
|
const data = await res.json();
|
|
alert("Valve connected: " + data.status);
|
|
}
|
|
|
|
async function fetchData() {
|
|
const res = await fetch('/position');
|
|
const data = await res.json();
|
|
|
|
const t0 = data.time.length > 0 ? data.time[0] : Date.now() / 1000;
|
|
const t = data.time.map(ts => ts - t0); // relative time
|
|
|
|
Plotly.react('plot_pos', [
|
|
{ x: t, y: data.position, name: 'Position', mode: 'lines', line: { color: 'blue' } },
|
|
{ x: t, y: data.setpoint, name: 'Setpoint', mode: 'lines', line: { color: 'red', dash: 'dash' } },
|
|
], layout_pos);
|
|
|
|
Plotly.react('plot_speed', [
|
|
{ x: t, y: data.speed, name: 'Speed', mode: 'lines', line: { color: 'orange'} },
|
|
{ x: t, y: data.speed_sp, name: 'Speed_sp', mode: 'lines', line: { color: 'green', dash: 'dash' } },
|
|
{ x: t, y: data.Kp_term, name: 'Kp_term', mode: 'lines'},
|
|
{ x: t, y: data.Ki_term, name: 'Ki_term', mode: 'lines'},
|
|
{ x: t, y: data.Kd_term, name: 'Kd_term', mode: 'lines'}
|
|
|
|
], layout_speed);
|
|
}
|
|
|
|
setInterval(fetchData, 50); // 20 Hz update
|
|
|
|
async function setSetpoint() {
|
|
let sp = parseFloat(document.getElementById("setpointInput").value);
|
|
let enabled = document.getElementById("setpointEnable").checked;
|
|
await fetch('/setpoint', {
|
|
method: "POST",
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ setpoint: sp, enabled: enabled })
|
|
});
|
|
}
|
|
|
|
async function setSpeed() {
|
|
let sp = parseFloat(document.getElementById("speedInput").value);
|
|
await fetch('/speed', {
|
|
method: "POST",
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ speed_sp: sp })
|
|
});
|
|
}
|
|
|
|
async function sendPID() {
|
|
let kp = parseFloat(document.getElementById("kpInput").value);
|
|
let ki = parseFloat(document.getElementById("kiInput").value);
|
|
let tol = parseFloat(document.getElementById("kdInput").value);
|
|
await fetch('/pid', {
|
|
method: "POST",
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ kp: kp, ki: ki, tol: tol })
|
|
});
|
|
}
|
|
|
|
async function startRecording() {
|
|
await fetch('/start_recording', { method: 'POST' });
|
|
}
|
|
|
|
async function startPRBS() {
|
|
await fetch('/prbs', { method: 'POST' });
|
|
}
|
|
|
|
async function stopRecording() {
|
|
const res = await fetch('/stop_recording', { method: 'POST' });
|
|
const data = await res.json();
|
|
alert("Recording stopped. CSV saved at:\n" + data.file);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|