Add unit tests and routes for department weather targets, including GET/PUT endpoints, thresholds validation, and aggregate status handling. Extend OpenAPI spec with schema mappings for department weather targets and machine relay endpoints.
This commit is contained in:
@@ -5,4 +5,6 @@ namespace modules\selfserve\helpers;
|
||||
enum selfserve_lane_relay
|
||||
{
|
||||
case MACHINE; // Relay that controls the machine power
|
||||
case MACHINE_PROGRAM_PICKER; // Relay that controls the machine program picker
|
||||
case MACHINE_CLEANER; // Relay that controls the machine cleaner
|
||||
}
|
||||
|
||||
@@ -358,6 +358,8 @@ Purpose: operational lane control and relay management.
|
||||
| `GET /modules/self-serve/lane/status` | optional `lane_id`, default `1` | `modules_selfserve_lane_status_view` | Returns lane status, mode, state, wash timer, reg, and customer number. |
|
||||
| `POST /modules/self-serve/lane/command` | `lane_id`, `command` | `modules_selfserve_lane_command_execute` plus command-specific permission | Valid commands: `START`, `STOP`, `RESET`, `RESERVE`, `RELEASE`. |
|
||||
| `POST /modules/self-serve/lane/services/allowed` | `lane_id`, optional `task_ids` | `modules_selfserve_lane_services_set_allowed` | Writes allowed service names to the lane cache. |
|
||||
| `GET /modules/self-serve/lane/relay/machine/status` | `lane_id` | `modules_selfserve_lane_relay_machine_status_view` | Reads the Shelly MACHINE relay state (`on`/`off`) for the lane. |
|
||||
| `POST /modules/self-serve/lane/relay/machine/set` | `lane_id`, `on` | `modules_selfserve_lane_relay_machine_status_set` | Sets Shelly MACHINE relay state directly (`on=true/false`) and returns updated status. |
|
||||
| `POST /modules/self-serve/lane/relay/machine/enable` | `lane_id`, optional `duration` | `modules_selfserve_lane_relay_enable_machine` | Manual enable, still gated by allowed services. |
|
||||
| `POST /modules/self-serve/lane/force/machine/enable` | `lane_id`, optional `duration`, optional `license_plate` | `modules_selfserve_lane_force_machine_enable` | Bypasses service gating and marks the lane as in wash. |
|
||||
| `POST /modules/self-serve/lane/force/machine/disable` | `lane_id`, optional `license_plate` | `modules_selfserve_lane_force_machine_disable` | Keeps the lane in wash but turns the machine relay off. |
|
||||
|
||||
+219
-28
@@ -15,9 +15,190 @@ use modules\shelly\helpers\shelly_request_body_get_states;
|
||||
|
||||
trait selfserve_lane_relay_controller_t
|
||||
{
|
||||
/**
|
||||
* Get current MACHINE relay status from Shelly.
|
||||
* @return array{relay_id: string, online: bool, on: bool}
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMachineRelayStatus(): array
|
||||
{
|
||||
return $this->getRelayStatus(selfserve_lane_relay::MACHINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current MACHINE_PROGRAM_PICKER relay status from Shelly.
|
||||
* @return array{relay_id: string, online: bool, on: bool}
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMachineProgramPickerRelayStatus(): array
|
||||
{
|
||||
return $this->getRelayStatus(selfserve_lane_relay::MACHINE_PROGRAM_PICKER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current MACHINE_CLEANER relay status from Shelly.
|
||||
* @return array{relay_id: string, online: bool, on: bool}
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMachineCleanerRelayStatus(): array
|
||||
{
|
||||
return $this->getRelayStatus(selfserve_lane_relay::MACHINE_CLEANER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current relay status from Shelly for a specific relay type.
|
||||
* @param selfserve_lane_relay $relay
|
||||
* @return array{relay_id: string, online: bool, on: bool}
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getRelayStatus(selfserve_lane_relay $relay): array
|
||||
{
|
||||
$relay_id = $this->getRelayId($relay);
|
||||
$devices = $this->fetchRelaySwitches($relay_id);
|
||||
if (count($devices) < 1) {
|
||||
throw new \Exception("No Shelly device state returned for {$relay->name} relay");
|
||||
}
|
||||
|
||||
$device = $devices[0];
|
||||
return [
|
||||
'relay_id' => $relay_id,
|
||||
'online' => isset($device->online) && (int)$device->online === 1,
|
||||
'on' => $this->extractRelayOnState($device, $relay),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MACHINE relay status directly.
|
||||
* @param bool $on true to turn on, false to turn off
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setMachineRelayStatus(bool $on): bool
|
||||
{
|
||||
return $this->setRelayStatus(selfserve_lane_relay::MACHINE, $on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MACHINE_PROGRAM_PICKER relay status directly.
|
||||
* @param bool $on true to turn on, false to turn off
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setMachineProgramPickerRelayStatus(bool $on): bool
|
||||
{
|
||||
return $this->setRelayStatus(selfserve_lane_relay::MACHINE_PROGRAM_PICKER, $on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MACHINE_CLEANER relay status directly.
|
||||
* @param bool $on true to turn on, false to turn off
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setMachineCleanerRelayStatus(bool $on): bool
|
||||
{
|
||||
return $this->setRelayStatus(selfserve_lane_relay::MACHINE_CLEANER, $on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set relay status directly for a specific relay type.
|
||||
* @param selfserve_lane_relay $relay
|
||||
* @param bool $on true to turn on, false to turn off
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setRelayStatus(selfserve_lane_relay $relay, bool $on): bool
|
||||
{
|
||||
return $on
|
||||
? $this->forceTurnOnRelay($relay)
|
||||
: $this->forceTurnOffRelay($relay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve relay ID for the current lane.
|
||||
* @param selfserve_lane_relay $relay
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getRelayId(selfserve_lane_relay $relay): string
|
||||
{
|
||||
if (empty($this->department_lane)) {
|
||||
throw new \Exception("Department lane object not found for lane ID {$this->id}");
|
||||
}
|
||||
$relay_id = match ($relay) {
|
||||
selfserve_lane_relay::MACHINE => (string)$this->department_lane->relay_machine_id->value(),
|
||||
selfserve_lane_relay::MACHINE_PROGRAM_PICKER => (string)$this->department_lane->relay_machine_program_picker_id->value(),
|
||||
selfserve_lane_relay::MACHINE_CLEANER => (string)$this->department_lane->relay_machine_cleaner_id->value(),
|
||||
default => throw new \Exception("Invalid relay type: {$relay->name}"),
|
||||
};
|
||||
if ($relay_id === '') {
|
||||
throw new \Exception("Invalid relay ID for {$relay->name} relay");
|
||||
}
|
||||
return $relay_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Shelly switch state objects for a relay ID.
|
||||
* @param string $relay_id
|
||||
* @return array<shelly_device_switch>
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function fetchRelaySwitches(string $relay_id): array
|
||||
{
|
||||
$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);
|
||||
|
||||
if (is_object($result)) {
|
||||
$result = [$result];
|
||||
}
|
||||
if (!is_array($result)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($device) {
|
||||
return (new shelly_device_switch())->populate($device);
|
||||
}, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract boolean on/off status from a Shelly switch state object.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function extractRelayOnState(shelly_device_switch $device, selfserve_lane_relay $relay): bool
|
||||
{
|
||||
if (isset($device->on)) {
|
||||
return (bool)$device->on;
|
||||
}
|
||||
if (!isset($device->status) || !is_object($device->status)) {
|
||||
throw new \Exception('Missing status payload from Shelly response');
|
||||
}
|
||||
|
||||
$status = (array)$device->status;
|
||||
foreach (['switch:0', 'switch_0', 'switch0'] as $switch_key) {
|
||||
if (!array_key_exists($switch_key, $status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$switch_state = $status[$switch_key];
|
||||
if (is_object($switch_state) && isset($switch_state->output)) {
|
||||
return (bool)$switch_state->output;
|
||||
}
|
||||
if (is_array($switch_state) && array_key_exists('output', $switch_state)) {
|
||||
return (bool)$switch_state['output'];
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Exception("Unable to determine {$relay->name} relay state from Shelly status payload");
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on the lane relay
|
||||
* @param selfserve_lane_relay $relay The relay to turn on (MACHINE)
|
||||
* @param selfserve_lane_relay $relay The relay to turn on (MACHINE or MACHINE_PROGRAM_PICKER)
|
||||
* @parm int|null $duration The duration in seconds to keep the relay on (optional)
|
||||
* @return bool True if the relay was successfully turned on
|
||||
* @throws \Exception If an invalid relay is specified or if the lane is not in a state to turn on the relay
|
||||
@@ -30,7 +211,8 @@ trait selfserve_lane_relay_controller_t
|
||||
if ($this->getLaneStatus()->equals(selfserve_lane_status::CLOSED)) throw new \Exception("Cannot turn on relay on CLOSED lane");
|
||||
if ($this->getLaneStatus()->equals(selfserve_lane_status::MAINTENANCE)) throw new \Exception("Cannot turn on relay on MAINTENANCE lane");
|
||||
if ($this->getLaneStatus()->equals(selfserve_lane_status::FAULT)) throw new \Exception("Cannot turn on relay on FAULT lane");
|
||||
// Enforce that MACHINE relay can only be enabled when allowed by current self-serve tasks (self-serve, manual trigger required)
|
||||
|
||||
// Gating
|
||||
if ($relay === selfserve_lane_relay::MACHINE) {
|
||||
// Allowed services are stored as an array of names in lane cache
|
||||
$allowed = $this->getLaneCache($this->id, self::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES);
|
||||
@@ -38,15 +220,10 @@ trait selfserve_lane_relay_controller_t
|
||||
throw new \Exception("MACHINE relay is not allowed to be enabled at this time");
|
||||
}
|
||||
}
|
||||
|
||||
// Get the relay ID based on the relay type
|
||||
$relay_id = match ($relay) {
|
||||
selfserve_lane_relay::MACHINE => $this->department_lane->relay_machine_id->value(),
|
||||
default => throw new \Exception("Invalid relay specified, must be MACHINE"),
|
||||
};
|
||||
// Make sure relay ID is valid
|
||||
if (empty($relay_id)) {
|
||||
throw new \Exception("Invalid relay ID for relay {$relay->name}");
|
||||
}
|
||||
$relay_id = $this->getRelayId($relay);
|
||||
|
||||
$shelly = new shelly();
|
||||
$shelly->requireModuleEnabled();
|
||||
$shelly->requireValidSecretKey();
|
||||
@@ -77,6 +254,18 @@ trait selfserve_lane_relay_controller_t
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function forceTurnOnMachineRelay(?int $duration = null): bool
|
||||
{
|
||||
return $this->forceTurnOnRelay(selfserve_lane_relay::MACHINE, $duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force turn on a specific relay, bypassing allowed services gating.
|
||||
* @param selfserve_lane_relay $relay
|
||||
* @param int|null $duration Optional auto-off duration in seconds
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function forceTurnOnRelay(selfserve_lane_relay $relay, ?int $duration = null): bool
|
||||
{
|
||||
// Require department lane object
|
||||
if (empty($this->department_lane)) throw new \Exception("Department lane object not found for lane ID {$this->id}");
|
||||
@@ -85,10 +274,8 @@ trait selfserve_lane_relay_controller_t
|
||||
if ($this->getLaneStatus()->equals(selfserve_lane_status::MAINTENANCE)) throw new \Exception("Cannot turn on relay on MAINTENANCE lane");
|
||||
if ($this->getLaneStatus()->equals(selfserve_lane_status::FAULT)) throw new \Exception("Cannot turn on 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");
|
||||
}
|
||||
$relay_id = $this->getRelayId($relay);
|
||||
|
||||
$shelly = new shelly();
|
||||
$shelly->requireModuleEnabled();
|
||||
$shelly->requireValidSecretKey();
|
||||
@@ -118,6 +305,17 @@ trait selfserve_lane_relay_controller_t
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function forceTurnOffMachineRelay(): bool
|
||||
{
|
||||
return $this->forceTurnOffRelay(selfserve_lane_relay::MACHINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force turn off a specific relay, bypassing allowed services gating.
|
||||
* @param selfserve_lane_relay $relay
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function forceTurnOffRelay(selfserve_lane_relay $relay): bool
|
||||
{
|
||||
// Require department lane object
|
||||
if (empty($this->department_lane)) throw new \Exception("Department lane object not found for lane ID {$this->id}");
|
||||
@@ -126,10 +324,8 @@ trait selfserve_lane_relay_controller_t
|
||||
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");
|
||||
}
|
||||
$relay_id = $this->getRelayId($relay);
|
||||
|
||||
$shelly = new shelly();
|
||||
$shelly->requireModuleEnabled();
|
||||
$shelly->requireValidSecretKey();
|
||||
@@ -152,7 +348,7 @@ trait selfserve_lane_relay_controller_t
|
||||
|
||||
/**
|
||||
* Turn off the lane relay
|
||||
* @param selfserve_lane_relay $relay The relay to turn off (MACHINE)
|
||||
* @param selfserve_lane_relay $relay The relay to turn off (MACHINE or MACHINE_PROGRAM_PICKER)
|
||||
* @return bool True if the relay was successfully turned off
|
||||
* @throws \Exception If an invalid relay is specified or if the lane is not in a state to turn off the relay
|
||||
*/
|
||||
@@ -164,15 +360,10 @@ trait selfserve_lane_relay_controller_t
|
||||
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");
|
||||
|
||||
// Get the relay ID based on the relay type
|
||||
$relay_id = match ($relay) {
|
||||
selfserve_lane_relay::MACHINE => $this->department_lane->relay_machine_id->value(),
|
||||
default => throw new \Exception("Invalid relay specified, must be MACHINE"),
|
||||
};
|
||||
// Make sure relay ID is valid
|
||||
if (empty($relay_id)) {
|
||||
throw new \Exception("Invalid relay ID for relay {$relay->name}");
|
||||
}
|
||||
$relay_id = $this->getRelayId($relay);
|
||||
|
||||
$shelly = new shelly();
|
||||
$shelly->requireModuleEnabled();
|
||||
$shelly->requireValidSecretKey();
|
||||
@@ -192,4 +383,4 @@ trait selfserve_lane_relay_controller_t
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user