У меня есть самописный движок автоматизации для IoT на PHP + MySQL + MQTT. Он умеет запускать действия по расписанию — например, включать фильтр в аквариуме в 8:00 и выключать в 23:00.
Раньше логика была простой: каждые 10 секунд скрипт проверял, совпадает ли текущее время (H:i) со временем в правиле. Потом я добавил поддержку секунд — стал сравнивать H:i:s — и стал создавать правила вроде:
08:00:00 → включить реле23:00:00 → выключить релеВроде всё логично. Но однажды ночью фильтр не выключился. Почему?
Мой цикл работает с sleep(5) (а потом я уменьшил до sleep(1)). Но даже при проверке раз в секунду:
А так как условие было строгое — if (текущее_время === правило.время) — правило не срабатывало. Никогда.
Я переделал логику. Теперь вместо точного совпадения используется условие:
«Если текущее время больше или равно заданному, и правило ещё не выполнялось сегодня — запустить его один раз».
Для этого я использую уже существующее поле в таблице automations — last_triggered (тип TIMESTAMP).
Алгоритм:
H:i:s и дату Y-m-d.если (текущее_время >= правило.время)если дата(last_triggered) != сегодняlast_triggered = NOW()Теперь даже если скрипт проверит правило в 23:00:03 — условие '23:00:03' >= '23:00:00' истинно, и фильтр выключится. А повторного запуска не будет, потому что last_triggered уже обновлён на сегодняшнюю дату.
sleep(5))Если вы пишете свой IoT-планировщик на PHP (или любом другом языке) — никогда не полагайтесь на точное совпадение по секунде. Всегда используйте «окно срабатывания» и механизм защиты от повторов.
Это особенно критично для задач, которые должны выполниться один раз в сутки (выключение насосов, полив, обогрев и т.д.).
last_triggered (без изменения БД)В таблице automations уже есть колонка:
last_triggered TIMESTAMP NULL COMMENT 'Когда последний раз сработало'
Её достаточно для отслеживания выполнения за день:
$lastDate = $rule['last_triggered']
? date('Y-m-d', strtotime($rule['last_triggered']))
: null;
$today = date('Y-m-d');
if ($currentTime >= $ruleTime && $lastDate !== $today) {
handleAction($rule);
// last_triggered обновится автоматически в handleAction()
}
Плюсы: не требует ALTER TABLE, использует уже существующую логику обновления времени срабатывания.
Минусы: если last_triggered используется где-то ещё (например, для статистики), возможна коллизия по смыслу — но в вашем случае это маловероятно.
last_triggered_dateМожно создать специальную колонку только для даты:
ALTER TABLE automations ADD COLUMN last_triggered_date DATE NULL
COMMENT 'Дата последнего срабатывания (для daily-правил)';
Тогда проверка становится проще и семантически чище:
$today = date('Y-m-d');
if ($currentTime >= $ruleTime &&
($rule['last_triggered_date'] !== $today || is_null($rule['last_triggered_date']))) {
handleAction($rule);
$pdo->prepare("UPDATE automations SET last_triggered_date = ? WHERE id = ?")
->execute([$today, $rule['id']]);
}
Плюсы: чёткое разделение ответственности — last_triggered для точного времени, last_triggered_date для защиты от повторов.
Минусы: нужно изменять структуру БД, что может быть нежелательно в продакшене без миграций.
Позже я планирую добавить поддержку временных зон и более сложных расписаний (например, «каждые 30 минут с 8 до 22»), но базовая логика останется той же: «настало или прошло» + «ещё не делали сегодня».
Надёжность важнее эстетики точного времени.
Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.
Комментарии
Пока нет комментариев. Будьте первым!