Всем привет.

Есть малая механизация, когда вы мотокультиватором типа «Крот» сажаете картошку на 20 сотках, а есть малая автоматизация, когда вам лень ставить жирный homeassiant и/или приложение от вендора с завязкой на облака и хочется пожить в уютном локалхосте. Сегодня пойдет речь про вторую. И да, важный момент — вы не планировали умный дом и не тянули провода заранее.

Достаточно давно была куплена розетка «Умная розетка Wi‑Fi SmartHome 2300W 10А IP20 c заземлением Gauss» в озоне, к сожалению сейчас уже не продается и непонятно продается ли она у вендора. Важно в ней то, что протокол общения там Philips WiZ, он расковырян, много скриптов на гитхабе, оттуда и подчерпнута важная информация.

Сегодня мы делаем включение ее и приборов, подключенных в ней, по закату солнца в конкретной вашей локации. И выключение в 23:00. Для доосвещения рассады дома, что весной, что осенью.
Пререквезиты — любой линукс хост с bash, curl, jq, netcat и доступом в интернет.
Что оставляем за кадром — подключение в ваш локальный LAN, это инструкция из коробки. После подключения доступ в интернет розетке можно заблокировать, она не потеряет адрес и даже валяющаяся на полке в течении полугода сохранит подключение wifi.

Итак, что делает скрипт:

  1. опрашивает по вашему фактическому местоположению когда у вас будет закат, на основании показателей api.sunrise‑sunset.org, это бесплатно как минимум для домашнего использования. для бизнеса не изучал, извините.

  2. до заката шлет форсированно выключение розетки, через nc, на случай если случайно нажали кнопку. износа розетки в этом случае не видно, по крайней мере за 3 года.

  3. после заката включает питание на ней, так же через nc

  4. в 23:00 выключает, тк растениям тоже нужно спать

  5. опционально, если у вас 2-3-4 и более розеток работает по массиву.

Скрытый текст
#!/usr/bin/env bash
#
# light.sh
#
# Requires:
#   - curl
#   - jq
#   - GNU date (coreutils)
#   - netcat / nc
#
# Run:
#   ./light.sh
#
# Print only sunset Unix timestamp:
#   ./light.sh --print-sunset
#

set -euo pipefail

# --------------------------------------------
# Config
# --------------------------------------------

LAT="37.619024"
LNG="55.751999"

# Use the timezone for both API and 23:00 cutoff
TIMEZONE="Europe/Moscow"

# Configurable IP addresses.
# Add as many as you need.
IP_ADDR=(
    "192.168.1.185"
    # "192.168.1.186"
    # "192.168.1.187"
)

LIGHT_PORT=38899
LIGHT_ID=1

# --------------------------------------------
# Dependency check
# --------------------------------------------

for cmd in curl jq nc date; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
        printf 'Missing dependency: %s\n' "$cmd" >&2
        exit 1
    fi
done

# --------------------------------------------
# Send UDP command to all configured IPs
# --------------------------------------------

send_state() {
    local state="$1"   # true or false

    local json_state
    if [[ "$state" == "true" ]]; then
        json_state="true"
    else
        json_state="false"
    fi

    for ip in "${IP_ADDR[@]}"; do
        printf '{"id":%s,"method":"setState","params":{"state":%s}}\n' \
            "$LIGHT_ID" "$json_state" |
            nc -u -w 1 "$ip" "$LIGHT_PORT" || true
    done
}

# --------------------------------------------
# Get sunset as Unix epoch seconds
# --------------------------------------------

get_sunset_epoch() {
    # Explicit local date for the API request.
    # This avoids issues if the API interprets "today" differently.
    local date_str
    date_str=$(TZ="$TIMEZONE" date +%Y-%m-%d)

    local url
    url="https://api.sunrise-sunset.org/json?lat=${LAT}&lng=${LNG}&date=${date_str}&tzid=${TIMEZONE}&formatted=0"

    local response
    response=$(curl -sL --max-time 10 "$url") || return 1

    if ! jq -e '.status == "OK"' <<< "$response" >/dev/null; then
        return 1
    fi

    local sunset_iso
    sunset_iso=$(jq -r '.results.sunset // ""' <<< "$response")

    if [[ -z "$sunset_iso" ]]; then
        return 1
    fi

    # If the API returns only a time like "18:00:00+03:00",
    # add the local date before conversion.
    if [[ ! "$sunset_iso" =~ ^[0-9]{4} ]]; then
        sunset_iso="${date_str} ${sunset_iso}"
    fi

    # GNU date supports --date / -d
    date -d "$sunset_iso" +%s
}

# --------------------------------------------
# Optional: print only sunset Unix timestamp
# --------------------------------------------

