Add relay enable toggles to WebUI
This commit is contained in:
+1
-1
@@ -396,7 +396,7 @@ Fields:
|
|||||||
|---|---:|---|
|
|---|---:|---|
|
||||||
| `id` | Yes | `relay_1` or `relay_2` |
|
| `id` | Yes | `relay_1` or `relay_2` |
|
||||||
| `name` | No | User display name |
|
| `name` | No | User display name |
|
||||||
| `enabled` | No | Configured enabled state |
|
| `enabled` | No | Configured enabled state. When set to `false`, the relay output is forced OFF and future relay control requests are rejected with `relay_disabled`. |
|
||||||
|
|
||||||
Returns the updated config.
|
Returns the updated config.
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ HTTP endpoint:
|
|||||||
|
|
||||||
POST /api/v1/config/relay
|
POST /api/v1/config/relay
|
||||||
|
|
||||||
|
Disabled relays are forced OFF immediately, hidden from normal dashboard controls, and protected from `/api/v1/relay/set` commands until re-enabled.
|
||||||
|
|
||||||
## Temperature Sensor Configuration
|
## Temperature Sensor Configuration
|
||||||
|
|
||||||
Temperature sensors use generic firmware IDs:
|
Temperature sensors use generic firmware IDs:
|
||||||
|
|||||||
@@ -1249,11 +1249,20 @@ function renderConfigControls(data){
|
|||||||
<div class="configrow">
|
<div class="configrow">
|
||||||
<label>${r.id}</label>
|
<label>${r.id}</label>
|
||||||
<input id="relayName${i}" placeholder="Relay name" oninput="markConfigFieldTouched('relayName${i}')">
|
<input id="relayName${i}" placeholder="Relay name" oninput="markConfigFieldTouched('relayName${i}')">
|
||||||
<label>${r.enabled?"Enabled":"Disabled"}</label>
|
<label class="tempCheck">
|
||||||
|
<input id="relayEnabled${i}" type="checkbox" onchange="markConfigFieldTouched('relayEnabled${i}')">
|
||||||
|
Enabled
|
||||||
|
</label>
|
||||||
</div>`).join("");
|
</div>`).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
relays.forEach((r,i)=>setConfigValue("relayName"+i,r.name||""));
|
relays.forEach((r,i)=>{
|
||||||
|
setConfigValue("relayName"+i,r.name||"");
|
||||||
|
const enabled=$("relayEnabled"+i);
|
||||||
|
if(enabled && !configTouched["relayEnabled"+i] && document.activeElement!==enabled){
|
||||||
|
enabled.checked=!!r.enabled;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigValue("tempEnabledCount", temps.filter(t=>t.enabled).length);
|
setConfigValue("tempEnabledCount", temps.filter(t=>t.enabled).length);
|
||||||
@@ -1359,8 +1368,8 @@ async function saveRelayConfig(){
|
|||||||
const relays=data.config?.relays||[];
|
const relays=data.config?.relays||[];
|
||||||
|
|
||||||
for(let i=0;i<relays.length;i++){
|
for(let i=0;i<relays.length;i++){
|
||||||
const name=$("relayName"+i)?.value.trim();
|
const name=$("relayName"+i)?.value.trim() || relays[i].name || relays[i].id;
|
||||||
if(!name) continue;
|
const enabled=$("relayEnabled"+i)?.checked ?? relays[i].enabled;
|
||||||
|
|
||||||
await fetch(api("/config/relay"),{
|
await fetch(api("/config/relay"),{
|
||||||
method:"POST",
|
method:"POST",
|
||||||
@@ -1368,14 +1377,14 @@ async function saveRelayConfig(){
|
|||||||
body:JSON.stringify({
|
body:JSON.stringify({
|
||||||
id:relays[i].id,
|
id:relays[i].id,
|
||||||
name:name,
|
name:name,
|
||||||
enabled:relays[i].enabled
|
enabled:enabled
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch(api("/config/save"),{method:"POST"});
|
await fetch(api("/config/save"),{method:"POST"});
|
||||||
await load();
|
await load();
|
||||||
alert("Relay names saved");
|
alert("Relay settings saved");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveTempConfig(){
|
async function saveTempConfig(){
|
||||||
@@ -2643,9 +2652,19 @@ void sendError(Stream& output, const char* message) {
|
|||||||
|
|
||||||
int findRelayConfigIndex(const String& id);
|
int findRelayConfigIndex(const String& id);
|
||||||
|
|
||||||
|
bool relayExistsAndAvailable(const String& relayId) {
|
||||||
|
int index = findRelayConfigIndex(relayId);
|
||||||
|
return index >= 0 && appConfig.relays[index].available;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool relayIsEnabled(const String& relayId) {
|
||||||
|
int index = findRelayConfigIndex(relayId);
|
||||||
|
return index >= 0 && appConfig.relays[index].enabled;
|
||||||
|
}
|
||||||
|
|
||||||
bool setRelayById(const String& relayId, bool enabled) {
|
bool setRelayById(const String& relayId, bool enabled) {
|
||||||
int index = findRelayConfigIndex(relayId);
|
int index = findRelayConfigIndex(relayId);
|
||||||
if (index < 0 || !appConfig.relays[index].available) {
|
if (index < 0 || !appConfig.relays[index].available || !appConfig.relays[index].enabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3651,7 +3670,13 @@ void handleUpdateRelayConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (doc["name"].is<String>()) appConfig.relays[index].name = doc["name"].as<String>();
|
if (doc["name"].is<String>()) appConfig.relays[index].name = doc["name"].as<String>();
|
||||||
if (doc["enabled"].is<bool>()) appConfig.relays[index].enabled = doc["enabled"].as<bool>();
|
if (doc["enabled"].is<bool>()) {
|
||||||
|
appConfig.relays[index].enabled = doc["enabled"].as<bool>();
|
||||||
|
if (!appConfig.relays[index].enabled) {
|
||||||
|
relays.state[index] = false;
|
||||||
|
updateRelayOutputs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendOkConfig();
|
sendOkConfig();
|
||||||
}
|
}
|
||||||
@@ -3994,10 +4019,18 @@ void handleSetRelayPost() {
|
|||||||
bool state = doc["state"] | false;
|
bool state = doc["state"] | false;
|
||||||
|
|
||||||
unsigned long applyStartMs = millis();
|
unsigned long applyStartMs = millis();
|
||||||
if (!setRelayById(relayId, state)) {
|
if (!relayExistsAndAvailable(relayId)) {
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!relayIsEnabled(relayId)) {
|
||||||
|
server.send(409, "application/json", "{\"ok\":false,\"error\":\"relay_disabled\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!setRelayById(relayId, state)) {
|
||||||
|
server.send(409, "application/json", "{\"ok\":false,\"error\":\"relay_disabled\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
unsigned long appliedMs = millis() - applyStartMs;
|
unsigned long appliedMs = millis() - applyStartMs;
|
||||||
|
|
||||||
DynamicJsonDocument response(512);
|
DynamicJsonDocument response(512);
|
||||||
@@ -4052,10 +4085,18 @@ void handleGenericRelayRoute() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setRelayById(relayId, enabled)) {
|
if (!relayExistsAndAvailable(relayId)) {
|
||||||
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
server.send(404, "application/json", "{\"ok\":false,\"error\":\"unknown_relay\"}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!relayIsEnabled(relayId)) {
|
||||||
|
server.send(409, "application/json", "{\"ok\":false,\"error\":\"relay_disabled\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!setRelayById(relayId, enabled)) {
|
||||||
|
server.send(409, "application/json", "{\"ok\":false,\"error\":\"relay_disabled\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(512);
|
DynamicJsonDocument doc(512);
|
||||||
doc["ok"] = true;
|
doc["ok"] = true;
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ def test_relay_set_contract_fixtures_and_firmware_errors():
|
|||||||
source = firmware_source()
|
source = firmware_source()
|
||||||
assert '"invalid_json"' in source
|
assert '"invalid_json"' in source
|
||||||
assert '"unknown_relay"' in source
|
assert '"unknown_relay"' in source
|
||||||
|
assert "relay_disabled" in source
|
||||||
|
assert "relayIsEnabled" in source
|
||||||
|
|
||||||
|
|
||||||
def test_relay_api_is_output_count_agnostic():
|
def test_relay_api_is_output_count_agnostic():
|
||||||
@@ -259,6 +261,7 @@ def test_relay_api_is_output_count_agnostic():
|
|||||||
assert 'relay["hardware_channel"] = appConfig.relays[i].hardwareChannel;' in source
|
assert 'relay["hardware_channel"] = appConfig.relays[i].hardwareChannel;' in source
|
||||||
assert 'relay["available"] = appConfig.relays[i].available;' in source
|
assert 'relay["available"] = appConfig.relays[i].available;' in source
|
||||||
assert 'relays.state[index] = enabled;' in source
|
assert 'relays.state[index] = enabled;' in source
|
||||||
|
assert '!appConfig.relays[index].enabled' in source
|
||||||
assert 'relays.relay1' not in source
|
assert 'relays.relay1' not in source
|
||||||
assert 'relays.relay2' not in source
|
assert 'relays.relay2' not in source
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user