Modules/devices/devices class php

Материал из MajorDoMo инфо


ᐂ В корневой раздел ᐃ В директорию расположения файла

<?php

/**
 * Общий коммент к пониманию стандарта PHPDoc:
 * Аннотация @method для указания на то, что это комментарий к методу.
 * Аннотация @param для описания параметров метода.
 * Аннотация @return для описания возвращаемого значения метода.
 * Аннотация @see для ссылок на другие классы или методы.
 * Аннотация @since для указания версии, с которой был добавлен метод.
 *
 * Типы параметров и возвращаемых значений:
 * - string: строковое значение.
 * - int: целочисленное значение.
 * - float: число с плавающей точкой.
 * - bool: булево значение (true или false).
 * - array: массив.
 * - object: объект.
 * - mixed: любой тип данных.
 * - callable: функция или метод.
 * - resource: ресурс (например, файл).
 * - null: специальное значение, обозначающее отсутствие значения.
 *
 * Пример использования:
 * @method void myVoidMethod() Описание метода, который не возвращает значение.
 * @param string $param1 Параметр типа string.
 * @param int $param2 Параметр типа int.
 * @param float $param3 Параметр типа float.
 * @param bool $param4 Параметр типа bool.
 * @param array $param5 Параметр типа array.
 * @param object $param6 Параметр типа object.
 * @param mixed $param7 Параметр типа mixed (любой тип).
 * @param callable $param8 Параметр типа callable (функция или метод).
 * @param resource $param9 Параметр типа resource (ресурс, например, файл).
 * @param null $param10 Параметр типа null.
 * @return string Возвращает значение типа string.
 * @return int Возвращает значение типа int.
 * @return float Возвращает значение типа float.
 * @return bool Возвращает значение типа bool.
 * @return array Возвращает значение типа array.
 * @return object Возвращает значение типа object.
 * @return mixed Возвращает значение типа mixed (любой тип).
 * @return callable Возвращает значение типа callable (функция или метод).
 * @return resource Возвращает значение типа resource (ресурс, например, файл).
 * @return null Возвращает значение типа null.
 */
/**
 * Класс Devices
 * @package project
 * @author Wizard <sergejey@gmail.com>
 * @copyright http://majordomo.smartliving.ru/ (c)
 * @version 0.1 (wizard, 13:07:05 [Jul 19, 2016])
 * @class Devices
 * @property string $name Имя модуля
 * @property string $title Заголовок модуля
 * @property string $module_category Категория модуля
 * @method void __construct() Конструктор класса
 * @method void saveParams($data = 0) Сохранение параметров модуля
 * @method void getParams() Получение параметров модуля из строки запроса
 * @method void setDictionary() Установка словаря для модуля
 * @method void run() Основная логика модуля
 * @see module
 * @since 0.1
 */
// Класс devices расширяет модуль, что позволяет использовать функциональность модуля в классе devices.
class devices extends module
{
    var $view;
    var $id;
    /**
     * Конструктор класса модуля
     * @method void __construct()
     * @see module
     * @since 0.1
     */
    function __construct()
    {
        // Инициализация имени модуля.
        $this->name = "devices";
        // Установка заголовка модуля, который будет отображаться в интерфейсе.
        $this->title = "<#LANG_SECTION_DEVICES#>";
        // Установка категории модуля, которая также будет отображаться в интерфейсе.
        $this->module_category = "<#LANG_SECTION_DEVICES#>";
        // Проверка, установлен ли модуль.
        $this->checkInstalled();

        // Установка словаря для модуля.
        $this->setDictionary();
    }
    /**
     * saveParams
     *
     * Сохранение параметров модуля
     *
     * @method void saveParams($data = 0)
     * @param mixed $data Данные для сохранения
     * @return void
     * @see module
     * @since 0.1
     */
    function saveParams($data = 0)
    {
        $p = array();
        // Проверка наличия идентификатора модуля и его сохранение в массив параметров.
        if (isset($this->id)) {
            $p["id"] = $this->id;
        }
        // Проверка наличия режима просмотра и его сохранение в массив параметров.
        if (isset($this->view_mode)) {
            $p["view_mode"] = $this->view_mode;
        }
        // Проверка наличия режима редактирования и его сохранение в массив параметров.
        if (isset($this->edit_mode)) {
            $p["edit_mode"] = $this->edit_mode;
        }
        // Проверка наличия вкладки и ее сохранение в массив параметров.
        if (isset($this->tab)) {
            $p["tab"] = $this->tab;
        }
        // Вызов родительского метода saveParams с передачей массива параметров.
        return parent::saveParams($p);
    }

