Категории

Как я добавил JavaScript-условия в свой самописный IoT-ассистент

2025-09-16 12:21:12 | IOT умный дом
JavaScript-условия в IoT-ассистент
🔧 Самописный домашний ассистент · 📚 Продолжение серии

Привет, хомасистенты! 👋 Если вы читали мою первую статью (там старый код) о том, как я построил свой собственный IoT-ассистент на Raspberry Pi без Home Assistant, то знаете — мы уже умеем слушать MQTT, записывать показания датчиков и запускать действия по простым правилам вроде:

Если температура > 25 → включить вентилятор

Но… что если нужно что-то сложнее? Например:

Стандартные операторы >, == — это как молоток. А нам нужен фрезерный станок.


💡 Идея: "Скрипт-условие" вместо простого сравнения

Я решил: «Почему бы не позволить пользователю писать условие как маленькое JavaScript-выражение?»

Теперь в интерфейсе управления автоматизациями появился новый режим:

Простое условие — старый вариант: temperature > 25
Скрипт-условие — новый вариант: sensor.temperature > 24 && sensor.humidity < 60 && sensor.window === 'closed'

Пользователь выбирает тип — и либо вводит стандартное сравнение, либо пишет выражение, которое вычисляется на сервере.

🛠️ Как это выглядит в форме

Вот фрагмент формы в PHP-админке:

<select name="condition_type" onchange="toggleConditionType(this.value)">
  <option value="simple">Простое (A > B)</option>
  <option value="script">Скрипт (JS-выражение)</option>
</select>

<div id="scriptCondition" style="display:none;">
  <textarea name="condition_script" rows="4" placeholder="
// Примеры:
sensor.temperature > 24 && sensor.humidity < 60
Object.values(sensor).filter(v => v > 20).length >= 3
sensor.motion_1 || sensor.motion_2
"></textarea>
</div>

Интерфейс переключается динамически — всё просто, без JS-фреймворков.


🔒 Безопасность: Никакого eval()!

Первое, что приходит в голову — eval(). Но это смертельно опасно в веб-интерфейсе. Даже один неверный символ — и кто-то может удалить всю систему.

Мой подход: не исполнять код, а безопасно вычислять выражение.

⚠️ ВАЖНО: Это не eval() в чистом виде, а файл-интерпретатор, который автоматически удаляется. Это гораздо безопаснее, чем eval().

Вот как я реализовал это в PHP (внутри /admin/logic.php):

// Безопасный интерпретатор условий
function evaluateScriptCondition($script, $sensorData) {
    // Заменяем sensor.xxx на массив
    $script = preg_replace('/\bsensor\.([a-zA-Z0-9_]+)\b/', '$sensorData[\'$1\']', $script);
    $script = str_replace('data', '$data', $script);

    // Проверка на допустимые символы (защита от инъекций)
    if (!preg_match('/^[a-zA-Z0-9_\[\]\.\(\)\+\-\*\/\!\&\|\s\=\>\<\,\{\}\'\"]+$/', $script)) {
        throw new Exception("Недопустимые символы");
    }

    // Запрещённые слова
    $forbidden = ['eval', 'exec', 'system', 'shell_exec', 'include', 'require'];
    foreach ($forbidden as $word) {
        if (stripos($script, $word) !== false) {
            throw new Exception("Запрещённая функция: " . $word);
        }
    }

    // Создаём временный файл для безопасного выполнения
    $tmpFile = sys_get_temp_dir() . '/eval_' . uniqid() . '.php';
    file_put_contents($tmpFile, "getMessage());
    }
    unlink($tmpFile); // Автоматическое удаление!

    return (bool)$result;
}

Это работает так:

  1. Пользователь вводит: sensor.temperature > 24 && sensor.humidity < 60
  2. Скрипт заменяет sensor.temperature на $sensorData['temperature']
  3. Создаётся временный PHP-файл: return ($sensorData['temperature'] > 24 && $sensorData['humidity'] < 60);
  4. Файл подключается через include — это безопасно, потому что он генерируется только из проверенных символов
  5. Файл сразу удаляется

Никаких внешних вызовов. Ни одного eval(). Только математика и логика.


🚀 Примеры условий, которые теперь возможны

Вот что можно написать прямо в поле скрипта:

// Условие 1: Все три датчика должны быть активны
sensor.movement_kitchen || sensor.movement_hall || sensor.movement_bedroom

// Условие 2: Средняя температура за 3 датчика > 22
(Object.values(sensor).filter(v => typeof v === 'number').reduce((a,b) => a + b, 0) / Object.values(sensor).filter(v => typeof v === 'number').length) > 22

// Условие 3: Температура > 25 ИЛИ влажность > 70 И окно открыто
sensor.temperature > 25 || (sensor.humidity > 70 && sensor.window === 'open')

// Условие 4: За последние 5 минут было больше 3 событий движения (если есть история)
sensor.motion_count_5m > 3

Да, это почти полноценный язык. И всё это — в одной строке, без Python, без Node.js, без Docker.


🔧 Что дальше?

Сейчас система работает, но я уже думаю о расширениях:

Всё это — в рамках одного файла PHP. Никаких сторонних зависимостей. Только мой Raspberry Pi, MQTT и немного креатива.


✅ Почему это важно?

Многие говорят: «Зачем самому делать, когда есть Home Assistant?»

А я отвечаю: потому что я хочу понимать, как всё работает внутри.

Когда я добавляю новое условие — я не кликаю в UI, я пишу выражение. Когда оно работает — я знаю, почему. Когда не работает — я могу отладить его за 2 минуты, потому что я создал эту систему с нуля.

Это не просто «умный дом». Это мой дом, мой код, моя логика.

И да — это круто.

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Важно: Блог-эксперимент

Внимание: Cтатьи здесь сгенерированны через нейросеть, не правил ошибки, да и не до этого пока. Блог только запустил. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
НО!
Каждый кейс я делал минимум один раз. Сервера стоят, клиенты довольны, дата-центры не горят.

Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA
Кто я | HSH | Контакты и регион