Newer
Older
smart-home-server / automation / Scopes / SleepScope.php
<?php

namespace ControlScripts\Scopes;

use \SHServ\Middleware\ControlScripts;
use \SHServ\Implements\ControlScriptsInterface;

/**
 * Sleep mode: turns off all indoor lighting, blocks physical buttons (mute),
 * and guards against web-client toggles via regular script.
 * Outdoor spotlights are left untouched.
 */
class SleepScope extends ControlScripts implements ControlScriptsInterface {
	use \ControlScripts\Common;

	/** Indoor relays that must stay OFF while sleep is active. */
	protected const INDOOR_RELAYS = [
		"light_hub_1:0",
		"light_hub_1:1",
		"light_hub_1:2",
		"craft_table_lamp",
		"computer_table_lamp",
		"bathroom_2_light",
		"plants_room_light",
		"italy_lamp_relay",
		"floor_lamp_relay",
		"server_room_light",
		"fisrt_floor_big_relay:0",
		"fisrt_floor_big_relay:1",
		"fisrt_floor_big_relay:2",
		"fisrt_floor_big_relay:3",
		"fisrt_floor_big_relay:4",
	];

	public function register_sync_map(): void {
		$this -> register_global_device_sync_map();
	}

	public function register_events_handlers(): void {
		// No per-button event handlers needed; we mute channels in activate_sleep.
	}

	public function register_actions_scripts(): void {
		$this -> add_action_script([
			"alias" => "activate_sleep",
			"name" => "Режим сна",
			"description" => "Включить режим сна: выключить всё indoor-освещение и заблокировать кнопки",
			"author" => "Eugene Sukhodolskiy",
			"icon" => '<i class="ph ph-moon"></i>',
			"danger_level" => "cautious",
			"state_callback" => function() {
				$isSleep = $this -> mode() -> is('sleep');
				return [
					["label" => $isSleep ? "On" : "Off", "variant" => $isSleep ? "success" : "secondary"]
				];
			},
		], function($params) {
			$this -> mode() -> enable('sleep');

			// 1. Turn everything off immediately
			$results = $this -> helper() -> group_set_state(self::INDOOR_RELAYS, false, $this -> sync_map());

			// 2. Mute indoor button channels so physical presses are ignored
			$this -> set_indoor_buttons_mute(true);

			return [
				"mode_enabled" => true,
				"lights_turned_off" => $results,
			];
		});

		$this -> add_action_script([
			"alias" => "deactivate_sleep",
			"name" => "Выход из сна",
			"description" => "Отключить режим сна и разблокировать кнопки",
			"author" => "Eugene Sukhodolskiy",
			"icon" => '<i class="ph ph-sun"></i>',
			"danger_level" => "safe",
			"state_callback" => function() {
				$isSleep = $this -> mode() -> is('sleep');
				return [
					["label" => $isSleep ? "On" : "Off", "variant" => $isSleep ? "success" : "secondary"]
				];
			},
		], function($params) {
			$this -> mode() -> disable('sleep');

			// Unmute buttons and sync their indicators back to relay states
			$this -> set_indoor_buttons_mute(false);

			return [
				"mode_disabled" => true,
			];
		});
	}

	public function register_regular_scripts(): void {
		// Guard script: if sleep mode is active and someone turned a light on via
		// web client (or any other way), turn it back off immediately.
		$this -> add_regular_script([
			"alias" => "sleep_guard",
			"name" => "Sleep guard",
			"description" => "Принудительно гасит indoor-свет, пока активен режим сна",
			"author" => "Eugene Sukhodolskiy",
		], function() {
			if (!$this -> mode() -> is('sleep')) {
				return;
			}

			foreach (self::INDOOR_RELAYS as $target) {
				$parts = explode(":", $target, 2);
				$alias = $parts[0];
				$channel = isset($parts[1]) ? intval($parts[1]) : null;

				$relay = $this -> devices() -> by_alias($alias);
				if (!$relay) {
					continue;
				}

				$relay_api = $relay -> device_api();
				if (!($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay)) {
					continue;
				}

				$status = $relay_api -> get_status();
				$ch = $channel ?? 0;
				$state = $status["channels"][$ch]["state"] ?? "off";

				if ($state === "on") {
					if ($channel === null) {
						$relay_api -> set_state(false);
					} else {
						$relay_api -> set_channel_state(false, $channel);
					}
					$this -> helper() -> sync_relay_to_btns($this -> sync_map(), $alias);
				}
			}
		});
	}

	/**
	 * Mute or unmute button channels that are wired to INDOOR relays.
	 * When muted, physical button presses on the device are ignored.
	 * When unmuted, we restore enabled/disabled state from the linked relay.
	 */
	protected function set_indoor_buttons_mute(bool $mute): void {
		$indoor_map = [];
		foreach (self::INDOOR_RELAYS as $target) {
			$parts = explode(":", $target, 2);
			$indoor_map[$parts[0]] = isset($parts[1]) ? intval($parts[1]) : null;
		}

		$sync_map = $this -> sync_map();
		if (!isset($sync_map["connections"])) {
			return;
		}

		foreach ($sync_map["connections"] as $connection) {
			$has_indoor = false;
			$btn_entries = [];

			foreach ($connection as $entry) {
				$type = $entry["type"] ?? "";
				$alias = $entry["alias"] ?? "";
				$channel = $entry["channel"] ?? 0;

				if ($type === "relay" && isset($indoor_map[$alias])) {
					$expected = $indoor_map[$alias];
					if ($expected === null || $expected === $channel) {
						$has_indoor = true;
					}
				} elseif ($type === "button") {
					$btn_entries[] = $entry;
				}
			}

			if (!$has_indoor || empty($btn_entries)) {
				continue;
			}

			foreach ($btn_entries as $btn) {
				$btn_device = $this -> devices() -> by_alias($btn["alias"]);
				if (!$btn_device) {
					continue;
				}

				$btn_api = $btn_device -> device_api();
				if (!($btn_api instanceof \SHServ\Tools\DeviceAPI\Button)) {
					continue;
				}

				$btn_channel = $btn["channel"] ?? 0;

				if ($mute) {
					$btn_api -> set_channel_state("mute", $btn_channel);
				} else {
					// Unmute: figure out whether the linked relay is on or off
					$sync_state = "disabled";
					foreach ($connection as $e) {
						if (($e["type"] ?? "") === "relay" && isset($indoor_map[$e["alias"]])) {
							$expected = $indoor_map[$e["alias"]];
							$relay_ch = $e["channel"] ?? 0;
							if ($expected === null || $expected === $relay_ch) {
								$relay = $this -> devices() -> by_alias($e["alias"]);
								if ($relay) {
									$relay_api = $relay -> device_api();
									if ($relay_api instanceof \SHServ\Tools\DeviceAPI\Relay) {
										$status = $relay_api -> get_status();
										$st = $status["channels"][$relay_ch]["state"] ?? "off";
										$sync_state = ($st === "on") ? "enabled" : "disabled";
									}
								}
								break;
							}
						}
					}
					$btn_api -> set_channel_state($sync_state, $btn_channel);
				}
			}
		}
	}
}