refactor: drop firmware-version display, move Enable toggle to Controller
Firmware version is already printed to the log at connect() time (HendiCtrl/Pololu1376) - remove the live GUI/websocket firmware reporting entirely (Connectable, HeaterHendi/StirrerPololu1376, Heater/StirrerTask, web GUI) as redundant. Also move the Heater panel's Closed-loop checkbox into the Controller panel and rename it "Enable" - the Controller panel now shows a live Enabled (green) / Disabled (gray) status badge next to its heading, matching the Connected/Disconnected badges on the Heater/ Stirrer panels. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
This commit is contained in:
@@ -23,13 +23,11 @@ class HeaterHendi(AHeater):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
ok = self.hendi.connect()
|
ok = self.hendi.connect()
|
||||||
self.connected = ok
|
self.connected = ok
|
||||||
self.firmware_version = self.hendi.sw_ver if ok else None
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
self.hendi.disconnect()
|
self.hendi.disconnect()
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.firmware_version = None
|
|
||||||
|
|
||||||
def activate(self, enable):
|
def activate(self, enable):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
|
|||||||
@@ -23,13 +23,11 @@ class StirrerPololu1376(AStirrer):
|
|||||||
def connect(self):
|
def connect(self):
|
||||||
ok = self.drv.connect()
|
ok = self.drv.connect()
|
||||||
self.connected = ok
|
self.connected = ok
|
||||||
self.firmware_version = self.drv.firmware_version if ok else None
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
self.drv.disconnect()
|
self.drv.disconnect()
|
||||||
self.connected = False
|
self.connected = False
|
||||||
self.firmware_version = None
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def remote_open(self):
|
def remote_open(self):
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ from utils.value import AttributeChange
|
|||||||
class Connectable(AttributeChange):
|
class Connectable(AttributeChange):
|
||||||
"""Observable hardware-connection state shared by AHeater/AStirrer.
|
"""Observable hardware-connection state shared by AHeater/AStirrer.
|
||||||
|
|
||||||
Simulated devices are always connected and report no firmware version;
|
Simulated devices are always connected; real (serial) devices start
|
||||||
real (serial) devices start disconnected and only flip once connect()
|
disconnected and only flip once connect() actually reaches the
|
||||||
actually reaches the hardware.
|
hardware.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, simulated):
|
def __init__(self, simulated):
|
||||||
AttributeChange.__init__(self)
|
AttributeChange.__init__(self)
|
||||||
self.simulated = simulated
|
self.simulated = simulated
|
||||||
self.connected = simulated
|
self.connected = simulated
|
||||||
self.firmware_version = None
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
return self.connected
|
return self.connected
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class HeaterTask(ATask):
|
|||||||
self._on_connected_changed = None
|
self._on_connected_changed = None
|
||||||
device.set_on_changed('power_eff', ChangedInteger(self.on_changed_power).set)
|
device.set_on_changed('power_eff', ChangedInteger(self.on_changed_power).set)
|
||||||
device.set_on_changed('connected', self.on_connected_changed)
|
device.set_on_changed('connected', self.on_connected_changed)
|
||||||
device.set_on_changed('firmware_version', self.on_firmware_version_changed)
|
|
||||||
self.power_set_changed = ChangedInteger(self.on_changed_power_set).set
|
self.power_set_changed = ChangedInteger(self.on_changed_power_set).set
|
||||||
|
|
||||||
def set_on_closed_loop_changed(self, callback):
|
def set_on_closed_loop_changed(self, callback):
|
||||||
@@ -38,9 +37,6 @@ class HeaterTask(ATask):
|
|||||||
if self._on_connected_changed:
|
if self._on_connected_changed:
|
||||||
self._on_connected_changed(value)
|
self._on_connected_changed(value)
|
||||||
|
|
||||||
def on_firmware_version_changed(self, value):
|
|
||||||
fire_and_forget(self.send({'FirmwareVersion': value}))
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
"""Called when the brew ends (DONE/IDLE). Switches to open-loop at
|
"""Called when the brew ends (DONE/IDLE). Switches to open-loop at
|
||||||
power 0 and disables TC, mirroring what a Stop press should do."""
|
power 0 and disables TC, mirroring what a Stop press should do."""
|
||||||
@@ -125,7 +121,6 @@ class HeaterTask(ATask):
|
|||||||
# fire and a freshly-subscribed client would never learn it's
|
# fire and a freshly-subscribed client would never learn it's
|
||||||
# connected.
|
# connected.
|
||||||
await self.send({'Connected': self.device.connected})
|
await self.send({'Connected': self.device.connected})
|
||||||
await self.send({'FirmwareVersion': self.device.firmware_version})
|
|
||||||
|
|
||||||
with self.device.open():
|
with self.device.open():
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ class StirrerTask(ATask):
|
|||||||
stirrer_device.set_on_changed("dutyCycle", self.on_dutycycle_changed)
|
stirrer_device.set_on_changed("dutyCycle", self.on_dutycycle_changed)
|
||||||
stirrer_device.set_on_changed("cycleTime", self.on_cycletime_changed)
|
stirrer_device.set_on_changed("cycleTime", self.on_cycletime_changed)
|
||||||
stirrer_device.set_on_changed("connected", self.on_connected_changed)
|
stirrer_device.set_on_changed("connected", self.on_connected_changed)
|
||||||
stirrer_device.set_on_changed("firmware_version", self.on_firmware_version_changed)
|
|
||||||
|
|
||||||
def on_speed_changed(self, value):
|
def on_speed_changed(self, value):
|
||||||
asyncio.create_task(self.send({'Speed': value}))
|
asyncio.create_task(self.send({'Speed': value}))
|
||||||
@@ -38,9 +37,6 @@ class StirrerTask(ATask):
|
|||||||
if self._on_connected_changed:
|
if self._on_connected_changed:
|
||||||
self._on_connected_changed(value)
|
self._on_connected_changed(value)
|
||||||
|
|
||||||
def on_firmware_version_changed(self, value):
|
|
||||||
fire_and_forget(self.send({'FirmwareVersion': value}))
|
|
||||||
|
|
||||||
async def recv(self, data):
|
async def recv(self, data):
|
||||||
for pair in data.items():
|
for pair in data.items():
|
||||||
if pair[0] == 'Connect':
|
if pair[0] == 'Connect':
|
||||||
@@ -69,7 +65,6 @@ class StirrerTask(ATask):
|
|||||||
# for why a simulated device's connect() alone doesn't fire the
|
# for why a simulated device's connect() alone doesn't fire the
|
||||||
# observer.
|
# observer.
|
||||||
await self.send({'Connected': self.device.connected})
|
await self.send({'Connected': self.device.connected})
|
||||||
await self.send({'FirmwareVersion': self.device.firmware_version})
|
|
||||||
|
|
||||||
with self.device.open():
|
with self.device.open():
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
+23
-18
@@ -45,10 +45,8 @@ let closedLoop = true;
|
|||||||
// are always connected and never expose Connect/Disconnect (see
|
// are always connected and never expose Connect/Disconnect (see
|
||||||
// updateDeviceStatus()).
|
// updateDeviceStatus()).
|
||||||
let heaterConnected = false;
|
let heaterConnected = false;
|
||||||
let heaterFirmware = null;
|
|
||||||
let heaterSimulated = null;
|
let heaterSimulated = null;
|
||||||
let stirrerConnected = false;
|
let stirrerConnected = false;
|
||||||
let stirrerFirmware = null;
|
|
||||||
let stirrerSimulated = null;
|
let stirrerSimulated = null;
|
||||||
|
|
||||||
// Pot hardware config from server's Pot section - used for display before
|
// Pot hardware config from server's Pot section - used for display before
|
||||||
@@ -536,15 +534,14 @@ function updateSudActions() {
|
|||||||
document.getElementById('btn-sud-stop').disabled = !(running || paused);
|
document.getElementById('btn-sud-stop').disabled = !(running || paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared by onHeaterChanged/onStirrerChanged - updates the status badge,
|
// Shared by onHeaterChanged/onStirrerChanged - updates the status badge
|
||||||
// firmware label, and Connect/Disconnect buttons for one device. Simulated
|
// and Connect/Disconnect buttons for one device. Simulated devices are
|
||||||
// devices are always connected and have nothing to connect/disconnect, so
|
// always connected and have nothing to connect/disconnect, so their
|
||||||
// their buttons are hidden entirely rather than just disabled.
|
// buttons are hidden entirely rather than just disabled.
|
||||||
function updateDeviceStatus(prefix, isConnected, firmware, simulated) {
|
function updateDeviceStatus(prefix, isConnected, simulated) {
|
||||||
const statusEl = document.getElementById(`${prefix}-status`);
|
const statusEl = document.getElementById(`${prefix}-status`);
|
||||||
statusEl.textContent = isConnected ? 'Connected' : 'Disconnected';
|
statusEl.textContent = isConnected ? 'Connected' : 'Disconnected';
|
||||||
statusEl.className = `panel-status ${isConnected ? 'status-connected' : 'status-disconnected'}`;
|
statusEl.className = `panel-status ${isConnected ? 'status-connected' : 'status-disconnected'}`;
|
||||||
document.getElementById(`${prefix}-firmware`).textContent = firmware ? `F/W ${firmware}` : '';
|
|
||||||
const connectBtn = document.getElementById(`btn-${prefix}-connect`);
|
const connectBtn = document.getElementById(`btn-${prefix}-connect`);
|
||||||
const disconnectBtn = document.getElementById(`btn-${prefix}-disconnect`);
|
const disconnectBtn = document.getElementById(`btn-${prefix}-disconnect`);
|
||||||
connectBtn.classList.toggle('hidden', !!simulated);
|
connectBtn.classList.toggle('hidden', !!simulated);
|
||||||
@@ -553,6 +550,15 @@ function updateDeviceStatus(prefix, isConnected, firmware, simulated) {
|
|||||||
disconnectBtn.disabled = !isConnected;
|
disconnectBtn.disabled = !isConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reflects closedLoop (the Controller panel's Enable toggle) in the
|
||||||
|
// Controller heading's status badge - Enabled/green when the TC is
|
||||||
|
// driving the heater, Disabled/gray in open-loop.
|
||||||
|
function updateClosedLoopStatus() {
|
||||||
|
const statusEl = document.getElementById('closed-loop-status');
|
||||||
|
statusEl.textContent = closedLoop ? 'Enabled' : 'Disabled';
|
||||||
|
statusEl.className = `panel-status ${closedLoop ? 'status-connected' : 'status-disconnected'}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Mirrors client/brewpi_gui.py's show_user_message(): the Sud is already
|
// Mirrors client/brewpi_gui.py's show_user_message(): the Sud is already
|
||||||
// blocked in WAIT_USER server-side - dismissing this dialog (OK or Escape,
|
// blocked in WAIT_USER server-side - dismissing this dialog (OK or Escape,
|
||||||
// both fire the dialog's 'close' event) is what unblocks it.
|
// both fire the dialog's 'close' event) is what unblocks it.
|
||||||
@@ -632,6 +638,7 @@ function onHeaterChanged(msg) {
|
|||||||
} else if (key === 'ClosedLoop') {
|
} else if (key === 'ClosedLoop') {
|
||||||
closedLoop = msg.ClosedLoop;
|
closedLoop = msg.ClosedLoop;
|
||||||
document.getElementById('closed-loop').checked = closedLoop;
|
document.getElementById('closed-loop').checked = closedLoop;
|
||||||
|
updateClosedLoopStatus();
|
||||||
updateControlsEnabled();
|
updateControlsEnabled();
|
||||||
} else if (key === 'Capabilities') {
|
} else if (key === 'Capabilities') {
|
||||||
const power = msg.Capabilities.Power;
|
const power = msg.Capabilities.Power;
|
||||||
@@ -641,15 +648,12 @@ function onHeaterChanged(msg) {
|
|||||||
slider.max = power.Max;
|
slider.max = power.Max;
|
||||||
} else if (key === 'Connected') {
|
} else if (key === 'Connected') {
|
||||||
heaterConnected = msg.Connected;
|
heaterConnected = msg.Connected;
|
||||||
updateDeviceStatus('heater', heaterConnected, heaterFirmware, heaterSimulated);
|
updateDeviceStatus('heater', heaterConnected, heaterSimulated);
|
||||||
updateControlsEnabled();
|
updateControlsEnabled();
|
||||||
updateSudActions();
|
updateSudActions();
|
||||||
} else if (key === 'FirmwareVersion') {
|
|
||||||
heaterFirmware = msg.FirmwareVersion;
|
|
||||||
updateDeviceStatus('heater', heaterConnected, heaterFirmware, heaterSimulated);
|
|
||||||
} else if (key === 'Simulated') {
|
} else if (key === 'Simulated') {
|
||||||
heaterSimulated = msg.Simulated;
|
heaterSimulated = msg.Simulated;
|
||||||
updateDeviceStatus('heater', heaterConnected, heaterFirmware, heaterSimulated);
|
updateDeviceStatus('heater', heaterConnected, heaterSimulated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -669,15 +673,12 @@ function onStirrerChanged(msg) {
|
|||||||
slider.max = power.Max;
|
slider.max = power.Max;
|
||||||
} else if (key === 'Connected') {
|
} else if (key === 'Connected') {
|
||||||
stirrerConnected = msg.Connected;
|
stirrerConnected = msg.Connected;
|
||||||
updateDeviceStatus('stirrer', stirrerConnected, stirrerFirmware, stirrerSimulated);
|
updateDeviceStatus('stirrer', stirrerConnected, stirrerSimulated);
|
||||||
updateControlsEnabled();
|
updateControlsEnabled();
|
||||||
updateSudActions();
|
updateSudActions();
|
||||||
} else if (key === 'FirmwareVersion') {
|
|
||||||
stirrerFirmware = msg.FirmwareVersion;
|
|
||||||
updateDeviceStatus('stirrer', stirrerConnected, stirrerFirmware, stirrerSimulated);
|
|
||||||
} else if (key === 'Simulated') {
|
} else if (key === 'Simulated') {
|
||||||
stirrerSimulated = msg.Simulated;
|
stirrerSimulated = msg.Simulated;
|
||||||
updateDeviceStatus('stirrer', stirrerConnected, stirrerFirmware, stirrerSimulated);
|
updateDeviceStatus('stirrer', stirrerConnected, stirrerSimulated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -751,6 +752,7 @@ function onSudChanged(msg) {
|
|||||||
if (isRunningNow && !wasRunning) {
|
if (isRunningNow && !wasRunning) {
|
||||||
closedLoop = true;
|
closedLoop = true;
|
||||||
document.getElementById('closed-loop').checked = true;
|
document.getElementById('closed-loop').checked = true;
|
||||||
|
updateClosedLoopStatus();
|
||||||
sendMsg('Heater', {ClosedLoop: true});
|
sendMsg('Heater', {ClosedLoop: true});
|
||||||
// Accept the next Soll push from the server even if it
|
// Accept the next Soll push from the server even if it
|
||||||
// arrives before sudRunning has gone true at this client
|
// arrives before sudRunning has gone true at this client
|
||||||
@@ -886,6 +888,7 @@ function connect() {
|
|||||||
initialTempSollSync = true;
|
initialTempSollSync = true;
|
||||||
initialPotTempSync = true;
|
initialPotTempSync = true;
|
||||||
closedLoop = true;
|
closedLoop = true;
|
||||||
|
updateClosedLoopStatus();
|
||||||
potConfig = {};
|
potConfig = {};
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
for (const channel of CHANNELS) {
|
for (const channel of CHANNELS) {
|
||||||
@@ -937,6 +940,8 @@ document.getElementById('btn-connect').addEventListener('click', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('closed-loop').addEventListener('change', (e) => {
|
document.getElementById('closed-loop').addEventListener('change', (e) => {
|
||||||
|
closedLoop = e.target.checked;
|
||||||
|
updateClosedLoopStatus();
|
||||||
sendMsg('Heater', {ClosedLoop: e.target.checked});
|
sendMsg('Heater', {ClosedLoop: e.target.checked});
|
||||||
});
|
});
|
||||||
document.getElementById('btn-heater-connect').addEventListener('click', () => {
|
document.getElementById('btn-heater-connect').addEventListener('click', () => {
|
||||||
|
|||||||
+4
-6
@@ -38,7 +38,10 @@
|
|||||||
<button id="btn-manual-toggle" title="Collapse manual panel">◀</button>
|
<button id="btn-manual-toggle" title="Collapse manual panel">◀</button>
|
||||||
<div id="manual-panels">
|
<div id="manual-panels">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h3>Controller</h3>
|
<h3>Controller <span id="closed-loop-status" class="panel-status status-connected">Enabled</span></h3>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="closed-loop" checked> Enable
|
||||||
|
</label>
|
||||||
<label>Temperature [°C]
|
<label>Temperature [°C]
|
||||||
<div class="slider-row">
|
<div class="slider-row">
|
||||||
<input type="range" id="temp-soll" min="0" max="100" step="1">
|
<input type="range" id="temp-soll" min="0" max="100" step="1">
|
||||||
@@ -52,13 +55,9 @@
|
|||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h3>Heater <span id="heater-status" class="panel-status status-disconnected">Disconnected</span></h3>
|
<h3>Heater <span id="heater-status" class="panel-status status-disconnected">Disconnected</span></h3>
|
||||||
<div class="device-status-row">
|
<div class="device-status-row">
|
||||||
<span id="heater-firmware" class="device-firmware"></span>
|
|
||||||
<button id="btn-heater-connect">Connect</button>
|
<button id="btn-heater-connect">Connect</button>
|
||||||
<button id="btn-heater-disconnect" disabled>Disconnect</button>
|
<button id="btn-heater-disconnect" disabled>Disconnect</button>
|
||||||
</div>
|
</div>
|
||||||
<label class="checkbox-label">
|
|
||||||
<input type="checkbox" id="closed-loop" checked> Closed-loop
|
|
||||||
</label>
|
|
||||||
<label>Power [W]
|
<label>Power [W]
|
||||||
<div class="slider-row">
|
<div class="slider-row">
|
||||||
<input type="range" id="heater-power" min="0" max="100" step="1">
|
<input type="range" id="heater-power" min="0" max="100" step="1">
|
||||||
@@ -69,7 +68,6 @@
|
|||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h3>Stirrer <span id="stirrer-status" class="panel-status status-disconnected">Disconnected</span></h3>
|
<h3>Stirrer <span id="stirrer-status" class="panel-status status-disconnected">Disconnected</span></h3>
|
||||||
<div class="device-status-row">
|
<div class="device-status-row">
|
||||||
<span id="stirrer-firmware" class="device-firmware"></span>
|
|
||||||
<button id="btn-stirrer-connect">Connect</button>
|
<button id="btn-stirrer-connect">Connect</button>
|
||||||
<button id="btn-stirrer-disconnect" disabled>Disconnect</button>
|
<button id="btn-stirrer-disconnect" disabled>Disconnect</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -350,7 +350,6 @@ header#connection-bar {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
.device-status-row button { padding: 0.15em 0.6em; font-size: 0.9em; }
|
.device-status-row button { padding: 0.15em 0.6em; font-size: 0.9em; }
|
||||||
.device-firmware { color: var(--text-muted); }
|
|
||||||
|
|
||||||
/* --- Hidden --- */
|
/* --- Hidden --- */
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|||||||
Reference in New Issue
Block a user