Реверс-инжиниринг Yeelight Screen Light Bar Pro: управляем лампой напрямую по LAN
У меня на столе стоит монитор-лампа Yeelight LED Screen Light Bar Pro. К ней в комплекте идёт беспроводной пульт-крутилка — удобно, но скучно. И вот сидишь ты вечером, смотришь на эту лампу и думаешь: она же подключена к Wi-Fi. А раз так — где-то там, внутри, у неё точно есть способ управления по сети. Не может не быть.
Спойлер: способ есть, и довольно приятный. Yeelight давным-давно открыли свой локальный протокол управления — никакого облака, никаких токенов, просто JSON-команды по TCP прямо в лампу. В этой статье я с нуля разберусь, как до неё достучаться: найду лампу в сети, включу нужный режим, наткнусь на странную загадку с двумя IP-адресами у одного устройства и в итоге научусь рулить обеими её зонами — основным светом и той самой RGB-подсветкой, которую, как считается, можно крутить только из приложения.
Всё, что описано ниже, работает с любой Wi-Fi лампой Yeelight, а не только с моей Screen Bar Pro. Протокол общий. Просто у Pro есть приятная особенность — две независимые зоны света, и с ними чуть интереснее.
Как это вообще устроено
Локальный протокол Yeelight держится на двух китах. Первый — обнаружение через механизм, очень похожий на SSDP: ты кидаешь в мультикаст-группу UDP-пакет «эй, кто тут Yeelight?», и все лампы в той же сети отвечают, кто они и по какому адресу с ними говорить. Второй — само управление: открываешь TCP-соединение на порт 55443 и шлёшь туда JSON-команды, на каждую получаешь JSON-ответ.
Важный нюанс, без которого ничего не заведётся: по умолчанию этот порт закрыт. Нужно зайти в приложение Yeelight, открыть настройки конкретной лампы и включить опцию «Управление по локальной сети» (LAN Control, в старых версиях называлась «Режим разработчика» / Developer Mode). Пока ты этого не сделал — лампа в сети висит, пингуется, но на 55443 не отвечает.
Протокол не шифрованный. Все команды летят по сети открытым текстом, и безопасность тут держится исключительно на твоём роутере. Для домашней сети это норма, но выставлять порт 55443 наружу в интернет — очень плохая идея.
Находим лампу в сети
Самый честный способ — отправить SSDP-запрос и посмотреть, кто откликнется. Лампы слушают мультикаст-группу 239.255.255.250 на порту 1982.
Надёжнее сделать это на Python — он корректно биндит сокет и ловит юникаст-ответы. Никаких зависимостей, только стандартная библиотека. Скрипт сразу вытаскивает из ответа самое полезное — адрес, модель и поле support (о нём чуть ниже):
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
"""Обнаружение Yeelight-ламп в локальной сети через SSDP."""
import socket
MCAST = ("239.255.255.250", 1982)
MSG = (
"M-SEARCH * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1982\r\n"
'MAN: "ssdp:discover"\r\n'
"ST: wifi_bulb\r\n\r\n"
)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
s.settimeout(3)
s.sendto(MSG.encode(), MCAST)
seen = set()
print("Ищу лампы 3 секунды...\n")
while True:
try:
data, addr = s.recvfrom(2048)
except socket.timeout:
break
if addr[0] in seen:
continue
seen.add(addr[0])
fields = {}
for line in data.decode(errors="replace").split("\r\n"):
if ":" in line:
k, _, v = line.partition(":")
fields[k.strip().lower()] = v.strip()
print(f"=== Лампа на {addr[0]} ===")
print(f" Location : {fields.get('location', '?')}")
print(f" Model : {fields.get('model', '?')}")
print(f" Power : {fields.get('power', '?')}")
print(f" Support : {fields.get('support', '?')}")
print()
if not seen:
print("Никто не ответил. Проверь: лампа включена, LAN Control включён,")
print("и ты в той же Wi-Fi-сети (не на гостевой/изолированной).")
else:
print(f"Найдено ламп: {len(seen)}")
Запускаем и получаем реальный ответ от моей лампы:
1
python3 yee-discover.py
$ Ищу лампы 3 секунды...$ $ === Лампа на 192.168.88.104 ===$ Location : yeelight://192.168.88.104:55443$ Model : lamp15$ Power : on$ Support : get_prop set_default set_power toggle set_ct_abx set_bright$ start_cf stop_cf set_scene cron_add cron_get cron_del set_adjust$ adjust_bright adjust_ct set_name bg_set_rgb bg_set_hsv bg_set_ct_abx$ bg_start_cf bg_stop_cf bg_set_scene bg_set_default bg_set_power$ bg_set_bright bg_set_adjust bg_adjust_bright bg_adjust_color$ bg_adjust_ct bg_toggle dev_toggle udp_sess_new udp_sess_keep_alive$ udp_chroma_sess_new set_segment_rgb$ $ Найдено ламп: 1
Вот тут сразу видно всё самое важное. Адрес лампы — в поле Location. Модель — lamp15 (полное название в экосистеме Xiaomi — yeelink.light.lamp15, это и есть наша Screen Bar Pro).
А самое ценное — поле support. Это буквально исчерпывающий список всех команд, которые лампа понимает. Не надо ничего угадывать — всё перед глазами. Разберём, что тут есть:
- Обычные команды основного света:
set_power, set_bright, set_ct_abx, toggle, set_name - Целый набор с приставкой
bg_ — это управление фоновой подсветкой: bg_set_rgb, bg_set_hsv, bg_set_power, bg_set_bright и так далее. Они здесь есть — значит, подсветка управляема. Запомним это, вернёмся ниже start_cf / bg_start_cf — color flow, те самые плавные переливы цветаset_scene — сложные сцены одной командойcron_add / cron_del — таймеры внутри самой лампы (например, выключиться через N минут)set_segment_rgb — посегментная адресация подсветки — интересный задел на будущееudp_sess_new, udp_chroma_sess_new — механизм быстрых обновлений (music mode и chroma-синхронизация), обходящий лимит на частоту команд
Первый контакт
Адрес есть, порт известен. Проверяем, отвечает ли лампа на команды. Команда get_prop позволяет запросить любые её свойства — передаём список интересующих нас полей. И тут уже можно обойтись без Python — это обычный TCP, и nc с ним прекрасно справляется:
12
echo -e '{"id":1,"method":"get_prop","params":["power","bright","ct","rgb","color_mode","name","model"]}\r\n' \
| nc -w 2 192.168.88.104 55443
И лампа отвечает:
$ {"id":1,"result":["on","46","6500","","2","","lamp15"]}Работает! Разберём ответ по порядку запрошенных полей:
power: on — лампа включенаbright: 46 — яркость 46%ct: 6500 — цветовая температура 6500K, холодный белыйrgb: "" — пусто, потому что сейчас лампа не в RGB-режимеcolor_mode: 2 — режим работы: 2 означает «цветовая температура» (1 был бы RGB, 3 — HSV)model: lamp15 — наша подопытная
Ответ приходит как плоский массив в том же порядке, в каком мы перечислили поля в запросе. Без имён, просто значения по позициям — имей в виду, когда будешь парсить.
Загадка двух IP
А теперь то, на чём я залип. Когда я полез смотреть устройства в сети, лампа обнаружилась на ДВУХ разных адресах:
$ 192.168.88.104$ 192.168.88.16
На самом деле для устройств с двумя зонами света это нормальное поведение. Основной свет и фоновая подсветка — это, по сути, две логические лампы внутри одного корпуса, и в некоторых прошивках они анонсируются по SSDP как два отдельных устройства, каждое со своим адресом.
Звучит стройно. Я решил проверить — постучался во второй адрес тем же get_prop. И получил тишину. Соединение просто висло до таймаута. Тогда я попробовал пингануть:
1
ping -c 2 192.168.88.16
$ PING 192.168.88.16 (192.168.88.16): 56 data bytes$ ping: sendto: No route to host$ ping: sendto: Host is down$ Request timeout for icmp_seq 0
No route to host — по этому адресу в сети нет вообще ничего. ARP его не резолвит, хост мёртв. То есть красивая теория про «две зоны = два IP» в моём случае не подтвердилась: вторая зона там не живёт.
Возможно это IP адрес джойстика для управления лампой, до конца не ясно.
Возможно — призрак из прошлого. Лампа когда-то получала адрес .16 по DHCP, потом аренда сменилась, устройство переехало на .104, а старая запись осела в кэше.
Управляем основным светом
Начнём с простого — с белого света. Базовые команды очевидны из того самого поля support. Включить и выключить:
12345
# включить плавно за 500 мс
echo -e '{"id":1,"method":"set_power","params":["on","smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
# выключить
echo -e '{"id":1,"method":"set_power","params":["off","smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
Третий параметр в большинстве команд — длительность плавного перехода в миллисекундах, а "smooth" / "sudden" задаёт, плавно менять или резко. Яркость задаётся в процентах от 1 до 100:
12
# яркость 80%
echo -e '{"id":1,"method":"set_bright","params":[80,"smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
А цветовую температуру — командой set_ct_abx, в кельвинах. Диапазон у этой лампы стандартный, от 2700 (тёплый) до 6500 (холодный):
12
# нейтрально-тёплый белый
echo -e '{"id":1,"method":"set_ct_abx","params":[4000,"smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
На каждую успешную команду лампа отвечает лаконичным {"id":1,"result":["ok"]}. Если что-то не так — придёт объект error с кодом и сообщением.
А теперь — подсветка
Вот мы и добрались до интересного. У Screen Bar Pro кроме основного белого света есть вторая зона — RGB-подсветка сзади (ambient / background light). И вот тут есть нюанс, на который жалуются все владельцы: в сторонних интеграциях (HomeKit через Homebridge и прочие) эта подсветка обычно не управляется — крутить её можно только из родного приложения. Я специально перерыл обсуждения — у людей в HomeKit видна вторая зона, но ползунок на неё не влияет.
Так вот, напрямую по протоколу — влияет. Секрет в той самой приставке bg_ (от background), которую мы видели в поле support. Это полный набор команд-близнецов основного света, только для подсветки. Включаем подсветку:
1
echo -e '{"id":1,"method":"bg_set_power","params":["on","smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
Цвет задаётся командой bg_set_rgb, и тут есть подвох: цвет передаётся не тройкой, а одним целым числом по формуле R*65536 + G*256 + B. То есть чистый красный (255,0,0) это 16711680, зелёный — 65280, синий — 255:
12
# красная подсветка
echo -e '{"id":1,"method":"bg_set_rgb","params":[16711680,"smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
Считать в уме это число — удовольствие сомнительное, поэтому на практике удобнее задавать цвет через оттенок и насыщенность командой bg_set_hsv. Первый параметр — hue (угол на цветовом круге, 0–359), второй — saturation (0–100):
12
# голубая подсветка через hue/saturation
echo -e '{"id":1,"method":"bg_set_hsv","params":[200,100,"smooth",500]}\r\n' | nc -w 2 192.168.88.104 55443
Яркость подсветки — отдельная от основного света, командой bg_set_bright. Получается полноценное независимое управление двумя зонами с одного адреса, без всякого приложения. Ровно то, чего не хватало в сторонних интеграциях.
Собираем всё в один скрипт
Каждый раз печатать эти полотна из echo и nc невозможно. Завернём всё в маленький bash-скрипт-обёртку. IP можно переопределить через переменную окружения, по умолчанию — наш .104:
12345678910111213141516171819202122232425
#!/usr/bin/env bash
# Управление Yeelight Screen Light Bar Pro по LAN
# Использование: ./yee.sh <команда> [значения]
IP="${YEE_IP:-192.168.88.104}"
PORT=55443
send() {
echo -e "{\"id\":1,\"method\":\"$1\",\"params\":[$2]}\r\n" | nc -w 2 "$IP" "$PORT"
echo
}
case "$1" in
on) send set_power '"on","smooth",500' ;;
off) send set_power '"off","smooth",500' ;;
bright) send set_bright "$2,\"smooth\",500" ;; # 1..100
ct) send set_ct_abx "$2,\"smooth\",500" ;; # 2700..6500
# --- фоновая подсветка ---
bg-on) send bg_set_power '"on","smooth",500' ;;
bg-off) send bg_set_power '"off","smooth",500' ;;
bg-bright) send bg_set_bright "$2,\"smooth\",500" ;;
bg-rgb) send bg_set_rgb "$2,\"smooth\",500" ;; # R*65536+G*256+B
bg-hsv) send bg_set_hsv "$2,$3,\"smooth\",500" ;; # hue 0..359, sat 0..100
status) send get_prop '"power","bright","ct","color_mode","bg_power","bg_bright","bg_rgb"' ;;
*) echo "on|off|bright N|ct N|bg-on|bg-off|bg-bright N|bg-rgb N|bg-hsv H S|status" ;;
esac
Делаем исполняемым и пользуемся по-человечески:
$ chmod +x yee.sh$ $ ./yee.sh on # включить основной свет$ ./yee.sh ct 4000 # тёплый-нейтральный белый$ ./yee.sh bg-on # включить подсветку$ ./yee.sh bg-hsv 200 100 # подсветка голубым$ ./yee.sh status # текущее состояние обеих зон
Всё. Лампа полностью под контролем из консоли, обе зоны, без облака и приложения.
На какие грабли наступишь
- Забыл включить LAN Control. Самая частая причина «лампа в сети, но порт 55443 молчит». Без галки в приложении протокол выключен наглухо.
- Лимит на частоту команд. Лампа ограничивает поток команд (примерно 60 в минуту). Если хочешь слать их часто — например, гонять плавные переливы — нужен music mode через
udp_sess_new, при котором лампа сама подключается к твоему сокету и лимит снимается. Это тема для отдельного разговора. - RGB — это одно число, а не тройка. Перепутать порядок байтов в
R*65536+G*256+B легко. Если сомневаешься — задавай цвет через bg_set_hsv, там понятные hue и saturation. - Не все команды есть у всех ламп. Прежде чем гадать — загляни в поле
support из SSDP-ответа. Это исчерпывающий список того, что лампа умеет.
Что дальше
Bash — это конечно здорово, но в быту хочется, чтобы лампа жила в общей системе умного дома, реагировала на сценарии. Логичное продолжение — завести её в Home Assistant. У HA есть встроенная интеграция Yeelight, которая работает по тому же самому локальному протоколу, что мы разобрали тут.
А ещё в поле support я присмотрел пару команд, которые просятся в отдельный эксперимент: start_cf и bg_start_cf — это color flow, плавные переливы цвета по заданным ключевым кадрам. Можно сделать подсветке «дыхание» или медленную радугу под настроение. А set_segment_rgb намекает, что подсветку можно адресовать посегментно — тоже интересно покопать.
А пока — у тебя есть лампа, которая слушается из терминала. Если повторял на своей Yeelight и наткнулся на что-то странное в поле support или на свои команды с приставкой bg_ — напиши в комментах, любопытно собрать карту того, что умеют разные модели.