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
}
}