diff --git a/automation/Scopes/SleepScope.php b/automation/Scopes/SleepScope.php index cc3f6ac..2801e28 100644 --- a/automation/Scopes/SleepScope.php +++ b/automation/Scopes/SleepScope.php @@ -6,24 +6,45 @@ use \SHServ\Implements\ControlScriptsInterface; /** - * Sleep mode: turns off all indoor lighting except outdoor spotlights. + * 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 event handlers needed; triggered via action script or mode API. + // 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" => "Включить режим сна: выключить всё освещение кроме прожекторов", + "description" => "Включить режим сна: выключить всё indoor-освещение и заблокировать кнопки", "author" => "Eugene Sukhodolskiy", "icon" => '', "danger_level" => "cautious", @@ -36,34 +57,11 @@ ], function($params) { $this -> mode() -> enable('sleep'); - $results = $this -> group_set_state([ - // Hall / Office / Master bedroom (light_hub_1 channels) - "light_hub_1:0", - "light_hub_1:1", - "light_hub_1:2", + // 1. Turn everything off immediately + $results = $this -> helper() -> group_set_state(self::INDOOR_RELAYS, false, $this -> sync_map()); - // Office lamps - "craft_table_lamp", - "computer_table_lamp", - - // Bathroom - "bathroom_2_light", - - // Plants room - "plants_room_light", - "italy_lamp_relay", - "floor_lamp_relay", - - // Server room - "server_room_light", - - // First floor (kitchen + hall) - "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", - ], false); + // 2. Mute indoor button channels so physical presses are ignored + $this -> set_indoor_buttons_mute(true); return [ "mode_enabled" => true, @@ -74,7 +72,7 @@ $this -> add_action_script([ "alias" => "deactivate_sleep", "name" => "Выход из сна", - "description" => "Отключить режим сна", + "description" => "Отключить режим сна и разблокировать кнопки", "author" => "Eugene Sukhodolskiy", "icon" => '', "danger_level" => "safe", @@ -87,11 +85,138 @@ ], 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 {} + 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); + } + } + } + } } diff --git a/webclient/android/app/capacitor.build.gradle b/webclient/android/app/capacitor.build.gradle index 53b0715..dcb4c6d 100644 --- a/webclient/android/app/capacitor.build.gradle +++ b/webclient/android/app/capacitor.build.gradle @@ -2,8 +2,8 @@ android { compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } }