Devices class php

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

Devices_class_php Возможно где-то ошибки с закрывающими или открывающими {} в связи с добавлением коментов, мог гдето херакнуть, прошу заранее прощения, сам ищу косяки и поправлю если найду. Но вроде бы не должно быть.

<?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);
    }

    // Если указаны параметры для связанной таблицы и идентификатор записи в таблице
    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: 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==
*
*/