# Руководство по написанию Control Scripts

Control Scripts — это PHP-классы автоматизации. Базовый класс находится в `server/SHServ/Middleware/ControlScripts.php`, интерфейс — в `server/SHServ/Implements/ControlScriptsInterface.php`.

Scope-классы располагаются в `server/ControlScripts/Scopes/` (или другом месте, если настроено иначе). Все Scope-файлы загружаются автоматически при старте сервера.

---

## Базовый класс

Все Scope-классы наследуют `\SHServ\Middleware\ControlScripts` и реализуют `\SHServ\Implements\ControlScriptsInterface`:

```php
<?php

namespace ControlScripts\Scopes;

class MyScope extends \SHServ\Middleware\ControlScripts 
              implements \SHServ\Implements\ControlScriptsInterface {
    // ...
}
```

**Автоматическая загрузка:**  
При старте сервера каждый Scope-класс:
1. Проверяет состояние в БД (таблица `scripts`, тип `scope`, имя = название класса)
2. Если scope включён (или это первый запуск) — вызывает 4 регистрационных метода
3. Методы регистрируют обработчики в статические коллекции Fury Events

---

---

## Четыре регистрационных метода

Каждый Scope-класс обязан реализовать 4 метода. Они вызываются при старте сервера (если scope включён в БД).

### 1. `register_sync_map(): void`

Регистрирует связи «реле ↔ кнопки» для синхронизации индикаторов.

```php
public function register_sync_map(): void {
    $this->add_sync_connection([
        // Первый элемент — источник состояния (обычно реле)
        ["type" => "relay",  "alias" => "kitchen_relay", "channel" => 0],
        // Остальные — кнопки, чьи индикаторы синхронизируются
        ["type" => "button", "alias" => "kitchen_btns",  "channel" => 1],
        ["type" => "button", "alias" => "hall_btns",     "channel" => 0],
    ]);
}
```

Sync map хранится в статической переменной `ControlScripts::$sync_map_storage`.

---

### 2. `register_events_handlers(): void`

Подписывается на события от устройств. Обработчик вызывается **асинхронно** после ответа устройству (`fastcgi_finish_request`).

```php
public function register_events_handlers(): void {
    // Нажатие кнопки (канал 1) конкретного устройства
    $this->add_event_handler("button@kitchen_btns(1).press", function(Device $device, array $data) {
        $relay = $this->devices()->by_alias("kitchen_relay");
        $relay->device_api()->toggle_channel(0);
        $this->helper()->sync_relay_to_btns($this->sync_map(), "kitchen_relay");
    });

    // Устройство вышло в сеть (после перезагрузки)
    $this->add_event_handler("button@kitchen_btns.online", function(Device $device, array $data) {
        $this->helper()->sync_btn_channels($this->sync_map(), $device->alias);
    });
}
```

**Паттерны имён событий:**

| Паттерн | Пример | Описание |
|---------|---------|---------|
| `{event_name}` | `press` | Любое устройство, любое событие |
| `{type}.{event_name}` | `button.press` | Все устройства типа `button` |
| `{type}@{alias}.{event_name}` | `button@kitchen_btns.press` | Конкретное устройство (все каналы) |
| `{type}({ch}).{event_name}` | `button(1).press` | Все устройства типа, канал 1 |
| `{type}@{alias}({ch}).{event_name}` | `button@kitchen_btns(1).press` | Конкретное устройство, канал 1 |

Полное описание: `docs/events-from-devices.md`.

---

### 3. `register_actions_scripts(): void`

Регистрирует action-скрипты (ручной запуск через UI/API).

```php
public function register_actions_scripts(): void {
    $this->add_action_script([
        "alias"       => "kitchen_light_toggle",
        "name"        => "Свет на кухне",
        "icon"        => '<i class="ph ph-lightbulb"></i>',
        "description" => "Включить/выключить основной свет",
        "author"      => "Eugene Sukhodolskiy"
    ], function($params) {
        $relay = $this->devices()->by_alias("kitchen_relay");
        $relay->device_api()->toggle_channel(0);
        $this->helper()->sync_relay_to_btns($this->sync_map(), "kitchen_relay");
        return ["result" => true];
    });
}
```

**API:**
- `POST /api/v1/scripts/actions/run` — запустить скрипт
- `GET /api/v1/scripts/actions/alias/{alias}/enable` — включить
- `GET /api/v1/scripts/actions/alias/{alias}/disable` — выключить