    /**
     * getParams
     *
     * Получение параметров модуля из строки запроса
     * Этот фрагмент кода содержит два метода:
     * saveParams и getParams. 
     * Метод saveParams сохраняет параметры модуля,
     * а метод getParams получает их из строки запроса.
     * Оба методы используют глобальные переменные для работы с параметрами
     *
     * @method void getParams()
     * @return void
     * @see module
     * @since 0.1
     */
    function getParams()
    {
        global $id;
        global $mode;
        global $view_mode;
        global $edit_mode;
        global $tab;
        // Проверка наличия идентификатора и его присвоение свойству класса.
        if (isset($id)) {
            $this->id = $id;
        }
        // Проверка наличия режима и его присвоение свойству класса.
        if (isset($mode)) {
            $this->mode = $mode;
        }
        // Проверка наличия режима просмотра и его присвоение свойству класса.
        if (isset($view_mode)) {
            $this->view_mode = $view_mode;
        }
        // Проверка наличия режима редактирования и его присвоение свойству класса.
        if (isset($edit_mode)) {
            $this->edit_mode = $edit_mode;
        }
        // Проверка наличия вкладки и ее присвоение свойству класса.
        if (isset($tab)) {
            $this->tab = $tab;
        }
    }
    /**
     * setDictionary
     * Метод setDictionary включает два файла,
     * которые содержат структуру устройств и связи между ними.
     * Это необходимо для корректной работы модуля устройств.
     *
     * @method void setDictionary()
     * @return void
     * @see module
     * @since 0.1
     */
    function setDictionary()
    {
        // Включение файлов, содержащих структуру устройств и связи между ними.
        include(dirname(__FILE__) . '/devices_structure.inc.php');
        include(dirname(__FILE__) . '/devices_structure_links.inc.php');
    }
    /**
     * Run
     *
     * Метод run выполняет основную логику модуля.
     * Он проверяет действие, которое необходимо выполнить, и вызывает соответствующий метод.
     * Затем он устанавливает различные параметры,
     * такие как режим просмотра, режим редактирования, режим, действие и вкладку.
     * После этого он сохраняет данные в свойстве класса и создает новый объект парсера,
     * используя указанный шаблон и данные.
     * Результат парсинга сохраняется в свойстве класса.
     *
     * @method void run()
     * @return void
     * @see module
     * @since 0.1
     */
    function run()
    {
        // Инициализация массива для хранения данных.
        $out = array();
        // Проверка действия и вызов соответствующего метода.
        if ($this->action == 'admin') {
            $this->admin($out);
        } elseif ($this->action == 'link') {
            $this->link($out);
        } else {
            $this->usual($out);
        }
        // Проверка и установка действия родительского объекта.
        if (isset($this->owner->action)) {
            $out['PARENT_ACTION'] = $this->owner->action;
        }
        // Проверка и установка имени родительского объекта.
        if (isset($this->owner->name)) {
            $out['PARENT_NAME'] = $this->owner->name;
        }
        // Установка режима просмотра.
        $out['VIEW_MODE'] = $this->view_mode;
        // Установка режима редактирования.
        $out['EDIT_MODE'] = $this->edit_mode;
        // Установка режима.
        $out['MODE'] = $this->mode;
        // Установка действия.
        $out['ACTION'] = $this->action;
        // Установка вкладки.
        $out['TAB'] = $this->tab;
        // Сохранение данных в свойстве класса.
        $this->data = $out;
        // Создание нового объекта парсера с указанным шаблоном и данными.
        $p = new parser(DIR_TEMPLATES . $this->name . "/" . $this->name . ".html", $this->data, $this);
        // Сохранение результата парсинга в свойстве класса.
        $this->result = $p->result;
    }
    /**
     * link
     *
     * Метод link обрабатывает связь устройства.
     * Он проверяет наличие различных параметров,
     * таких как тип устройства, исходная таблица, идентификатор исходной таблицы, префикс,
     * дополнительный заголовок и связанный объект.
     * Если все параметры корректны, он генерирует уникальный идентификатор для устройства
     * и устанавливает флаг успешного выполнения.
     *
     * @method void link()
     * @return void
     * @see module
     * @since 0.1
     */
    function link(&$out)
    {
        $ok = 1;
        // Проверка наличия типа устройства и его сохранение в массив вывода.
        if ($this->type) {
            $out['TYPE'] = $this->type;
        } else {
            // Если тип устройства не определен, устанавливаем флаг ошибки.
            $ok = 0;
        }
        // Проверка наличия исходной таблицы и ее сохранение в массив вывода.
        if ($this->source_table) {
            $out['SOURCE_TABLE'] = $this->source_table;
        } else {
            // Если исходная таблица не определена, устанавливаем флаг ошибки.
            $ok = 0;
        }
        // Проверка наличия идентификатора исходной таблицы и его сохранение в массив вывода.
        if ($this->source_table_id) {
            $out['SOURCE_TABLE_ID'] = $this->source_table_id;
        } else {
            // Если идентификатор исходной таблицы не определен, устанавливаем флаг ошибки.
            $ok = 0;
        }
        // Проверка наличия префикса и его сохранение в массив вывода.
        if ($this->prefix) {
            $out['PREFIX'] = $this->prefix;
        }

        // Проверка наличия дополнительного заголовка и его сохранение в массив вывода.
        if (isset($this->add_title)) {
            $out['ADD_TITLE'] = urlencode($this->add_title);
        }

        // Проверка наличия связанного объекта и его сохранение в массив вывода.
        if ($this->linked_object) {
            $device_rec = SQLSelectOne("SELECT ID,TITLE FROM devices WHERE LINKED_OBJECT LIKE '" . DBSafe($this->linked_object) . "'");
            if (isset($device_rec['TITLE'])) {
                $out['TITLE'] = $device_rec['TITLE'];
                // Если установлен флаг предварительного просмотра, обрабатываем устройство и сохраняем результат в массив вывода.
                if ($this->preview) {
                    $data = $this->processDevice($device_rec['ID']);
                    $out['HTML'] = $data['HTML'];
                }
                $out['ID'] = $device_rec['ID'];
            }
            $out['LINKED_OBJECT'] = $this->linked_object;
        }
        // Генерация уникального идентификатора для устройства.
        $out['UNIQ'] = uniqid('dev');
        // Если все параметры корректны, устанавливаем флаг успешного выполнения.
        if ($ok) {
            $out['OK'] = 1;
        }
    }
    /**
     * getTypeDetails
     *
     * Метод getTypeDetails возвращает детали типа устройства из словаря типов устройств.
     *
     * @method array getTypeDetails($type)
     * @param string $type Тип устройства
     * @return array Детали типа устройства
     * @see module
     * @since 0.1
     */
    function getTypeDetails($type)
    {
        // Возвращает детали типа устройства из словаря типов устройств.
        return $this->device_types[$type];
    }
    /**
     * getTypeLinks
     *
     * Метод getTypeLinks возвращает все связи, которые связаны с указанным типом устройства.
     * Он сначала получает детали типа устройства,
     * а затем проходит по всем связям устройств, проверяя,
     * присутствует ли класс или родительский класс типа устройства в типах связей.
     * Если да, то все связи этого типа добавляются в результирующий массив.
     *
     * @method array getTypeLinks($type)
     * @param string $type Тип устройства
     * @return array Все связи, связанные с указанным типом устройства
     * @see module
     * @since 0.1
     */
    function getTypeLinks($type)
    {
        // Получаем детали типа устройства.
        $type_details = $this->getTypeDetails($type);
        $res_links = array();
        // Проходимся по всем связям устройств.
        foreach ($this->device_links as $k => $v) {
            // Разделяем типы связей на отдельные элементы.
            $link_types = explode(',', $k);
            $link_types = array_map('trim', $link_types);
            // Если класс или родительский класс типа устройства присутствует в типах связей,
            if (in_array($type_details['CLASS'], $link_types) || in_array($type_details['PARENT_CLASS'], $link_types)) {
                // добавляем все связи этого типа в результирующий массив.
                foreach ($v as $link) {
                    $res_links[] = $link;
                }
            }
        }
        // Возвращаем результирующий массив связей.
        return $res_links;
    }
    /**
     * getLinkDetails
     *
     * Метод getLinkDetails возвращает детали связи устройства по указанному имени связи.
     * Он проходит по всем связям устройств и возвращает детали той связи,
     * имя которой совпадает с указанным именем связи.
     *
     * @method array getLinkDetails($link_name)
     * @param string $link_name Имя связи устройства
     * @return array Детали связи устройства
     * @see module
     * @since 0.1
     */
    function getLinkDetails($link_name)
    {
        // Проходимся по всем связям устройств.
        foreach ($this->device_links as $k => $v) {
            foreach ($v as $link) {
                // Если имя связи совпадает с указанным именем связи, возвращаем детали этой связи.
                if ($link['LINK_NAME'] == $link_name) {
                    return $link;
                }
            }
        }
    }
    /**
     * getAllGroups
     *
     * Метод getAllGroups возвращает все группы устройств, которые применимы к указанному типу устройства.
     * Он выбирает все группы устройств из базы данных,
     * проходит по ним и добавляет в результирующий массив те группы,
     * типы которых присутствуют в типах, применимых к группе.
     *
     * @method array getAllGroups($type)
     * @param string $type Тип устройства
     * @return array Все группы устройств, применимые к указанному типу устройства
     * @see module
     * @since 0.1
     */
    function getAllGroups($type)
    {
        // Выбираем все группы устройств из базы данных.
        $groups = SQLSelect("SELECT * FROM devices_groups");
        $res = array();
        $total = count($groups);
        // Проходимся по всем группам.
        for ($i = 0; $i < $total; $i++) {
            $tmp = explode(',', $groups[$i]['APPLY_TYPES']);
            // Если тип устройства присутствует в типах, применимых к группе, добавляем группу в результирующий массив.
            if (in_array($type, $tmp)) {
                $res[] = $groups[$i];
            }
        }
        // Возвращаем результирующий массив групп.
        return $res;
    }
    /**
     * getAllProperties
     *
     * Метод getAllProperties возвращает все свойства указанного типа устройства,
     * включая свойства его родительского класса.
     * Он сначала получает свойства текущего типа устройства,
     * а затем, если у типа устройства есть родительский класс,
     * проходит по всем типам устройств, ищет свойства родительского класса и добавляет их в свойства текущего типа,
     * если они еще не присутствуют.
     *
     * @method array getAllProperties($type)
     * @param string $type Тип устройства
     * @return array Все свойства указанного типа устройства, включая свойства его родительского класса
     * @see module
     * @since 0.1
     */
    function getAllProperties($type)
    {
        // Получаем свойства текущего типа устройства.
        $properties = $this->device_types[$type]['PROPERTIES'];
        $parent_class = isset($this->device_types[$type]['PARENT_CLASS']) ? $this->device_types[$type]['PARENT_CLASS'] : '';
        // Если у типа устройства есть родительский класс,
        if ($parent_class != '') {
            // проходимся по всем типам устройств.
            foreach ($this->device_types as $k => $v) {
                // Если класс текущего типа совпадает с родительским классом,
                if ($v['CLASS'] == $parent_class) {
                    // получаем свойства родительского класса.
                    $parent_properties = $this->getAllProperties($k);
                    // Проходимся по всем свойствам родительского класса.
                    foreach ($parent_properties as $pk => $pv) {
                        // Если свойство родительского класса еще не присутствует в свойствах текущего типа, добавляем его.
                        if (!isset($properties[$pk])) {
                            $properties[$pk] = $pv;
                        }
                    }
                }
            }
        }
        // Возвращаем все свойства текущего типа устройства, включая свойства родительского класса.
        return $properties;
    }
    /**
     * getAllMethods
     *
     * Метод getAllMethods возвращает все методы указанного типа устройства,
     * включая методы его родительского класса.
     * Он сначала получает методы текущего типа устройства,
     * а затем, если у типа устройства есть родительский класс,
     * проходит по всем типам устройств,
     * ищет методы родительского класса и добавляет их в методы текущего типа,
     * если они еще не присутствуют.
     *
     * @method array getAllMethods($type)
     * @param string $type Тип устройства
     * @return array Все методы указанного типа устройства, включая методы его родительского класса
     * @see module
     * @since 0.1
     */
    function getAllMethods($type)
    {
        // Получаем методы текущего типа устройства.
        $methods = $this->device_types[$type]['METHODS'];
        $parent_class = isset($this->device_types[$type]['PARENT_CLASS']) ? $this->device_types[$type]['PARENT_CLASS'] : '';
        // Если у типа устройства есть родительский класс,
        if ($parent_class != '') {
            // проходимся по всем типам устройств.
            foreach ($this->device_types as $k => $v) {
                // Если класс текущего типа совпадает с родительским классом,
                if ($v['CLASS'] == $parent_class) {
                    // получаем методы родительского класса.
                    $parent_methods = $this->getAllMethods($k);
                    // Проходимся по всем методам родительского класса.
                    foreach ($parent_methods as $pk => $pv) {
                        // Если метод родительского класса еще не присутствует в методах текущего типа, добавляем его.
                        if (!isset($methods[$pk])) {
                            $methods[$pk] = $pv;
                        }
                    }
                }
            }
        }
        // Возвращаем все методы текущего типа устройства, включая методы родительского класса.
        return $methods;
    }
    /**
     * getNewObjectIndex
     *
     * Метод getNewObjectIndex возвращает новый индекс для объекта указанного класса.
     * Он сначала получает объекты указанного класса, а затем, если префикс не пустой,
     * выбирает дополнительные объекты, которые начинаются с этого префикса.
     * Затем он проходит по всем объектам, ищет числа в их названиях и обновляет индекс,
     * если найденное число больше текущего индекса.
     * В конце метод возвращает индекс, увеличенный на 1, и добавляет в начало ноль, если индекс меньше 10.
     *
     * @method string getNewObjectIndex($class, $prefix = '')
     * @param string $class Имя класса
     * @param string $prefix Префикс для поиска объектов
     * @return string Новый индекс для объекта указанного класса
     * @see module
     * @since 0.1
     */
    function getNewObjectIndex($class, $prefix = '')
    {
        // Получаем объекты указанного класса.
        $objects = getObjectsByClass($class);
        if ($prefix != '') {
            // Если префикс не пустой, выбираем дополнительные объекты, которые начинаются с этого префикса.
            $other_objects = SQLSelect("SELECT TITLE FROM objects WHERE TITLE LIKE '" . $prefix . "%'");
            foreach ($other_objects as $obj) {
                $objects[] = $obj;
            }
        }
        $index = 0;
        $total = count($objects);
        // Проходимся по всем объектам.
        for ($i = 0; $i < $total; $i++) {
            // Если в названии объекта присутствует число,
            if (preg_match('/(\d+)/', $objects[$i]['TITLE'], $m)) {
                $current_index = (int)$m[1];
                // и это число больше текущего индекса, обновляем индекс.
                if ($current_index > $index) {
                    $index = $current_index;
                }
            }
        }
        $index++;
        // Если индекс меньше 10, добавляем в начало ноль.
        if ($index < 10) {
            $index = '0' . $index;
        }
        // Возвращаем индекс.
        return $index;
    }
    /**
     * processDevice
     *
     * Метод processDevice обрабатывает устройство с указанным идентификатором.
     * Он выбирает запись устройства из базы данных, получает шаблон для объекта класса устройства,
     * обрабатывает шаблон и сохраняет результат в массив результатов.
     * Если тип устройства - 'camera', он устанавливает высоту результата.
     * В конце метод возвращает результат.
     *
     * @method array processDevice($device_id, $view = '')
     * @param int $device_id Идентификатор устройства
     * @param string $view Название представления
     * @return array Результат обработки устройства
     * @see module
     * @since 0.1
     */
    function processDevice($device_id, $view = '')
    {
        // Начинаем измерение времени выполнения метода.
        startMeasure('processDevice');
        // Выбираем запись устройства из базы данных по его идентификатору.
        $device_rec = SQLSelectOne("SELECT * FROM devices WHERE ID=" . (int)$device_id);
        $result = array('HTML' => '', 'DEVICE_ID' => $device_rec['ID']);

        // Получаем шаблон для объекта класса устройства.
        $template = getObjectClassTemplate($device_rec['LINKED_OBJECT'], $view);

        // Обрабатываем шаблон и сохраняем результат в массив результатов.
        $result['HTML'] = processTitle($template, $this);
        // Если тип устройства - 'camera', устанавливаем высоту результата.
        if ($device_rec['TYPE'] == 'camera') {
            $result['HEIGHT'] = 5;
        }
        // Завершаем измерение времени выполнения метода.
        endMeasure('processDevice');
        // Возвращаем результат.
        return $result;
    }
    /**
     * getWatchedProperties
     *
     * Метод getWatchedProperties возвращает все свойства устройств, которые нужно отслеживать.
     * Он сначала устанавливает словарь для модуля,
     * затем выбирает устройства из базы данных и проходит по ним.
     * Для каждого устройства он получает все свойства текущего типа устройства и добавляет их в результирующий массив.
     * Если тип устройства - 'html', он обрабатывает содержимое устройства,
     * удаляет из него все символы, которые не являются частью имени свойства,
     * ищет все имена свойств в содержимом устройства
     * и добавляет каждое найденное свойство в результирующий массив.
     * В конце метод возвращает результирующий массив свойств.
     *
     * @method array getWatchedProperties($device_id = 0)
     * @param int $device_id Идентификатор устройства
     * @return array Все свойства устройств, которые нужно отслеживать
     * @see module
     * @since 0.1
     */
    function getWatchedProperties($device_id = 0)
    {
        // Устанавливаем словарь для модуля.
        $this->setDictionary();
        $properties = array();
        $qry = 1;
        if ($device_id) {
            // Если указан идентификатор устройства, добавляем его в запрос.
            $qry .= " AND devices.ID IN (" . $device_id . ")";
        }
        // Выбираем устройства из базы данных.
        $devices = SQLSelect("SELECT * FROM devices WHERE $qry");
        $total = count($devices);
        // Проходимся по всем устройствам.
        for ($i = 0; $i < $total; $i++) {
            if (!$devices[$i]['LINKED_OBJECT']) {
                continue;
            }
            // Получаем все свойства текущего типа устройства.
            $props = $this->getAllProperties($devices[$i]['TYPE']);
            if (is_array($props)) {
                foreach ($props as $k => $v) {
                    // Если имя свойства начинается с символа подчеркивания, пропускаем его.
                    if (substr($k, 0, 1) == '_') continue;
                    // Добавляем свойство в результирующий массив.
                    $properties[] = array('PROPERTY' => mb_strtolower($devices[$i]['LINKED_OBJECT'] . '.' . $k, 'UTF-8'), 'DEVICE_ID' => $devices[$i]['ID']);
                }
            }
            // Если тип устройства - 'html', обрабатываем содержимое устройства.
            if ($devices[$i]['TYPE'] == 'html') {
                $content = getGlobal($devices[$i]['LINKED_OBJECT'] . '.data');
                // Удаляем из содержимого устройства все символы, которые не являются частью имени свойства.
                $content = preg_replace('/%([\w\d\.]+?)\.([\w\d\.]+?)\|(\d+)%/uis', '%\1.\2%', $content);
                $content = preg_replace('/%([\w\d\.]+?)\.([\w\d\.]+?)\|(\d+)%/uis', '%\1.\2%', $content);
                $content = preg_replace('/%([\w\d\.]+?)\.([\w\d\.]+?)\|".+?"%/uis', '%\1.\2%', $content);
                // Ищем все имена свойств в содержимом устройства.
                if (preg_match_all('/%([\w\d\.]+?)%/is', $content, $m)) {
                    $totalm = count($m[1]);
                    for ($im = 0; $im < $totalm; $im++) {
                        // Добавляем каждое найденное свойство в результирующий массив.
                        $properties[] = array('PROPERTY' => mb_strtolower($m[1][$im], 'UTF-8'), 'DEVICE_ID' => $devices[$i]['ID']);
                    }
                }
            }
        }
        // Возвращаем результирующий массив свойств.
        return $properties;
    }
    /**
     * updateGroupObjects
     *
     * Метод updateGroupObjects обновляет объекты групп устройств.
     * Он сначала выбирает все группы устройств из базы данных,
     * затем проходит по ним и добавляет объект класса 'SGroups'
     * с указанным именем и описанием для каждой группы.
     * Затем он устанавливает свойство 'groupName' для каждого объекта
     * и сохраняет его в массив добавленных объектов.
     * После этого он получает все объекты класса 'SGroups'
     * и удаляет те из них, которые не присутствуют в массиве добавленных объектов.
     *
     * @method void updateGroupObjects()
     * @return void
     * @see module
     * @since 0.1
     */
    function updateGroupObjects()
    {
        // Выбираем все группы устройств из базы данных.
        $groups = SQLSelect("SELECT * FROM devices_groups WHERE 1");
        $total = count($groups);
        $added_objects = array();
        // Проходимся по всем группам.
        for ($i = 0; $i < $total; $i++) {
            // Добавляем объект класса 'SGroups' с указанным именем и описанием.
            $object_id = addClassObject('SGroups', 'group' . $groups[$i]['SYS_NAME'], 'group' . $groups[$i]['SYS_NAME']);
            $object_rec = SQLSelectOne("SELECT * FROM objects WHERE ID=" . $object_id);
            if ($object_rec['ID']) {
                // Обновляем описание объекта.
                $object_rec['DESCRIPTION'] = $groups[$i]['TITLE'];
                SQLUpdate('objects', $object_rec);
            }
            // Устанавливаем свойство 'groupName' для объекта.
            sg($object_rec['TITLE'] . '.groupName', $groups[$i]['SYS_NAME']);
            $added_objects[] = $object_id;
        }
        // Получаем все объекты класса 'SGroups'.
        $group_objects = getObjectsByClass('SGroups');
        foreach ($group_objects as $rec) {
            // Если объект не присутствует в массиве добавленных объектов, удаляем его.
            if (!in_array($rec['ID'], $added_objects)) {
                deleteObject($rec['ID']);
            }
        }
    }
    /**
     * renderStructure
     *
     * Эта функция отвечает за рендеринг структуры устройств.
     * Она проходит по всем типам устройств и для каждого типа выполняет следующие действия:
     * - Добавляет класс устройства, если он не существует.
     * - Обновляет описание класса, если оно задано.
     * - Добавляет свойства класса, если они не существуют.
     * - Добавляет методы класса, если они не существуют.
     * - Добавляет объекты класса, если они не существуют.
     * - Подписывается на события 'COMMAND' и 'MINUTELY' для модуля устройств.
     * - Обновляет превью камер.
     * - Обновляет объекты устройств.
     * - Обновляет объекты групп устройств.
     *
     * @method void renderStructure()
     * @return void
     * @see module
     * @since 0.1
     */
    function renderStructure()
    {
        // Проверяем, определена ли константа DISABLE_SIMPLE_DEVICES и равна ли она 1
        if (defined('DISABLE_SIMPLE_DEVICES') && DISABLE_SIMPLE_DEVICES == 1) {
            // Если условие выполняется, отписываемся от событий 'COMMAND' и 'MINUTELY' для модуля устройств и прекращаем выполнение функции
            unsubscribeFromEvent('devices', 'COMMAND');
            unsubscribeFromEvent('devices', 'MINUTELY');
            return;
        }

        // Проходимся по всем типам устройств
        foreach ($this->device_types as $k => $v) {
            // Класс устройства
            if (isset($v['PARENT_CLASS'])) {
                // Если у устройства есть родительский класс, добавляем класс с указанием родительского класса
                $class_id = addClass($v['CLASS'], $v['PARENT_CLASS']);
            } else {
                // Если у устройства нет родительского класса, добавляем класс без указания родительского класса
                $class_id = addClass($v['CLASS']);
            }
            // Если класс успешно добавлен
            if ($class_id) {
                // Получаем информацию о классе из базы данных
                $class = SQLSelectOne("SELECT * FROM classes WHERE ID=" . $class_id);
                // Если у устройства есть описание, обновляем описание класса в базе данных
                if (isset($v['DESCRIPTION'])) {
                    $class['DESCRIPTION'] = $v['DESCRIPTION'];
                    SQLUpdate('classes', $class);
                }
            }

            // Свойства класса
            if (isset($v['PROPERTIES']) && is_array($v['PROPERTIES'])) {
                // Проходимся по всем свойствам класса
                foreach ($v['PROPERTIES'] as $pk => $pv) {
                    // Добавляем свойство класса с указанием, нужно ли сохранять историю изменений свойства
                    $prop_id = addClassProperty($v['CLASS'], $pk, isset($pv['KEEP_HISTORY']) ? $pv['KEEP_HISTORY'] : 0);
                    // Если свойство успешно добавлено
                    if ($prop_id) {
                        // Получаем информацию о свойстве из базы данных
                        $property = SQLSelectOne("SELECT * FROM properties WHERE ID=" . $prop_id);
                        // Если свойство является массивом
                        if (is_array($pv)) {
                            // Проходимся по всем элементам массива свойства
                            foreach ($pv as $ppk => $ppv) {
                                // Если ключ свойства начинается с символа подчеркивания, пропускаем его
                                if (substr($ppk, 0, 1) == '_') continue;
                                // Если свойство 'KEEP_HISTORY' уже установлено, пропускаем его
                                if ($ppk == 'KEEP_HISTORY' && $property[$ppk]) continue;
                                // Обновляем значение свойства
                                $property[$ppk] = $ppv;
                            }
                            // Обновляем информацию о свойстве в базе данных
                            SQLUpdate('properties', $property);
                        }
                    }
                }
            }

            // Методы класса
            if (isset($v['METHODS']) && is_array($v['METHODS'])) {
                // Проходимся по всем методам класса
                foreach ($v['METHODS'] as $mk => $mv) {
                    // Добавляем метод класса с указанием кода метода и класса, к которому он принадлежит
                    $method_id = addClassMethod($v['CLASS'], $mk, "require(DIR_MODULES.'devices/" . $v['CLASS'] . "_" . $mk . ".php');", 'SDevices');
                    // Если файл метода не существует, создаем его
                    if (!file_exists(dirname(__FILE__) . '/' . $v['CLASS'] . "_" . $mk . ".php")) {
                        $code = '<?php' . "\n\n";
                        @SaveFile(dirname(__FILE__) . "/" . $v['CLASS'] . "_" . $mk . ".php", $code);
                    }
                    // Если метод успешно добавлен
                    if ($method_id) {
                        // Получаем информацию о методе из базы данных
                        $method = SQLSelectOne("SELECT * FROM methods WHERE ID=" . $method_id);
                        // Если метод является массивом
                        if (is_array($mv)) {
                            // Проходимся по всем элементам массива метода
                            foreach ($mv as $mmk => $mmv) {
                                // Если ключ метода начинается с символа подчеркивания, пропускаем его
                                if (substr($mmk, 0, 1) == '_') continue;
                                // Обновляем значение метода
                                $method[$mmk] = $mmv;
                            }
                            // Обновляем информацию о методе в базе данных
                            SQLUpdate('methods', $method);
                        }
                    }
                }
            }

            // Внедрение методов
            if (isset($v['INJECTS']) && is_array($v['INJECTS'])) {
                // Проходимся по всем внедренным методам
                foreach ($v['INJECTS'] as $class_name => $methods) {
                    // Добавляем класс
                    addClass($class_name);
                    // Проходимся по всем методам внедренного класса
                    foreach ($methods as $mk => $mv) {
                        // Разделяем имя объекта и имя метода
                        list($object, $method_name) = explode('.', $mk);
                        // Добавляем объект класса
                        addClassObject($class_name, $object);
                        // Если файл метода не существует, создаем его
                        if (!file_exists(dirname(__FILE__) . "/" . $mv . ".php")) {
                            $code = '<?php' . "\n\n";
                            @SaveFile(dirname(__FILE__) . "/" . $mv . ".php", $code);
                        }
                        // Внедряем код метода в объект
                        injectObjectMethodCode($mk, 'SDevices', "require(DIR_MODULES.'devices/" . $mv . ".php');");
                    }
                }
            }
        }
        // Подписываемся на события 'COMMAND' и 'MINUTELY' для модуля устройств
        subscribeToEvent('devices', 'COMMAND');
        subscribeToEvent('devices', 'MINUTELY');

        // Обновление камер
        $objects = getObjectsByClass('SCameras');
        $total = count($objects);
        for ($i = 0; $i < $total; $i++) {
            $ot = $objects[$i]['TITLE'];
            callMethod($ot . '.updatePreview');
        }

        // Обновление объектов устройств
        $devices = SQLSelect("SELECT ID, LINKED_OBJECT FROM devices");
        foreach ($devices as $device) {
            SQLExec("UPDATE objects SET `SYSTEM`='sdevice" . $device['ID'] . "' WHERE TITLE='" . DBSafe($device['LINKED_OBJECT']) . "' AND `SYSTEM`=''");
        }

        // Обновление объектов групп устройств
        $this->updateGroupObjects();
    }
    /**
     * processSubscription
     *
     * Эта функция обрабатывает подписки на события.
     * Она обрабатывает различные операции, такие как клик по устройству, получение устройства, получение устройств,
     * загрузка всех устройств HTML, клик по устройству и получение устройств.
     *
     * @method void processSubscription($event, &$details)
     * @param string $event Имя события
     * @param array $details Детали события
     * @return void
     * @see module
     * @since 0.1
     */
    function processSubscription($event, &$details)
    {
        // Если событие равно 'COMMAND' и есть идентификатор участника
        if ($event == 'COMMAND' && $details['member_id']) {
            // Включаем файл для обработки команды
            include_once(dirname(__FILE__) . '/processCommand.inc.php');
        }
        // Если событие равно 'MINUTELY'
        if ($event == 'MINUTELY') {
            // Выбираем все активные точки планировщика устройств
            $points = SQLSelect("SELECT devices_scheduler_points.*, devices.LINKED_OBJECT FROM devices_scheduler_points LEFT JOIN devices ON devices_scheduler_points.DEVICE_ID=devices.ID WHERE ACTIVE=1");
            $total = count($points);
            // Проходимся по всем точкам планировщика
            for ($i = 0; $i < $total; $i++) {
                $rec = $points[$i];
                // Если дни не установлены, пропускаем текущую итерацию
                if ($rec['SET_DAYS'] === '') {
                    continue;
                }
                // Разделяем дни на отдельные элементы
                $run_days = explode(',', $rec['SET_DAYS']);
                // Если текущий день не входит в список дней, пропускаем текущую итерацию
                if (!in_array(date('w'), $run_days)) {
                    continue;
                }
                // Преобразуем время запуска в метку времени
                $tm = strtotime(date('Y-m-d') . ' ' . $rec['SET_TIME']);
                // Вычисляем разницу между текущим временем и временем запуска
                $diff = time() - $tm;

                // Преобразуем время последнего запуска в метку времени
                $latestRun = strtotime($rec['LATEST_RUN']);
                // Вычисляем разницу между текущим временем и временем последнего запуска
                $diff2 = time() - $latestRun;

                // Если разница меньше нуля, разница больше или равна 10 минутам или разница между последним запуском и текущим временем меньше или равна 10 минутам, пропускаем текущую итерацию
                if ($diff < 0 || $diff >= 10 * 60 || $diff2 <= 10 * 60) {
                    continue;
                }

                // Проверяем доступ к точке планировщика
                if (!checkAccess('spoint', $rec['ID'])) continue;

                // Получаем связанный объект
                $linked_object = $rec['LINKED_OBJECT'];
                // Удаляем связанный объект из записи
                unset($rec['LINKED_OBJECT']);
                // Обновляем время последнего запуска
                $rec['LATEST_RUN'] = date('Y-m-d H:i:s');
                // Обновляем запись в базе данных
                SQLUpdate('devices_scheduler_points', $rec);
                // Записываем сообщение о запуске точки планировщика
                DebMes("Running point: " . $linked_object . '.' . $rec['LINKED_METHOD'] . ' (' . $rec['VALUE'] . ')', 'devices_schedule');
                // Если значение установлено, вызываем метод с этим значением
                if ($rec['VALUE'] != '') {
                    callMethodSafe($linked_object . '.' . $rec['LINKED_METHOD'], array('value' => $rec['VALUE']));
                } else {
                    // Если значение не установлено, вызываем метод без параметров
                    callMethodSafe($linked_object . '.' . $rec['LINKED_METHOD']);
                }
            }
        }
    }
    /**
     * computePermutations
     *
     * Эта функция вычисляет все возможные перестановки элементов массива.
     * Она использует рекурсивный подход для генерации всех возможных комбинаций.
     *
     * @method array computePermutations($array)
     * @param array $array Массив для перестановок
     * @return array Все возможные перестановки элементов массива
     * @see module
     * @since 0.1
     */
    function computePermutations($array)
    {
        $result = [];
        $recurse = function ($array, $start_i = 0) use (&$result, &$recurse) {
            // Если индекс начала равен индексу последнего элемента массива, добавляем текущую перестановку в результат
            if ($start_i === count($array) - 1) {
                array_push($result, $array);
            }
            // Проходимся по всем элементам массива, начиная с указанного индекса
            for ($i = $start_i; $i < count($array); $i++) {
                // Меняем местами элементы массива по индексам $i и $start_i
                $t = $array[$i];
                $array[$i] = $array[$start_i];
                $array[$start_i] = $t;
                // Рекурсивно вызываем функцию для следующего индекса
                $recurse($array, $start_i + 1);
                // Восстанавливаем исходный порядок элементов массива
                $t = $array[$i];
                $array[$i] = $array[$start_i];
                $array[$start_i] = $t;
            }
        };
        // Вызываем рекурсивную функцию для начального массива
        $recurse($array);
        // Возвращаем результат, содержащий все возможные перестановки
        return $result;
    }
    /**
     * generate_combinations
     *
     * Эта функция генерирует все возможные комбинации среди набора вложенных массивов.
     *
     * @method array generate_combinations(array $data, array &$all = array(), array $group = array(), $value = null, $i = 0)
     * @param array $data Входной массив-контейнер.
     * @param array $all Конечный контейнер (используется внутри функции).
     * @param array $group Подконтейнер (используется внутри функции).
     * @param mixed $val Значение для добавления (используется внутри функции).
     * @param int $i Индекс ключа (используется внутри функции).
     * @return array Все возможные комбинации среди набора вложенных массивов.
     * @see module
     * @since 0.1
     */
    function generate_combinations(array $data, array &$all = array(), array $group = array(), $value = null, $i = 0, $key = null)
    {
        $keys = array_keys($data);
        // Если значение установлено, добавляем его в подконтейнер
        if (isset($value) === true) {
            $group[$key] = $value;
        }
        // Если индекс больше или равен количеству элементов в данных, добавляем подконтейнер в конечный контейнер
        if ($i >= count($data)) {
            array_push($all, $group);
        } else {
            $currentKey = $keys[$i];
            $currentElement = $data[$currentKey];
            // Если в текущем элементе данных нет элементов, рекурсивно вызываем функцию для следующего индекса
            if (count($data[$currentKey]) <= 0) {
                $this->generate_combinations($data, $all, $group, null, $i + 1, $currentKey);
            } elseif (is_array($currentElement)) {
                // Если текущий элемент является массивом, проходимся по всем его элементам и рекурсивно вызываем функцию для каждого из них
                foreach ($currentElement as $val) {
                    $this->generate_combinations($data, $all, $group, $val, $i + 1, $currentKey);
                }
            }
        }
        // Возвращаем конечный контейнер с всеми сгенерированными комбинациями
        return $all;
    }
    /**
     * homebridgeSync
     *
     * Эта функция синхронизирует устройства с HomeBridge.
     * Она проверяет доступность HomeBridge и, если доступна, включает файл для синхронизации.
     *
     * @method void homebridgeSync($device_id = 0, $force_refresh = 0)
     * @param int $device_id Идентификатор устройства (по умолчанию 0).
     * @param int $force_refresh Принудительное обновление (по умолчанию 0).
     * @return void
     * @see module
     * @since 0.1
     */
    function homebridgeSync($device_id = 0, $force_refresh = 0)
    {
        // Проверяем доступность HomeBridge
        if ($this->isHomeBridgeAvailable()) {
            // Включаем файл для синхронизации с HomeBridge
            include_once(dirname(__FILE__) . '/homebridgeSync.inc.php');
        }
    }
    /**
     * admin
     *
     * Функция обрабатывает административный интерфейс модуля.
     * Она обрабатывает различные действия, такие как синхронизация с HomeBridge, поиск устройств, управление группами,
     * управление расписанием, редактирование устройств, быстрое редактирование,
     * рендеринг структуры и удаление устройств.
     *
     * @method void admin(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function admin(&$out)
    {
        // Проверяем, установлен ли источник данных и не переданы ли параметры GET или POST
        if (isset($this->data_source) && !$_GET['data_source'] && !$_POST['data_source']) {
            $out['SET_DATASOURCE'] = 1;
        }
        // Если источник данных равен 'devices' или пустой строке
        if ($this->data_source == 'devices' || $this->data_source == '') {

            // Если режим равен 'homebridgesync', вызываем функцию синхронизации с HomeBridge и перенаправляем на главную страницу
            if ($this->mode == 'homebridgesync') {
                $this->homebridgeSync();
                $this->redirect("?");
            }

            // Если режим просмотра пустой или равен 'search_devices', вызываем функцию поиска устройств
            if ($this->view_mode == '' || $this->view_mode == 'search_devices') {
                $this->search_devices($out);
                // Если доступна HomeBridge, устанавливаем флаг ENABLE_HOMEBRIDGE
                if ($this->isHomeBridgeAvailable()) {
                    $out['ENABLE_HOMEBRIDGE'] = 1;
                }
            }

            // Если режим просмотра равен 'manage_groups', вызываем функцию управления группами
            if ($this->view_mode == 'manage_groups') {
                $this->manage_groups($out);
            }

            // Если режим просмотра равен 'schedule', вызываем функцию управления расписанием
            if ($this->view_mode == 'schedule') {
                $this->manage_schedule($out);
            }

            // Если режим просмотра равен 'edit_devices', вызываем функцию редактирования устройств
            if ($this->view_mode == 'edit_devices') {
                $this->edit_devices($out, $this->id);
            }

            // Если режим просмотра равен 'quick_edit', вызываем функцию быстрого редактирования
            if ($this->view_mode == 'quick_edit') {
                $this->quick_edit($out);
            }

            // Если режим просмотра равен 'render_structure', вызываем функцию рендеринга структуры
            if ($this->view_mode == 'render_structure') {
                $this->renderStructure();
                $this->redirect("?");
            }

            // Если режим просмотра равен 'delete_devices', вызываем функцию удаления устройств
            if ($this->view_mode == 'delete_devices') {
                $this->delete_devices($this->id);
                $this->redirect("?type=" . gr('type') . '&location_id=' . gr('location_id') . '&group_name=' . gr('group_name'));
            }
        }
    }
    /**
     * isHomeBridgeAvailable
     *
     * Эта функция проверяет доступность HomeBridge.
     * Она выполняет запрос к базе данных, чтобы найти объект с названием 'HomeBridge'.
     * Если такой объект существует, функция возвращает true, иначе false.
     *
     * @method bool isHomeBridgeAvailable()
     * @return bool true если HomeBridge доступна, иначе false
     * @see module
     * @since 0.1
     */
    function isHomeBridgeAvailable()
    {
        //return true; // temporary
        // Выполняем запрос к базе данных, чтобы найти объект с названием 'HomeBridge'
        $tmp = SQLSelectOne("SELECT ID FROM objects WHERE TITLE='HomeBridge'");
        // Если объект найден, возвращаем true
        if (isset($tmp['ID'])) {
            return true;
        } else {
            // Если объект не найден, возвращаем false
            return false;
        }
    }
    /**
     * manage_groups
     *
     * Эта функция управляет группами устройств.
     * Она включает файл для управления группами устройств.
     *
     * @method void manage_groups(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function manage_groups(&$out)
    {
        // Включаем файл для управления группами устройств
        require(dirname(__FILE__) . '/devices_manage_groups.inc.php');
    }
    /**
     * manage_schedule
     *
     * Эта функция управляет расписанием устройств.
     * Она включает файл для управления расписанием устройств.
     *
     * @method void manage_schedule(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function manage_schedule(&$out)
    {
        // Включаем файл для управления расписанием устройств
        require(dirname(__FILE__) . '/devices_manage_schedule.inc.php');
    }
    /**
     * usual
     *
     * Эта функция отвечает за обычный режим работы модуля.
     * Она обрабатывает различные действия, такие как поиск устройств, управление группами,
     * управление расписанием, редактирование устройств, быстрое редактирование,
     * удаление устройств и рендеринг структуры.
     *
     * @method void usual(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function usual(&$out)
    {
        // Получаем значение параметра 'view' из запроса
        $view = gr('view');
        // Если значение параметра 'view' установлено, присваиваем его свойству класса
        if ($view) $this->view = $view;

        // Проверяем, является ли текущий запрос AJAX-запросом
        if ($this->ajax) {
            // Устанавливаем заголовки ответа
            header("HTTP/1.0: 200 OK\n");
            header('Content-Type: text/html; charset=utf-8');
            // Получаем значение параметра 'op' из запроса
            $op = gr('op');
            // Инициализируем массив для хранения результатов
            $res = array();
            // Если значение параметра 'op' равно 'clicked'
            if ($op == 'clicked') {
                // Получаем значение параметра 'object' из запроса
                $object = gr('object');
                // Если значение параметра 'object' не пустое
                if ($object != '') {
                    // Выполняем запрос к базе данных, чтобы найти устройство с соответствующим связанным объектом
                    $device_rec = SQLSelectOne("SELECT ID, TITLE FROM devices WHERE LINKED_OBJECT='" . DBSafe($object) . "'");
                    // Если устройство найдено
                    if ($device_rec['ID']) {
                        // Обновляем время последнего клика по устройству в базе данных
                        SQLExec("UPDATE devices SET CLICKED=NOW() WHERE ID='" . $device_rec['ID'] . "'");
                        // Записываем действие в журнал
                        logAction('device_clicked', $device_rec['TITLE']);
                    }
                }
            }
            // Если значение параметра 'op' равно 'get_device'
            if ($op == 'get_device') {
                // Получаем значение параметра 'id' из запроса
                $id = gr('id');
                // Обрабатываем устройство с указанным идентификатором и сохраняем результат в массив результатов
                $res = $this->processDevice($id, $view);
            }
            // Если значение параметра 'op' равно 'get_devices'
            if ($op == 'get_devices') {
                // Получаем значение параметра 'ids' из запроса
                $ids = gr('ids');
                // Разделяем строку идентификаторов на отдельные элементы
                $tmp = explode(',', $ids);
                // Инициализируем массив для хранения результатов
                $res = array();
                // Проходимся по всем идентификаторам
                foreach ($tmp as $id) {
                    // Если идентификатор пустой, пропускаем текущую итерацию
                    if (!$id) continue;
                    // Обрабатываем устройство с указанным идентификатором
                    $record = $this->processDevice($id, $view);
                    // Если устройство не найдено, пропускаем текущую итерацию
                    if (!$record['DEVICE_ID']) continue;
                    // Добавляем обработанное устройство в массив результатов
                    $res['devices'][] = $record;
                }
            }
            // Если значение параметра 'op' равно 'loadAllDevicesHTML'
            if ($op == 'loadAllDevicesHTML') {
                // Выполняем запрос к базе данных, чтобы получить все устройства, которые не являются системными и не архивированы
                $devices = SQLSelect("SELECT ID, LINKED_OBJECT FROM devices WHERE SYSTEM_DEVICE=0 AND ARCHIVED=0");
                // Получаем общее количество устройств
                $total = count($devices);
                // Проходимся по всем устройствам
                for ($i = 0; $i < $total; $i++) {
                    // Если устройство имеет связанный объект
                    if ($devices[$i]['LINKED_OBJECT']) {
                        // Обрабатываем устройство и сохраняем результат в переменную
                        $processed = $this->processDevice($devices[$i]['ID'], $view);
                        // Сохраняем HTML-представление устройства в массиве устройств
                        $devices[$i]['HTML'] = $processed['HTML'];
                    }
                }
                // Сохраняем массив устройств в массив результатов
                $res['DEVICES'] = $devices;
            }
            // Выводим результаты в формате JSON и завершаем выполнение скрипта
            echo json_encode($res);
            exit;
        }

        // Проверяем, является ли текущее действие родительского объекта 'apps'
        if ($this->owner->action == 'apps') {
            // Если действие равно 'apps', перенаправляем на страницу модуля устройств
            //$this->redirect(ROOTHTML."module/devices.html");
        }

        // Получаем значение параметра 'location_id' из запроса
        $location_id = gr('location_id');
        // Получаем значение параметра 'type' из запроса
        $type = gr('type');
        // Получаем значение параметра 'collection' из запроса
        $collection = gr('collection');

        // Инициализируем строку запроса
        $qry = "1";

        // Получаем значение параметра 'linked_object' из запроса
        $linked_object = gr('linked_object');
        // Если значение параметра 'linked_object' установлено
        if ($linked_object) {
            // Выполняем запрос к базе данных, чтобы найти устройство с соответствующим связанным объектом
            $device_rec = SQLSelectOne("SELECT ID FROM devices WHERE LINKED_OBJECT='" . DbSafe($linked_object) . "'");
            // Если устройство найдено
            if ($device_rec['ID']) {
                // Присваиваем идентификатор устройства свойству класса
                $this->id = $device_rec['ID'];
            }
        }

        // Генерируем уникальный идентификатор для устройства
        if (isset($this->id)) {
            $out['UNIQ'] = uniqid('dev' . $this->id);
            // Добавляем условие в строку запроса, чтобы выбрать устройство с указанным идентификатором
            $qry .= " AND devices.ID=" . (int)$this->id;
            // Устанавливаем флаг, что мы работаем с одним устройством
            $out['SINGLE_DEVICE'] = 1;
            // Устанавливаем режим просмотра
            $out['VIEW'] = $this->view;
        }
        // Если установлены параметры 'location_id', 'type' или 'collection'
        if ($location_id || $type || $collection) {
            // Инициализируем строку запроса и порядок сортировки
            $qry = "1 AND SYSTEM_DEVICE=0 AND ARCHIVED=0";
            $orderby = 'locations.PRIORITY DESC, LOCATION_ID, TYPE, TITLE';
            // Если в параметре 'type' содержится идентификатор локации
            if (preg_match('/loc(\d+)/', $type, $m)) {
                // Извлекаем идентификатор локации и очищаем параметр 'type'
                $location_id = $m[1];
                $type = '';
            }
            // Если в параметре 'type' содержится имя коллекции
            if (preg_match('/col\_(\w+)/', $type, $m)) {
                // Извлекаем имя коллекции и очищаем параметр 'type'
                $collection = $m[1];
                $type = '';
            }
            // Если установлен параметр 'location_id'
            if ($location_id) {
                // Если 'location_id' не равен 'all'
                if ($location_id != 'all') {
                    // Добавляем условие в строку запроса, чтобы выбрать устройства с указанным идентификатором локации
                    $qry .= " AND devices.LOCATION_ID=" . (int)$location_id;
                    // Выполняем запрос к базе данных, чтобы получить информацию о локации
                    $location = SQLSelectOne("SELECT * FROM locations WHERE ID=" . (int)$location_id);
                    // Сохраняем информацию о локации в массиве вывода
                    foreach ($location as $k => $v) {
                        $out['LOCATION_' . $k] = $v;
                    }
                    // Сохраняем название локации в массиве вывода
                    $out['TITLE'] = $location['TITLE'];
                } else {
                    // Если 'location_id' равен 'all', устанавливаем соответствующий флаг в массиве вывода
                    $out['LOCATION_ID'] = 'All';
                    // Добавляем условие в строку запроса, чтобы выбрать все устройства
                    $qry .= " AND 1";
                }
            }
            // Если установлен параметр 'type'
            if ($type) {
                // Если 'type' не равен 'all'
                if ($type != 'all') {
                    // Добавляем условие в строку запроса, чтобы выбрать устройства с указанным типом
                    $qry .= " AND devices.TYPE LIKE '" . DBSafe($type) . "'";
                    // Сохраняем название типа устройства в массиве вывода
                    $out['TITLE'] = $this->device_types[$type]['TITLE'];
                } else {
                    // Если 'type' равен 'all', изменяем порядок сортировки
                    $orderby = 'TYPE, locations.PRIORITY DESC, locations.TITLE, LOCATION_ID, TITLE';
                }
                // Сохраняем тип устройства в массиве вывода
                $out['TYPE'] = $type;
            }
            // Если установлена коллекция
            if ($collection != '') {
                // Получаем идентификаторы устройств, входящих в коллекцию
                $ids = $this->getCollectionIds($collection);
                // Добавляем условие в строку запроса, чтобы выбрать устройства с указанными идентификаторами
                $qry .= " AND devices.ID IN (" . implode(',', $ids) . ")";
                // Сохраняем название коллекции в массиве вывода
                $out['TITLE'] = constant('LANG_DEVICES_COLLECTION_' . strtoupper($collection));
                // Сохраняем имя коллекции в массиве вывода
                $out['COLLECTION'] = $collection;
            }
            // Инициализируем переменные для хранения названий локации и типа устройства
            $location_title = '';
            $type_title = '';
            // Выполняем запрос к базе данных, чтобы получить устройства, соответствующие условиям запроса
            $devices = SQLSelect("SELECT devices.*, locations.TITLE as LOCATION_TITLE FROM devices LEFT JOIN locations ON devices.LOCATION_ID=locations.ID WHERE $qry ORDER BY $orderby");
            // Получаем общее количество устройств
            $total = count($devices);
            // Проходимся по всем устройствам
            for ($i = 0; $i < $total; $i++) {
                // Если тип устройства равен 'all'
                if ($type == 'all') {
                    // Получаем название типа устройства
                    $devices[$i]['LOCATION_TITLE'] = $this->device_types[$devices[$i]['TYPE']]['TITLE'];
                    // Если название типа устройства отличается от предыдущего
                    if ($devices[$i]['LOCATION_TITLE'] != $location_title) {
                        // Устанавливаем флаг, что это новая локация
                        $devices[$i]['NEW_LOCATION'] = 1;
                        // Сохраняем текущее название типа устройства
                        $location_title = $devices[$i]['LOCATION_TITLE'];
                    }
                } else {
                    // Если название типа устройства отличается от предыдущего и не установлено название локации в массиве вывода
                    if ($devices[$i]['LOCATION_TITLE'] != $location_title && !isset($out['LOCATION_TITLE'])) {
                        // Устанавливаем флаг, что это новая локация
                        $devices[$i]['NEW_LOCATION'] = 1;
                        // Сохраняем текущее название типа устройства
                        $location_title = $devices[$i]['LOCATION_TITLE'];
                    }
                    // Если название типа устройства отличается от предыдущего
                    if ($this->device_types[$devices[$i]['TYPE']]['TITLE'] != $type_title) {
                        // Сохраняем текущее название типа устройства
                        $type_title = $this->device_types[$devices[$i]['TYPE']]['TITLE'];
                        // Устанавливаем флаг, что это новый тип устройства
                        $devices[$i]['NEW_TYPE'] = 1;
                    }
                }
            }
        } else {
            // Устанавливаем порядок сортировки
            $orderby = 'locations.PRIORITY DESC, locations.TITLE, LOCATION_ID, TYPE, TITLE';
            // Добавляем условие в строку запроса, чтобы выбрать все устройства, которые не являются системными и не архивированы
            $qry .= " AND SYSTEM_DEVICE=0 AND ARCHIVED=0";
            // Устанавливаем флаг, что мы работаем со всеми устройствами
            $out['ALL_DEVICES'] = 1;
            // Выполняем запрос к базе данных, чтобы получить все устройства, которые не являются системными и не архивированы
            $devices = SQLSelect("SELECT devices.*, locations.TITLE as LOCATION_TITLE FROM devices LEFT JOIN locations ON devices.LOCATION_ID=locations.ID WHERE $qry ORDER BY $orderby");
            // Выполняем запрос к базе данных, чтобы получить недавно использованные устройства
            $recent_devices = SQLSelect("SELECT devices.* FROM devices WHERE !IsNull(CLICKED) ORDER BY CLICKED DESC LIMIT 10");
        }

        // Если устройства найдены
        if ($devices[0]['ID']) {
            // Если установлены параметры 'location_id', 'type' или 'collection'
            if ($location_id || $type || 1) {
                // Получаем общее количество устройств
                $total = count($devices);
                // Проходимся по всем устройствам
                for ($i = 0; $i < $total; $i++) {
                    // Если устройство имеет связанный объект
                    if ($devices[$i]['LINKED_OBJECT']) {
                        // Обрабатываем устройство и сохраняем результат в переменную
                        $processed = $this->processDevice($devices[$i]['ID'], $this->view);
                        // Сохраняем HTML-представление устройства в массиве устройств
                        $devices[$i]['HTML'] = $processed['HTML'];
                    }
                }
            }
            // Сохраняем массив устройств в массиве вывода
            $out['DEVICES'] = $devices;
            // Если свойство класса 'id' установлено
            if ($this->id) {
                // Завершаем выполнение функции
                return;
            }
        }

        // Выполняем запрос к базе данных, чтобы получить все локации, упорядоченные по приоритету и названию
        $locations = SQLSelect("SELECT ID, TITLE FROM locations ORDER BY PRIORITY DESC, TITLE");
        // Получаем общее количество устройств
        $total_devices = count($devices);
        // Если устройства найдены
        if ($total_devices) {
            // Инициализируем массивы для хранения избранных, предупреждающих и проблемных устройств
            $favorite_devices = array();
            $warning_devices = array();
            $problem_devices = array();
            // Проходимся по всем устройствам
            for ($idv = 0; $idv < $total_devices; $idv++) {
                // Если устройство помечено как избранное
                if ($devices[$idv]['FAVORITE']) {
                    // Добавляем устройство в массив избранных устройств
                    $favorite_devices[] = $devices[$idv];
                } elseif ($devices[$idv]['LINKED_OBJECT']) {
                    // Если устройство имеет связанный объект

                    // Проверяем, является ли устройство предупреждающим
                    if (
                        gg($devices[$idv]['LINKED_OBJECT'] . '.normalValue') == '0' &&
                        gg($devices[$idv]['LINKED_OBJECT'] . '.notify') == '1'
                    ) {
                        // Добавляем устройство в массив предупреждающих устройств
                        $warning_devices[] = $devices[$idv];
                        // Устанавливаем флаг, что это новая секция
                        $warning_devices[0]['NEW_SECTION'] = 1;
                        // Устанавливаем название секции
                        $warning_devices[0]['SECTION_TITLE'] = LANG_WARNING;
                    } elseif (
                        // Проверяем, является ли устройство проблемным
                        ($devices[$idv]['TYPE'] == 'motion' ||
                            $devices[$idv]['TYPE'] == 'openclose' ||
                            $devices[$idv]['TYPE'] == 'leak' ||
                            $devices[$idv]['TYPE'] == 'smoke' ||
                            $devices[$idv]['TYPE'] == 'counter' ||
                            preg_match('/^sensor/', $devices[$idv]['TYPE']) ||
                            $this->device_types[$devices[$idv]['TYPE']]['PARENT_CLASS'] == 'SSensors' ||
                            (int)gg($devices[$idv]['LINKED_OBJECT'] . '.aliveTimeout') > 0
                        ) && gg($devices[$idv]['LINKED_OBJECT'] . '.alive') === '0'
                    ) {
                        // Добавляем устройство в массив проблемных устройств
                        $problem_devices[] = $devices[$idv];
                        // Устанавливаем флаг, что это новая секция
                        $problem_devices[0]['NEW_SECTION'] = 1;
                        // Устанавливаем название секции
                        $problem_devices[0]['SECTION_TITLE'] = LANG_OFFLINE;
                    }
                }
            }

            // Если в массиве избранных устройств есть элементы
            if (count($favorite_devices) > 0) {
                // Сортируем массив избранных устройств по значению 'FAVORITE'
                usort($favorite_devices, function ($a, $b) {
                    if ($a['FAVORITE'] == $b['FAVORITE']) {
                        return 0;
                    }
                    return ($a['FAVORITE'] > $b['FAVORITE']) ? -1 : 1;
                });
            }

            // Добавляем предупреждающие устройства в массив избранных устройств
            foreach ($warning_devices as $device) {
                $favorite_devices[] = $device;
            }

            // Если в массиве недавно использованных устройств есть элементы
            if (isset($recent_devices[0]['ID'])) {
                // Устанавливаем флаг, что это новая секция
                $recent_devices[0]['NEW_SECTION'] = 1;
                // Устанавливаем название секции
                $recent_devices[0]['SECTION_TITLE'] = LANG_RECENTLY_USED;
                // Проходимся по всем недавно использованным устройствам
                foreach ($recent_devices as &$device) {
                    // Если устройство имеет связанный объект
                    if ($device['LINKED_OBJECT']) {
                        // Обрабатываем устройство и сохраняем результат в переменную
                        $processed = $this->processDevice($device['ID']);
                        // Сохраняем HTML-представление устройства в массиве устройств
                        $device['HTML'] = $processed['HTML'];
                    }
                    // Добавляем устройство в массив избранных устройств
                    $favorite_devices[] = $device;
                }
            }

            // Добавляем проблемные устройства в массив избранных устройств
            foreach ($problem_devices as $device) {
                $favorite_devices[] = $device;
            }

            // Получаем общее количество устройств в массиве избранных устройств
            $devices_count = count($favorite_devices);

            // Если в массиве избранных устройств есть элементы
            if ($devices_count > 0) {
                // Создаем запись для секции избранных устройств
                $loc_rec = array();
                $loc_rec['ID'] = 0;
                $loc_rec['TITLE'] = LANG_FAVORITES;
                $loc_rec['DEVICES'] = $favorite_devices;
                $loc_rec['DEVICES_TOTAL'] = $devices_count;
                // Добавляем запись в начало массива локаций
                array_unshift($locations, $loc_rec);
            }
        }

        // Получаем общее количество локаций
        $total = count($locations);
        // Проходимся по всем локациям
        for ($i = 0; $i < $total; $i++) {
            // Если у локации есть идентификатор
            if ($locations[$i]['ID']) {
                // Инициализируем счетчик устройств
                $devices_count = 0;
                // Если есть устройства
                if ($total_devices) {
                    // Проходимся по всем устройствам
                    for ($idv = 0; $idv < $total_devices; $idv++) {
                        // Если идентификатор локации устройства совпадает с идентификатором текущей локации
                        if ($devices[$idv]['LOCATION_ID'] == $locations[$i]['ID']) {
                            // Увеличиваем счетчик устройств
                            $devices_count++;
                            // Добавляем устройство в массив устройств текущей локации
                            $locations[$i]['DEVICES'][] = $devices[$idv];
                        }
                    }
                }
                // Сохраняем общее количество устройств в массиве локации
                $locations[$i]['DEVICES_TOTAL'] = $devices_count;
            }
            // Устанавливаем индекс текущей локации
            $locations[$i]['INDEX'] = $i;
        }
        // Сохраняем массив локаций в массиве вывода
        $out['GROUPS'] = $locations;

        // Инициализируем массив для хранения типов устройств
        $types = array();

        // Если свойство класса 'device_types' является массивом
        if (is_array($this->device_types)) {
            // Проходимся по всем типам устройств
            foreach ($this->device_types as $k => $v) {
                // Если у типа устройства есть название
                if (isset($v['TITLE'])) {
                    // Создаем запись для типа устройства
                    $type_rec = array('NAME' => $k, 'TITLE' => $v['TITLE']);
                    // Выполняем запрос к базе данных, чтобы получить количество устройств данного типа
                    $tmp = SQLSelectOne("SELECT COUNT(*) AS TOTAL FROM devices WHERE SYSTEM_DEVICE=0 AND ARCHIVED=0 AND TYPE='" . $k . "'");
                    // Сохраняем количество устройств в записи
                    $type_rec['TOTAL'] = (int)$tmp['TOTAL'];
                    // Если количество устройств больше нуля
                    if ($type_rec['TOTAL'] > 0) {
                        // Добавляем запись в массив типов устройств
                        $types[] = $type_rec;
                    }
                }
            }
            // Сортируем массив типов устройств по названию
            usort($types, function ($a, $b) {
                return strcmp($a["TITLE"], $b["TITLE"]);
            });
        }

        // Обрабатываем коллекции устройств
        $col_name = 'is_heating';
        $col_ids = $this->getCollectionIds($col_name);
        $col_total = count($col_ids) - 1;
        $col = array('NAME' => 'col_' . $col_name, 'TITLE' => LANG_DEVICES_COLLECTION_IS_HEATING, 'TOTAL' => $col_total);
        if ($col_total > 0) array_unshift($types, $col);

        $col_name = 'is_on';
        $col_ids = $this->getCollectionIds($col_name);
        $col_total = count($col_ids) - 1;
        $col = array('NAME' => 'col_' . $col_name, 'TITLE' => LANG_DEVICES_COLLECTION_IS_ON, 'TOTAL' => $col_total);
        if ($col_total > 0) array_unshift($types, $col);

        $col_name = 'is_open';
        $col_ids = $this->getCollectionIds($col_name);
        $col_total = count($col_ids) - 1;
        $col = array('NAME' => 'col_' . $col_name, 'TITLE' => LANG_DEVICES_COLLECTION_IS_OPEN, 'TOTAL' => $col_total);
        if ($col_total > 0) array_unshift($types, $col);

        // Обрабатываем локации
        $list_locations = $locations;
        if (is_array($list_locations)) {
            // Сортируем массив локаций по названию
            usort($list_locations, function ($a, $b) {
                return strcmp($a["TITLE"], $b["TITLE"]);
            });
            // Добавляем запись для локаций в массив типов устройств
            $types[] = array('NAME' => '', 'TITLE' => LANG_LOCATION);
            // Проходимся по всем локациям
            foreach ($list_locations as $location) {
                // Если название локации не равно 'Избранное'
                if ($location['TITLE'] == LANG_FAVORITES) continue;
                // Добавляем запись для локации в массив типов устройств
                $types[] = array('NAME' => 'loc' . $location['ID'], 'TITLE' => $location['TITLE'], 'TOTAL' => $location['DEVICES_TOTAL']);
            }
        }

        // Сохраняем массив типов устройств в массиве вывода
        $out['TYPES'] = $types;
    }
    /**
     * getCollectionIds
     *
     * Эта функция возвращает идентификаторы устройств, которые входят в указанную коллекцию.
     * Она обрабатывает различные коллекции, такие как 'is_on', 'is_open', 'is_heating'.
     *
     * @method array getCollectionIds($collection)
     * @param string $collection Название коллекции
     * @return array Идентификаторы устройств, которые входят в указанную коллекцию
     * @see module
     * @since 0.1
     */
    function getCollectionIds($collection)
    {
        $ids = array(0);
        if ($collection == 'is_on') {
            // Выполняем запрос к базе данных, чтобы получить устройства типа 'relay', 'dimmer', 'rgb'
            $devices = SQLSelect("SELECT ID, LINKED_OBJECT FROM devices WHERE devices.TYPE IN ('relay','dimmer','rgb')");
            // Проходимся по всем устройствам
            foreach ($devices as $device) {
                // Если статус устройства равен 'on'
                if (gg($device['LINKED_OBJECT'] . '.status')) {
                    // Добавляем идентификатор устройства в массив идентификаторов
                    $ids[] = $device['ID'];
                }
            }
        } elseif ($collection == 'is_open') {
            // Выполняем запрос к базе данных, чтобы получить устройства типа 'openable', 'openclose'
            $devices = SQLSelect("SELECT ID, LINKED_OBJECT FROM devices WHERE devices.TYPE IN ('openable','openclose')");
            // Проходимся по всем устройствам
            foreach ($devices as $device) {
                // Если статус устройства равен 'off'
                if (!gg($device['LINKED_OBJECT'] . '.status')) {
                    // Добавляем идентификатор устройства в массив идентификаторов
                    $ids[] = $device['ID'];
                }
            }
        } elseif ($collection == 'is_heating') {
            // Выполняем запрос к базе данных, чтобы получить устройства типа 'thermostat'
            $devices = SQLSelect("SELECT ID, LINKED_OBJECT FROM devices WHERE devices.TYPE IN ('thermostat')");
            // Проходимся по всем устройствам
            foreach ($devices as $device) {
                // Если статус устройства равен 'on'
                if (gg($device['LINKED_OBJECT'] . '.relay_status')) {
                    // Добавляем идентификатор устройства в массив идентификаторов
                    $ids[] = $device['ID'];
                }
            }
        }
        // Возвращаем массив идентификаторов устройств
        return $ids;
    }
    /**
     * devices search
     *
     * Функция для поиска устройств.
     * Она включает в себя логику поиска, определенную в файле 'devices_search.inc.php'.
     *
     * @method void devices_search(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function search_devices(&$out)
    {
        // Включаем файл 'devices_search.inc.php', который содержит логику поиска устройств
        require(dirname(__FILE__) . '/devices_search.inc.php');
    }

    /**
     * devices edit/add
     *
     * Функция для редактирования или добавления устройств.
     * Она включает в себя логику редактирования/добавления,
     * определенную в файле 'devices_edit.inc.php'.
     *
     * @method void edit_devices(&$out, $id)
     * @param array $out Массив для вывода данных
     * @param int $id Идентификатор устройства для редактирования (0 для добавления нового устройства)
     * @return void
     * @see module
     * @since 0.1
     */
    function edit_devices(&$out, $id)
    {
        // Включаем файл 'devices_edit.inc.php', который содержит логику редактирования/добавления устройств
        require(dirname(__FILE__) . '/devices_edit.inc.php');
    }
    /**
     * quick_edit
     *
     * Функция для быстрого редактирования устройств.
     * Она включает в себя логику быстрого редактирования, определенную в файле 'devices_quick_edit.inc.php'.
     *
     * @method void quick_edit(&$out)
     * @param array $out Массив для вывода данных
     * @return void
     * @see module
     * @since 0.1
     */
    function quick_edit(&$out)
    {
        // Включаем файл 'devices_quick_edit.inc.php', который содержит логику быстрого редактирования устройств
        require(dirname(__FILE__) . '/devices_quick_edit.inc.php');
    }
    /**
     * devices delete record
     *
     * Функция для удаления записи об устройстве.
     * Она также выполняет дополнительные действия, связанные с удалением устройства, такие как удаление связанных элементов, объектов и команд.
     *
     * @method void delete_devices($id)
     * @param int $id Идентификатор устройства для удаления
     * @return void
     * @see module
     * @since 0.1
     */
    function delete_devices($id)
    {
        // Получаем запись об устройстве по идентификатору
        $rec = SQLSelectOne("SELECT * FROM devices WHERE ID='$id'");

        // Подготовка данных для удаления устройства из HomeBridge
        $payload = array();
        $payload['name'] = $rec['LINKED_OBJECT'];
        sg('HomeBridge.to_remove', json_encode($payload));

        // Удаление связанных элементов
        $elements = SQLSelect("SELECT * FROM elements WHERE `SYSTEM`='sdevice" . $rec['ID'] . "'");
        $total = count($elements);
        for ($i = 0; $i < $total; $i++) {
            SQLExec("DELETE FROM elm_states WHERE ELEMENT_ID=" . $elements[$i]['ID']);
            SQLExec("DELETE FROM elements WHERE ID=" . $elements[$i]['ID']);
        }

        // Удаление связанных объектов
        $objects = SQLSelect("SELECT ID FROM objects WHERE `SYSTEM`='sdevice" . $rec['ID'] . "'");
        $total = count($objects);
        for ($i = 0; $i < $total; $i++) {
            deleteObject($objects[$i]['ID']);
        }

        // Удаление связанных команд
        $tables = array('commands');
        $total = count($tables);
        for ($i = 0; $i < $total; $i++) {
            SQLExec("DELETE FROM " . $tables[$i] . " WHERE `SYSTEM`='sdevice" . $rec['ID'] . "'");
        }

        // Удаление связей устройств
        SQLExec("DELETE FROM devices_linked WHERE DEVICE1_ID='" . $rec['ID'] . "' OR DEVICE2_ID='" . $rec['ID'] . "'");

        // Удаление самого устройства
        SQLExec("DELETE FROM devices WHERE ID='" . $rec['ID'] . "'");
    }
    /**
     * addDevice
     *
     * Функция для добавления нового устройства.
     * Она принимает тип устройства и опции для настройки устройства, такие как связанный объект, название, локация и другие параметры.
     *
     * @method int addDevice($device_type, $options = 0)
     * @param string $device_type Тип устройства
     * @param array $options Опции для настройки устройства
     * @return int Идентификатор добавленного устройства или 0 в случае ошибки
     * @see module
     * @since 0.1
     */
    function addDevice($device_type, $options = 0)
    {
        // Устанавливаем словарь устройств
        $this->setDictionary();
        // Получаем детали типа устройства
        $type_details = $this->getTypeDetails($device_type);

        // Если опции не являются массивом, инициализируем их как пустой массив
        if (!is_array($options)) {
            $options = array();
        }
        // Если тип устройства не определен в словаре устройств, возвращаем 0
        if (!is_array($this->device_types[$device_type])) {
            return 0;
        }

        // Если указаны параметры для связанной таблицы и идентификатор записи в таблице
        if ($options['TABLE'] && $options['TABLE_ID']) {
            // Получаем запись из указанной таблицы
            $table_rec = SQLSelectOne("SELECT * FROM " . $options['TABLE'] . " WHERE ID=" . $options['TABLE_ID']);
            // Если запись не найдена, возвращаем 0
            if (!$table_rec['ID']) {
                return 0;
            }
        }

        // Если указано связанное устройство
        if ($options['LINKED_OBJECT'] != '') {
            // Проверяем, существует ли уже устройство с таким связанным объектом
            $old_device = SQLSelectOne("SELECT ID FROM devices WHERE LINKED_OBJECT LIKE '" . DBSafe($options['LINKED_OBJECT']) . "'");
            // Если устройство найдено, возвращаем его идентификатор
            if ($old_device['ID']) return $old_device['ID'];
            // Иначе, сохраняем связанный объект в записи
            $rec['LINKED_OBJECT'] = $options['LINKED_OBJECT'];
        }

        // Инициализируем запись для нового устройства
        $rec = array();
        $rec['TYPE'] = $device_type;
        // Если указано название, сохраняем его в записи
        if ($options['TITLE']) {
            $rec['TITLE'] = $options['TITLE'];
        } else {
            // Иначе, устанавливаем название по умолчанию
            $rec['TITLE'] = 'New device ' . date('H:i');
        }
        // Если указана локация, сохраняем ее в записи
        if ($options['LOCATION_ID']) {
            $rec['LOCATION_ID'] = $options['LOCATION_ID'];
        }
        // Добавляем запись в базу данных и сохраняем идентификатор нового устройства
        $rec['ID'] = SQLInsert('devices', $rec);

        // Если указана локация, получаем название локации
        if ($rec['LOCATION_ID']) {
            $location_title = getRoomObjectByLocation($rec['LOCATION_ID'], 1);
        }

        // Если не указан связанный объект, создаем новый объект для устройства
        if (!$rec['LINKED_OBJECT']) {
            $prefix = ucfirst($rec['TYPE']);
            $new_object_title = $prefix . $this->getNewObjectIndex($type_details['CLASS']);
            $object_id = addClassObject($type_details['CLASS'], $new_object_title, 'sdevice' . $rec['ID']);
            $rec['LINKED_OBJECT'] = $new_object_title;
            // Если название устройства по умолчанию, обновляем его на название связанного объекта
            if (preg_match('/New device .+/', $rec['TITLE'])) {
                $rec['TITLE'] = $rec['LINKED_OBJECT'];
            }
            // Обновляем запись в базе данных
            SQLUpdate('devices', $rec);

            $object_rec = SQLSelectOne("SELECT * FROM objects WHERE ID=" . $object_id);
            if (isset($object_rec['ID'])) {
                $object_rec['DESCRIPTION'] = $rec['TITLE'];
                SQLUpdate('objects', $object_rec);
            }
        }

        // Если указаны параметры для связанной таблицы и идентификатор записи в таблице
        if ($table_rec['ID']) {
            // Добавляем устройство в связанную таблицу
            $this->addDeviceToSourceTable($options['TABLE'], $table_rec['ID'], $rec['ID']);
        }

        // Если указано добавление устройства в меню
        if ($options['ADD_MENU']) {
            // Добавляем устройство в меню
            $this->addDeviceToMenu($rec['ID']);
        }

        // Если указано добавление устройства в сцену
        if ($options['ADD_SCENE']) {
            // Добавляем устройство в сцену
            $this->addDeviceToScene($rec['ID']);
        }

        // Возвращаем 1, указывая на успешное добавление устройства
        return 1;
    }

    /**
     * addDeviceToSourceTable
     *
     * Функция для добавления устройства в связанную таблицу.
     * Она принимает имя таблицы, идентификатор записи в таблице и идентификатор устройства.
     * Функция определяет свойства и методы связанного объекта устройства в зависимости от его типа и обновляет запись в таблице.
     *
     * @method void addDeviceToSourceTable($table_name, $table_id, $device_id)
     * @param string $table_name Имя таблицы
     * @param int $table_id Идентификатор записи в таблице
     * @param int $device_id Идентификатор устройства
     * @return void
     * @see module
     * @since 0.1
     */
    function addDeviceToSourceTable($table_name, $table_id, $device_id)
    {
        // Получаем запись об устройстве по идентификатору
        $rec = SQLSelectOne("SELECT * FROM devices WHERE ID=" . (int)$device_id);
        // Устанавливаем словарь устройств
        $this->setDictionary();
        // Получаем детали типа устройства
        $type_details = $this->getTypeDetails($rec['TYPE']);

        // Если у устройства нет связанного объекта, возвращаем 0
        if (!$rec['LINKED_OBJECT']) {
            return 0;
        }

        // Получаем запись из указанной таблицы
        $table_rec = SQLSelectOne("SELECT * FROM $table_name WHERE ID=" . DBSafe($table_id));
        // Если запись не найдена, возвращаем 0
        if (!$table_rec['ID']) {
            return 0;
        }

        // Инициализируем переменные для связанного объекта, свойства и метода
        $linked_object = $rec['LINKED_OBJECT'];
        $linked_property = '';
        $linked_method = '';

        // Определяем свойство и метод в зависимости от класса родителя устройства
        if ($type_details['PARENT_CLASS'] == 'SSensors') {
            $linked_property = 'value';
        } elseif ($type_details['PARENT_CLASS'] == 'SControllers') {
            $linked_property = 'status';
        }
        // Определяем свойство в зависимости от типа устройства
        if ($rec['TYPE'] == 'dimmer') {
            $linked_property = 'level';
        }
        if ($rec['TYPE'] == 'rgb') {
            $linked_property = 'color';
        }
        if ($rec['TYPE'] == 'motion') {
            $linked_property = 'status';
            $linked_method = 'motionDetected';
        }
        if ($rec['TYPE'] == 'button') {
            $linked_property = 'status';
            $linked_method = 'pressed';
        }
        if ($rec['TYPE'] == 'switch' || $rec['TYPE'] == 'openclose') {
            $linked_property = 'status';
        }

        // Если запись найдена, обновляем ее с новыми значениями связанного объекта, свойства и метода
        if ($table_rec['ID']) {
            $table_rec['LINKED_OBJECT'] = $linked_object;
            $table_rec['LINKED_PROPERTY'] = $linked_property;
            $table_rec['LINKED_METHOD'] = $linked_method;
            SQLUpdate($table_name, $table_rec);
        }
    }
    /**
     * addDeviceToMenu
     *
     * Функция для добавления устройства в меню.
     * Она принимает идентификатор устройства и идентификатор родительского элемента меню (необязательно).
     * Функция определяет тип элемента меню в зависимости от типа устройства и обновляет или создает запись в таблице команд.
     *
     * @method void addDeviceToMenu($device_id, $parent_id = 0)
     * @param int $device_id Идентификатор устройства
     * @param int $parent_id Идентификатор родительского элемента меню (необязательно)
     * @return void
     * @see module
     * @since 0.1
     */
    function addDeviceToMenu($device_id, $add_menu_id = 0)
    {
        // Получаем запись об устройстве по идентификатору
        $rec = SQLSelectOne("SELECT * FROM devices WHERE ID=" . (int)$device_id);
        // Если запись не найдена, возвращаем 0
        if (!$rec['ID']) {
            return 0;
        }
        // Получаем запись из таблицы команд, связанную с устройством
        $menu_rec = SQLSelectOne("SELECT * FROM commands WHERE `SYSTEM`='" . 'sdevice' . $rec['ID'] . "'");
        // Если запись не найдена, инициализируем новую запись
        if (!$menu_rec['ID']) {
            $menu_rec = array();
        }
        // Если заголовок не указан, используем название устройства
        if (!$menu_rec['TITLE']) {
            $menu_rec['TITLE'] = $rec['TITLE'];
        }
        // Устанавливаем идентификатор родительского элемента меню
        $menu_rec['PARENT_ID'] = (int)$add_menu_id;
        // Устанавливаем системный идентификатор
        $menu_rec['SYSTEM'] = 'sdevice' . $rec['ID'];
        // Устанавливаем связанный объект
        $menu_rec['LINKED_OBJECT'] = $rec['LINKED_OBJECT'];

        // Определяем тип элемента меню и связанное свойство в зависимости от типа устройства
        if ($rec['TYPE'] == 'relay' || $rec['TYPE'] == 'switch') {
            $menu_rec['TYPE'] = 'switch';
            $menu_rec['LINKED_PROPERTY'] = 'status';
            $menu_rec['CUR_VALUE'] = getGlobal($menu_rec['LINKED_OBJECT'] . '.' . $menu_rec['LINKED_PROPERTY']);
        } elseif ($rec['TYPE'] == 'button') {
            $menu_rec['TYPE'] = 'button';
            $menu_rec['LINKED_PROPERTY'] = '';
            $menu_rec['ONCHANGE_METHOD'] = 'pressed';
        } elseif ($rec['TYPE'] == 'dimmer') {
            $menu_rec['TYPE'] = 'sliderbox';
            $menu_rec['MIN_VALUE'] = '0';
            $menu_rec['MAX_VALUE'] = '100';
            $menu_rec['STEP_VALUE'] = '1';
            $menu_rec['LINKED_PROPERTY'] = 'level';
            $menu_rec['CUR_VALUE'] = getGlobal($menu_rec['LINKED_OBJECT'] . '.' . $menu_rec['LINKED_PROPERTY']);
        } else {
            $menu_rec['TYPE'] = 'object';
        }

        // Если запись уже существует, обновляем ее
        if ($menu_rec['ID']) {
            SQLUpdate('commands', $menu_rec);
        } else {
            // Иначе, создаем новую запись
            $menu_rec['ID'] = SQLInsert('commands', $menu_rec);
        }

        // Возвращаем идентификатор созданного или обновленного элемента меню
        return $menu_rec['ID'];
    }
    /**
     * addDeviceToScene
     *
     * Функция для добавления устройства в сцену.
     * Она принимает идентификатор устройства и идентификатор сцены (необязательно).
     * Функция определяет тип элемента сцены в зависимости от типа устройства и обновляет или создает запись в таблице элементов.
     *
     * @method void addDeviceToScene($device_id, $scene_id = 0)
     * @param int $device_id Идентификатор устройства
     * @param int $scene_id Идентификатор сцены (необязательно)
     * @return void
     * @see module
     * @since 0.1
     */
    function addDeviceToScene($device_id, $add_scene_id = 0)
    {
        // Получаем запись об устройстве по идентификатору
        $rec = SQLSelectOne("SELECT * FROM devices WHERE ID=" . (int)$device_id);
        // Если запись не найдена, возвращаем 0
        if (!$rec['ID']) {
            return 0;
        }

        // Если идентификатор сцены не указан, получаем первую сцену из базы данных
        if (!$add_scene_id) {
            $scene_rec = SQLSelectOne("SELECT ID FROM scenes ORDER BY ID LIMIT 1");
            // Если сцена найдена, используем ее идентификатор
            if ($scene_rec['ID']) {
                $add_scene_id = $scene_rec['ID'];
            } else {
                return 0;
            }
        }

        // Получаем запись из таблицы элементов, связанную с устройством и сценой
        $element_rec = SQLSelectOne("SELECT * FROM elements WHERE SCENE_ID=" . (int)$add_scene_id . " AND `SYSTEM`='" . 'sdevice' . $rec['ID'] . "'");
        // Если запись не найдена, инициализируем новую запись и данные мастера
        if (!$element_rec['ID']) {
            $element_rec = array();
            $wizard_data = array();
        } else {
            // Иначе, декодируем данные мастера из записи
            $wizard_data = json_decode($element_rec['WIZARD_DATA'], true);
        }
        // Устанавливаем идентификатор сцены
        $element_rec['SCENE_ID'] = (int)$add_scene_id;
        // Устанавливаем системный идентификатор
        $element_rec['SYSTEM'] = 'sdevice' . $rec['ID'];
        // Если координаты не указаны, задаем случайные значения
        if (!$element_rec['TOP'] && !$element_rec['LEFT']) {
            $element_rec['TOP'] = 10 + rand(0, 300);
            $element_rec['LEFT'] = 10 + rand(0, 300);
        }
        // Если стиль CSS не указан, используем стиль по умолчанию
        if (!$element_rec['CSS_STYLE']) {
            $element_rec['CSS_STYLE'] = 'default';
        }
        // Кодируем данные мастера обратно в JSON
        $element_rec['WIZARD_DATA'] = json_encode($wizard_data) . '';
        // Устанавливаем флаг фона
        $element_rec['BACKGROUND'] = 0;
        // Устанавливаем связанный объект
        $element_rec['LINKED_OBJECT'] = $rec['LINKED_OBJECT'];
        // Устанавливаем название
        $element_rec['TITLE'] = $rec['TITLE'];
        // Устанавливаем флаг легкой конфигурации
        $element_rec['EASY_CONFIG'] = 1;
        // Инициализируем единицу измерения связанного свойства
        $linked_property_unit = '';

        // Устанавливаем тип элемента сцены
        $element_rec['TYPE'] = 'device';
        // Устанавливаем идентификатор устройства
        $element_rec['DEVICE_ID'] = $rec['ID'];
        /*
    // Закомментированный код для определения типа элемента сцены и связанного свойства в зависимости от типа устройства
    if ($rec['TYPE']=='relay' || $rec['TYPE']=='dimmer' || $rec['TYPE']=='switch') {
        $element_rec['TYPE'] = 'switch';
        $element_rec['LINKED_PROPERTY'] = 'status';
    } elseif ($rec['TYPE']=='button') {
        $element_rec['TYPE'] = 'button';
    } elseif ($rec['TYPE']=='motion') {
        $element_rec['TYPE'] = 'warning';
        $element_rec['LINKED_PROPERTY'] = 'status';
        $element_rec['CSS_STYLE']='motion';
    } elseif ($rec['TYPE']=='sensor_temp') {
        $element_rec['CSS_STYLE']='temp';
        $linked_property_unit='°C';
    } elseif ($rec['TYPE']=='sensor_humidity') {
        $element_rec['CSS_STYLE']='humidity';
        $linked_property_unit='%';
    } else {
        $element_rec['TYPE']='object';
    }
    // Дополнительная логика для типов датчиков температуры и влажности
    if ($rec['TYPE']=='sensor_temp' || $rec['TYPE']=='sensor_humidity') {
        $element_rec['TYPE'] = 'informer';
        $element_rec['LINKED_PROPERTY'] = 'value';
        $wizard_data['STATE_HIGH']=1;
        $wizard_data['STATE_HIGH_VALUE']='%'.$element_rec['LINKED_OBJECT'].'.maxValue%';
        $wizard_data['STATE_LOW']=1;
        $wizard_data['STATE_LOW_VALUE']='%'.$element_rec['LINKED_OBJECT'].'.maxValue%';
        $wizard_data['UNIT']=$linked_property_unit;
    }
    */
        // Кодируем данные мастера обратно в JSON
        $element_rec['WIZARD_DATA'] = json_encode($wizard_data);

        // Если запись уже существует, обновляем ее
        if ($element_rec['ID']) {
            SQLUpdate('elements', $element_rec);
        } else {
            // Иначе, создаем новую запись
            $element_rec['ID'] = SQLInsert('elements', $element_rec);

            // Получаем связанный объект
            $linked_object = $rec['LINKED_OBJECT'];

            // Если тип элемента сцены - переключатель
            if ($element_rec['TYPE'] == 'switch') {

                // Создаем запись состояния для 'off'
                $state_rec = array();
                $state_rec['TITLE'] = 'off';
                $state_rec['HTML'] = $element_rec['TITLE'];
                $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                $state_rec['IS_DYNAMIC'] = 1;
                $state_rec['LINKED_OBJECT'] = $rec['LINKED_OBJECT'] . '';
                $state_rec['LINKED_PROPERTY'] = 'status';
                $state_rec['CONDITION'] = 4;
                $state_rec['CONDITION_VALUE'] = 1;
                $state_rec['ACTION_OBJECT'] = $rec['LINKED_OBJECT'] . '';
                $state_rec['ACTION_METHOD'] = 'turnOn';
                $state_rec['ID'] = SQLInsert('elm_states', $state_rec);

                // Создаем запись состояния для 'on'
                $state_rec = array();
                $state_rec['TITLE'] = 'on';
                $state_rec['HTML'] = $element_rec['TITLE'];
                $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                $state_rec['IS_DYNAMIC'] = 1;
                $state_rec['LINKED_OBJECT'] = $rec['LINKED_OBJECT'] . '';
                $state_rec['LINKED_PROPERTY'] = 'status';
                $state_rec['CONDITION'] = 1;
                $state_rec['CONDITION_VALUE'] = 1;
                $state_rec['ACTION_OBJECT'] = $rec['LINKED_OBJECT'] . '';
                $state_rec['ACTION_METHOD'] = 'turnOff';
                $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
            } elseif ($element_rec['TYPE'] == 'warning') {

                // Создаем запись состояния для 'default'
                $state_rec = array();
                $state_rec['TITLE'] = 'default';
                $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                $state_rec['HTML'] = $element_rec['TITLE'] . '<br/>%' . $rec['LINKED_OBJECT'] . '.updatedText%';
                $state_rec['LINKED_OBJECT'] = $rec['LINKED_OBJECT'] . '';
                $state_rec['LINKED_PROPERTY'] = 'status';
                $state_rec['IS_DYNAMIC'] = 1;
                $state_rec['CONDITION'] = 1;
                $state_rec['CONDITION_VALUE'] = 1;
                $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
            } elseif ($element_rec['TYPE'] == 'informer') {

                // Устанавливаем связанное свойство
                $linked_property = 'value';
                // Устанавливаем флаги для высокого и низкого состояний
                $state_high = 1;
                $state_high_value = '%' . $linked_object . '.maxValue%';

                // Если флаг высокого состояния установлен
                if ($state_high) {
                    // Создаем запись состояния для 'high'
                    $state_rec = array();
                    $state_rec['TITLE'] = 'high';
                    $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                    $state_rec['HTML'] = '%' . $linked_object . '.' . $linked_property . '%';
                    // Если установлена единица измерения, добавляем ее к HTML
                    if ($linked_property_unit) {
                        $state_rec['HTML'] .= ' ' . $linked_property_unit;
                    }
                    $state_rec['LINKED_OBJECT'] = $linked_object . '';
                    $state_rec['LINKED_PROPERTY'] = $linked_property . '';
                    $state_rec['IS_DYNAMIC'] = 1;
                    // Если установлено значение высокого состояния, добавляем его к записи
                    if ($state_high_value) {
                        $state_rec['CONDITION'] = 2;
                        $state_rec['CONDITION_VALUE'] = $state_high_value;
                    }
                    $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
                }

                // Устанавливаем флаги для низкого состояния
                $state_low = 1;
                $state_low_value = '%' . $linked_object . '.minValue%';

                // Если флаг низкого состояния установлен
                if ($state_low) {
                    // Создаем запись состояния для 'low'
                    $state_rec = array();
                    $state_rec['TITLE'] = 'low';
                    $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                    $state_rec['HTML'] = '%' . $linked_object . '.' . $linked_property . '%';
                    // Если установлена единица измерения, добавляем ее к HTML
                    if ($linked_property_unit) {
                        $state_rec['HTML'] .= ' ' . $linked_property_unit;
                    }
                    $state_rec['LINKED_OBJECT'] = $linked_object . '';
                    $state_rec['LINKED_PROPERTY'] = $linked_property . '';
                    $state_rec['IS_DYNAMIC'] = 1;
                    // Если установлено значение низкого состояния, добавляем его к записи
                    if ($state_low_value) {
                        $state_rec['CONDITION'] = 3;
                        $state_rec['CONDITION_VALUE'] = $state_low_value;
                    }
                    $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
                }

                // Создаем запись состояния для 'default'
                $state_rec = array();
                $state_rec['TITLE'] = 'default';
                $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                $state_rec['HTML'] = '%' . $linked_object . '.' . $linked_property . '%';
                // Если установлена единица измерения, добавляем ее к HTML
                if ($linked_property_unit) {
                    $state_rec['HTML'] .= ' ' . $linked_property_unit;
                }
                // Если установлены флаги высокого или низкого состояния
                if ($state_high || $state_low) {
                    $state_rec['IS_DYNAMIC'] = 1;
                    $state_rec['LINKED_OBJECT'] = $linked_object . '';
                    $state_rec['LINKED_PROPERTY'] = $linked_property . '';
                    // Если установлены оба флага, устанавливаем is_dynamic в 2 и добавляем условие
                    if ($state_high && $state_low) {
                        $state_rec['IS_DYNAMIC'] = 2;
                        $state_rec['CONDITION_ADVANCED'] = 'if (gg(\'' . $linked_object . '.' . $linked_property . '\')>=gg(\'' . $linked_object . '.minValue\') && gg(\'' . $linked_object . '.' . $linked_property . '\')<=gg(\'' . $linked_object . '.maxValue\')) {' . "\n " . '$display=1;' . "\n" . '} else {' . "\n " . '$display=0;' . "\n" . '}';
                    } elseif ($state_high) {
                        $state_rec['IS_DYNAMIC'] = 1;
                        $state_rec['CONDITION'] = 3;
                        $state_rec['CONDITION_VALUE'] = $state_high_value;
                    } elseif ($state_low) {
                        $state_rec['IS_DYNAMIC'] = 1;
                        $state_rec['CONDITION'] = 2;
                        $state_rec['CONDITION_VALUE'] = $state_low_value;
                    }
                }
                $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
            } elseif ($element_rec['TYPE'] == 'button') {
                // Устанавливаем метод связанного объекта
                $linked_method = 'pressed';
                // Создаем запись состояния для 'default'
                $state_rec = array();
                $state_rec['TITLE'] = 'default';
                $state_rec['ELEMENT_ID'] = $element_rec['ID'];
                $state_rec['HTML'] = $element_rec['TITLE'];
                // Если установлены связанный объект и метод, добавляем их к записи
                if ($linked_object && $linked_method) {
                    $state_rec['ACTION_OBJECT'] = $linked_object;
                    $state_rec['ACTION_METHOD'] = $linked_method;
                }
                $state_rec['ID'] = SQLInsert('elm_states', $state_rec);
            }
        }
    }
    /**
     * checkLinkedDevicesAction
     *
     * Функция для проверки действий связанных устройств.
     * Она принимает название связанного объекта и значение (необязательно).
     * Функция выполняет поиск устройств, связанных с указанным объектом, и включает файл с действиями связанных устройств.
     *
     * @method void checkLinkedDevicesAction($object_name, $value = '')
     * @param string $object_name Название связанного объекта
     * @param string $value Значение (необязательно)
     * @return void
     * @see module
     * @since 0.1
     */
    function checkLinkedDevicesAction($object_title, $value = 0)
    {
        // Начинаем измерение времени выполнения функции
        startMeasure('checkLinkedDevicesAction');
        // Получаем запись об устройстве, связанном с указанным объектом
        $device1 = SQLSelectOne("SELECT * FROM devices WHERE LINKED_OBJECT LIKE '" . $object_title . "'");
        // Если устройство не найдено, завершаем измерение времени и возвращаем 0
        if (!$device1['ID']) {
            endMeasure('checkLinkedDevicesAction');
            return 0;
        }
        // Включаем файл с действиями связанных устройств
        include(dirname(__FILE__) . '/devices_links_actions.inc.php');
        // Завершаем измерение времени выполнения функции
        endMeasure('checkLinkedDevicesAction');
        // Возвращаем 1, указывая на успешное выполнение функции
        return 1;
    }
    /**
     * Install
     *
     * Маршрутина установки модуля.
     *
     * @method void Install()
     * @return void
     * @see module
     * @since 0.1
     */
    function install($data = '')
    {
        // Выполняем установку родительского класса
        parent::install();

        // Включаем файлы языка модуля для текущего языка сайта и по умолчанию
        if (file_exists(ROOT . 'languages/' . $this->name . '_' . SETTINGS_SITE_LANGUAGE . '.php')) {
            include_once(ROOT . 'languages/' . $this->name . '_' . SETTINGS_SITE_LANGUAGE . '.php');
        }
        if (file_exists(ROOT . 'languages/' . $this->name . '_default' . '.php')) {
            include_once(ROOT . 'languages/' . $this->name . '_default' . '.php');
        }
        // Обновляем заголовок модуля в таблице проектных модулей
        SQLExec("UPDATE project_modules SET TITLE='" . LANG_DEVICES_MODULE_TITLE . "' WHERE NAME='" . $this->name . "'");

        // Устанавливаем словарь устройств
        $this->setDictionary();
        // Рендерим структуру модуля
        $this->renderStructure();
        // Синхронизируем с Homebridge
        $this->homebridgeSync();
    }
    /**
     * Uninstall
     *
     * Маршрутина удаления модуля.
     *
     * @method void Uninstall()
     * @return void
     * @see module
     * @since 0.1
     */
    function uninstall()
    {
        // Удаляем таблицу устройств
        SQLDropTable('devices');
        // Выполняем удаление родительского класса
        parent::uninstall();
    }
    /**
     * dbInstall
     *
     * Маршрутина установки базы данных.
     *
     * @method void dbInstall()
     * @return void
     * @see module
     * @since 0.1
     */
    function dbInstall($data = '')
    {
        /*
* Создание таблиц для устройств и связей между ними.
*/
        $data = <<<EOD
 devices: ID int(10) unsigned NOT NULL auto_increment
 devices: TITLE varchar(100) NOT NULL DEFAULT ''
 devices: ALT_TITLES varchar(255) NOT NULL DEFAULT ''
 devices: TYPE varchar(100) NOT NULL DEFAULT ''
 devices: LINKED_OBJECT varchar(100) NOT NULL DEFAULT ''
 devices: LOCATION_ID int(10) unsigned NOT NULL DEFAULT 0  
 devices: FAVORITE int(3) unsigned NOT NULL DEFAULT 0 
 devices: SYSTEM_DEVICE int(3) unsigned NOT NULL DEFAULT 0
 devices: CLICKED datetime DEFAULT NULL
 devices: ARCHIVED int(3) unsigned NOT NULL DEFAULT 0

 devices: SYSTEM varchar(255) NOT NULL DEFAULT ''
 devices: SUBTYPE varchar(100) NOT NULL DEFAULT ''
 devices: ENDPOINT_MODULE varchar(255) NOT NULL DEFAULT ''
 devices: ENDPOINT_NAME varchar(255) NOT NULL DEFAULT ''
 devices: ENDPOINT_TITLE varchar(255) NOT NULL DEFAULT ''
 devices: ROLES varchar(100) NOT NULL DEFAULT ''

 devices_linked: ID int(10) unsigned NOT NULL auto_increment
 devices_linked: IS_ACTIVE int(3) unsigned NOT NULL DEFAULT 1
 devices_linked: DEVICE1_ID int(10) unsigned NOT NULL DEFAULT 0
 devices_linked: DEVICE2_ID int(10) unsigned NOT NULL DEFAULT 0
 devices_linked: LINK_TYPE varchar(100) NOT NULL DEFAULT ''
 devices_linked: LINK_SETTINGS text
 devices_linked: COMMENT varchar(255) NOT NULL DEFAULT ''
  
 devices_groups: ID int(10) unsigned NOT NULL auto_increment
 devices_groups: SYS_NAME varchar(100) NOT NULL DEFAULT ''
 devices_groups: TITLE varchar(255) NOT NULL DEFAULT ''
 devices_groups: APPLY_TYPES text

 devices_scheduler_points: ID int(10) unsigned NOT NULL auto_increment
 devices_scheduler_points: LINKED_METHOD varchar(255) NOT NULL DEFAULT ''
 devices_scheduler_points: VALUE varchar(255) NOT NULL DEFAULT ''
 devices_scheduler_points: SET_TIME varchar(50) NOT NULL DEFAULT ''
 devices_scheduler_points: SET_DAYS varchar(50) NOT NULL DEFAULT '' 
 devices_scheduler_points: DEVICE_ID int(10) NOT NULL DEFAULT '0'
 devices_scheduler_points: ACTIVE int(3) NOT NULL DEFAULT '1'
 devices_scheduler_points: LATEST_RUN datetime

EOD;
        // Выполняем установку базы данных
        parent::dbInstall($data);
    }
    // --------------------------------------------------------------------
}
/*
* Зачем эта строка с кодировкой? не понятно.
* TW9kdWxlIGNyZWF0ZWQgSnVsIDE5LCAyMDE2IHVzaW5nIFNlcmdlIEouIHdpemFyZCAoQWN0aXZlVW5pdCBJbmMgd3d3LmFjdGl2ZXVuaXQuY29tKQ==
*
*/