Enhance MACHINE relay control logic and add emergency override functionality

- Add `forceTurnOffMachineRelay` method for superuser/emergency operations to bypass gating restrictions.
- Enforce `toggle_after = 0` to prevent auto-toggle behavior.
- Update routes to explicitly reflect relay state when modifying MACHINE relay.
- Extend Shelly device switch handling with optional `skip_toggle_after` parameter.
This commit is contained in:
Jeppe Bundgaard
2026-02-19 11:37:16 +01:00
parent aaaaedc70c
commit 6d09e5449e
4 changed files with 57 additions and 6 deletions
@@ -17,4 +17,9 @@ enum selfserve_lane_state
case FAULT; // The lane is in a fault state
case MAINTENANCE; // The lane is under maintenance
case CLOSED; // The lane is closed
public function equals(selfserve_lane_state $state): bool
{
return $this === $state;
}
}
@@ -62,7 +62,8 @@ trait selfserve_lane_relay_controller_t
}, $result);
// Turn on the switch
foreach ($result as $device) {
if ($duration !== null) $device->toggle_after = $duration; // Set the duration to turn off the relay (if specified)
// Ensure machine switches never auto-toggle off; enforce toggle_after = 0
$device->toggle_after = 0;
$device->switch(true);
}
return true;
@@ -103,12 +104,52 @@ trait selfserve_lane_relay_controller_t
}, $result);
// Turn on the switch
foreach ($result as $device) {
if ($duration !== null) $device->toggle_after = $duration;
// Ensure machine switches never auto-toggle off; enforce toggle_after = 0
$device->toggle_after = 0;
$device->switch(true);
}
return true;
}
/**
* Force turn off the MACHINE relay, bypassing allowed services gating.
* Intended for superuser/emergency operations.
* @return bool
* @throws \Exception
*/
public function forceTurnOffMachineRelay(): bool
{
// Require department lane object
if (empty($this->department_lane)) throw new \Exception("Department lane object not found for lane ID {$this->id}");
// Basic sanity checks on lane status (still disallow clearly invalid states)
if ($this->getLaneStatus()->equals(selfserve_lane_status::CLOSED)) throw new \Exception("Cannot turn off relay on CLOSED lane");
if ($this->getLaneStatus()->equals(selfserve_lane_status::MAINTENANCE)) throw new \Exception("Cannot turn off relay on MAINTENANCE lane");
if ($this->getLaneStatus()->equals(selfserve_lane_status::FAULT)) throw new \Exception("Cannot turn off relay on FAULT lane");
// Directly control Shelly without checking allowed services
$relay_id = $this->department_lane->relay_machine_id->value();
if (empty($relay_id)) {
throw new \Exception("Invalid relay ID for MACHINE relay");
}
$shelly = new shelly();
$shelly->requireModuleEnabled();
$shelly->requireValidSecretKey();
$parameters = new shelly_request_body_get_states();
$parameters->ids = [$relay_id];
$parameters->select = ['status'];
$result = $shelly->sendPostRequest('/v2/devices/api/get', (array)$parameters);
// Wait 1 second
sleep(1);
// Format the result
$result = array_map(function ($device) {
return (new shelly_device_switch())->populate($device);
}, $result);
// Turn off the switch
foreach ($result as $device) {
$device->switch(false);
}
return true;
}
/**
* Turn off the lane relay
* @param selfserve_lane_relay $relay The relay to turn off (MACHINE)
@@ -35,16 +35,17 @@ class shelly_device_switch extends shelly_device_state
/**
* Send a request to the shelly API to switch the output
* @param bool $on
* @param bool|null $skip_toggle_after Whether to prevent the toggle_after parameter from being sent (if null, will use the value of $this->toggle_after)
* @return array|object|null
* @throws Exception
*/
public function switch(bool $on): array|object|null
public function switch(bool $on, ?bool $skip_toggle_after = false): array|object|null
{
$parameters = [
'id' => (string)$this->id,
'channel' => (int)$this->channel,
'on' => $on,
'toggle_after' => (int)$this->toggle_after,
'toggle_after' => $skip_toggle_after ? 0 : (int)$this->toggle_after,
];
return (new shelly())->sendPostRequest('/v2/devices/api/set/switch', $parameters);
}
@@ -241,6 +241,8 @@ class moduleSelfServeRoute
}
// Force enable the machine relay (bypass gating)
$lane->forceTurnOnMachineRelay($duration);
// Reflect relay state explicitly
try { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_ON); } catch (\Throwable $ignored) {}
$response->success([
'lane_id' => $lane_id,
'forced' => true,
@@ -283,8 +285,10 @@ class moduleSelfServeRoute
if ((int)$lane->getWashStartTime() <= 0) {
$lane->setWashStartTime(time());
}
// Turn off the machine relay if currently on
try { $lane->turnOffRelay(\modules\selfserve\helpers\selfserve_lane_relay::MACHINE); } catch (\Throwable $ignored) {}
// Turn off the machine relay (do not swallow errors)
$lane->forceTurnOffMachineRelay();
// Reflect relay state explicitly
try { $lane->setLaneState(\modules\selfserve\helpers\selfserve_lane_state::MACHINE_RELAY_OFF); } catch (\Throwable $ignored) {}
$response->success([
'lane_id' => $lane_id,
'forced' => true,