---

### 4. `register_regular_scripts(): void`

Регистрирует regular-скрипты (периодический запуск по cron).

```php
public function register_regular_scripts(): void {
    $this->add_regular_script([
        "alias" => "check_door_sensor",
        "name"  => "Проверка датчика двери",
    ], function() {
        $sensor = $this->devices()->by_alias("door_sensor");
        // ...
    });
}
```

**API:**
- `GET /cron/regular-scripts` — запустить все enabled regular-скрипты (cron)
- `GET /api/v1/scripts/regular/alias/{alias}/enable` — включить
- `GET /api/v1/scripts/regular/alias/{alias}/disable` — выключить

---

## Доступные методы базового класса

| Метод | Возвращает | Описание |
|-------|------------|----------|
| `$this->devices()` | `\SHServ\Models\Devices` | Поиск устройств по alias, id, hard_id |
| `$this->helper()` | `\SHServ\Helpers\DeviceScriptsHelper` | Хелперы синхронизации реле/кнопок |
| `$this->sync_map()` | `array` | Текущий sync_map (статическое хранилище) |
| `$this->add_event_handler($name, $cb)` | `void` | Подписаться на событие |
| `$this->add_action_script($attrs, $cb)` | `bool` | Зарегистрировать action-скрипт |
| `$this->add_regular_script($attrs, $cb)` | `bool` | Зарегистрировать regular-скрипт |
| `$this->add_sync_connection($entries)` | `void` | Добавить связь в sync_map |

---

## DeviceScriptsHelper

Класс `\SHServ\Helpers\DeviceScriptsHelper` предоставляет методы для синхронизации индикаторов кнопок с состоянием реле.

### Методы

| Метод | Описание |
|-------|---------|
| `sync_relay_to_btn_channel($relay_api, $btn_api, $relay_ch, $btn_ch)` | Синхронизировать один канал реле с одним каналом кнопки |
| `sync_relay_to_btns($sync_map, $relay_alias)` | Синхронизировать все кнопки из sync_map с указанным реле |
| `sync_btn_channels($sync_map, $btn_alias)` | Синхронизировать индикаторы указанной кнопки с реле из sync_map |
| `get_sync_entries_by_type($sync_map, $type)` | Получить все записи указанного типа из sync_map |
| `prepare_sync_map_by_alias($sync_map, $alias)` | Подготовить sync_map для указанного устройства |

### Пример использования

```php
// В event-хендлере: переключить реле и синхронизировать кнопки
$relay = $this->devices()->by_alias("kitchen_relay");
$relay_api = $relay->device_api();
$relay_api->toggle_channel(0);

// Синхронизировать все кнопки, связанные с этим реле
$this->helper()->sync_relay_to_btns($this->sync_map(), "kitchen_relay");

// Или: синхронизировать конкретную кнопку с реле
$this->helper()->sync_btn_channels($this->sync_map(), "kitchen_btns");
```

---

## Управление Scope через API

| Endpoint | Описание |
|----------|---------|
| `GET /api/v1/scripts/actions/scope/{name}/enable` | Включить Scope (в БД) |
| `GET /api/v1/scripts/actions/scope/{name}/disable` | Выключить Scope (в БД) |
| `GET /api/v1/scripts/scopes/list` | Список всех Scope с состоянием |
| `GET /api/v1/scripts/scopes/name/{name}` | Исходный код PHP-файла Scope |
| `POST /api/v1/scripts/scopes/update` | Обновить код Scope |

**Важно:** Scope отключается через БД — при следующем запуске сервера его скрипты не зарегистрируются.

---

## Trait Common (опционально)

Некоторые проекты используют trait `\ControlScripts\Common` с готовыми хелперами:

```php
use \ControlScripts\Common;

class MyScope extends \SHServ\Middleware\ControlScripts {
    use Common;
    
    public function register_sync_map(): void {
        // Зарегистрировать глобальный sync_map (все реле и кнопки системы)
        $this->register_global_device_sync_map();
    }
    
    public function register_events_handlers(): void {
        // Установить обработчики нажатий для кнопок из sync_map
        $this->set_btns_click_handlers("kitchen_btns");
    }
}
```

Проверьте наличие trait в вашем проекте: `server/ControlScripts/Common.php`.