if [[ "${1:-}" == "--print-sunset" ]]; then
    printf '%d\n' "$(get_sunset_epoch)"
    exit 0
fi

# --------------------------------------------
# Main logic
# --------------------------------------------

current_epoch=$(date +%s)

local_date=$(TZ="$TIMEZONE" date +%Y-%m-%d)
cutoff_epoch=$(TZ="$TIMEZONE" date -d "${local_date} 23:00:00" +%s)

if ! sunset_epoch=$(get_sunset_epoch); then
    printf 'ERROR: failed to get sunset from API\n' >&2
    exit 1
fi

printf 'current_epoch=%d\n' "$current_epoch"
printf 'sunset_epoch=%d\n' "$sunset_epoch"
printf 'cutoff_23_00=%d\n' "$cutoff_epoch"

# Turn ON only if:
#   current time is AFTER sunset
#   AND current time is BEFORE 23:00 local time
#
# Otherwise, turn OFF.
if (( current_epoch > sunset_epoch && current_epoch < cutoff_epoch )); then
    send_state true
    printf 'light_state=on reason=after_sunset_and_before_23:00\n'
else
    send_state false
    printf 'light_state=off reason=before_sunset_or_after_23:00\n'
fi

Для использования требуется настроить вашу локацию (можно взять из яндекса), адреса розеток, таймзону, остальное по вкусу.

Далее регистрируем сервис в systemd, господам в openwrt и прочей встройке сразу рекомендуется идти в крон:

cat /usr/lib/systemd/system/light.service
[Unit]
Description=Gauss managing
After=network.target

[Service]
ExecStart=/opt/gauss/on_off2.sh
User=max
Restart=never

[Install]
WantedBy=multi-user.target

Собственно /opt/gauss/on_off2.sh место, куда мы положили шелльник.

И регистрируем таймер /etc/systemd/system/light.timer

[Unit]
Description=Run light.sh every 5 minute

[Timer]
OnBootSec=1sec
OnUnitActiveSec=5min
Unit=light.service

[Install]
WantedBy=multi-user.target

Который начнет запускать ваш таймер каждые 5 минут (выбрать по вкусу).
Далее стандартно systemctl daemon‑reload && systemctl enable light.service light.timer && systemctl start light.service light.timer. Скрипт немного флудит в journalctl, дебаг можно убрать.

К сожалению при написании пострадала одна локальная нейросеть, изначальный скрипт, писавшийся руками был ужасен и к публикации не был одобрен.

Всем хорошего урожая

Скрытый текст

Комментарии (7)


  1. MarkCraft_Dev
    03.11.2026 17:04

    Интересный подход с nc напрямую к розетке, без облаков. А насколько стабильно держится TCP-соединение? У меня был опыт с Wi-Fi устройствами (правда, не Gauss), так они иногда отваливались от сети, если роутер перезагружался. Приходилось ли вам добавлять какие-то проверки ping перед отправкой команды, или она и так годами работает без сбоев?


    1. max9 Автор
      03.11.2026 17:04

      там только udp, ничего не надо ни пингов ничего. улетело и улетело. имекнно поэтому в цикле скрипта вечная отправка


      1. MarkCraft_Dev
        03.11.2026 17:04

        А, так вот почему цикл вечный! Логично, UDP же не гарантирует доставку, поэтому проще «кричать» в сеть, пока не дойдет. Спасибо за разъяснение, буду знать


  1. BigBrother
    03.11.2026 17:04

    Зачем вообще здесь обращаться к какому-то забугорному серверу, чтобы только узнать время захода солнца? Это уже не совсем локалхост. В наше неспокойное время блокировок всего и всех.
    Проще забить в локальный файлик. Тем более точное время здесь не слишком актуально. Думаю, достаточно будет даже подекадно внести: растениям это не повредит.


    1. max9 Автор
      03.11.2026 17:04

      есть опция, да, либо забить в файлик, либо лучше по фактическому освещению включать свет, но там надо думать рокет-сайнс по вариантам

      • сутра темно - включаем на весь день

      • пришла туча летом/зимой не алармить и не зажигать свет на 20 минут, лампочка быстро кончится от переходных процессов

      • отличать аларм п2 от заката

      у меня даже валялась esp с датчиком освещения и снятыми показателями освещенности, чтобы написать, но не дошли руки


      1. Lizdroz
        03.11.2026 17:04

        Рокет-сайнс начинается там, где хочется учесть все возможные краевые сценарии)

        А почему бы просто не купить таймер-розетку за триста рублей в строительном магазине?


  1. Lizdroz
    03.11.2026 17:04

    Решение интересное, но скрипт выглядит немного перегруженным для такой простой задачи. Вместо запросов к внешним API можно было просто поставить локальный cron с хардкодными таймингами по сезонам. Для рассады минутная точность заката не нужна