Управление лампочками Yeelight

Использование системы в различных ситуациях, вопросы программирования сценариев.

Модератор: immortal

erandess
Сообщения: 50
Зарегистрирован: Пт окт 03, 2014 9:04 am
Благодарил (а): 2 раза
Поблагодарили: 13 раз

Управление лампочками Yeelight

Сообщение erandess » Чт июл 06, 2017 4:57 pm

В ожидании пока кто-то из знатоков PHP приобретет себе какую-нибудь лампочку семейства Yeelight и напишет модуль для управления этим хозяйством из Majordomo, я решил хоть что-нибудь сварганить самому. PHP я знаю на уровне скриптов в Majordomo, т.е. на минимуме, поэтому по качеству кода претензий не принимаю, но советам буду рад.
API Yeelight довольно подробно описан, и, в принципе, все управление сводится к поиску лампочек через широковещательные пакеты UDP и обмену JSON строками между лампочкой и сервером. Ниже я выкладываю свой гуглоперевод этого API. За основу взял класс Yeelight отсюда: https://github.com/elberth90/yeelight-api-client , и немного упростил его.

Итак, что же нужно чтобы управлять лампочкой (светильником, RGB-лентой) Yeelight:
1. Поставить на смартфон фирменное приложение Yeelight и в настройках включить режим разработчика.
2. Распаковать из прикрепленного архива библиотеку классов Yeelight_library.php в папку .../htdocs/modules/Yeelight/.
3. Создать сценарий поиска лампочек и формирования класса, объектов и методов для Majordomo, назовем его, например, searchYeelightBulb с нижеследующим кодом.

Код: Выделить всё

//Скрипт для MD, поиск устройств в сети, создание объектов MD, создание и заполнение свойств объектов MD
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
//Создание класса MD Yeelight, классовых свойств и методов
addClass('Yeelight');
addClassProperty('Yeelight', 'id', 0); //Создаёт свойство класса и указывает, что необходимо хранить историю значений 0 дней
addClassProperty('Yeelight', 'model', 0);
addClassProperty('Yeelight', 'status', 0);
addClassProperty('Yeelight', 'model', 0);
addClassProperty('Yeelight', 'bright', 0);
addClassProperty('Yeelight', 'Location', 0);
addClassProperty('Yeelight', 'name', 0);
addClassProperty('Yeelight', 'support', 0);
addClassMethod('Yeelight', 'on_off');
addClassMethod('Yeelight', 'set_bright');
addClassMethod('Yeelight', 'set_name');
//=======================================
//Создание объектов класса
//    Поиск устройств
$client = new YeelightClient();
$bulbList_prop = $client->search_prop();
foreach ($bulbList_prop as $bulb) {
 //получаем из массива bulbList_prop характеристики устройств
 $id = trim($bulb[id]);
 $Location = trim($bulb[Location]);
 $model = trim($bulb[model]); 
 $name =  trim($bulb[name]); 
 $COLOR_MODE = trim($bulb[color_mode]);
 $powerTXT = $bulb[power];
 if ($powerTXT == "on") { $power = 1; }
 if ($powerTXT = "off") { $power = 0; }
 $bright = trim($bulb[bright]);
 $ct = trim($bulb[ct]);
 $rgb = dechex($bulb[rgb]);
 $hue = trim($bulb[hue]);
 $sat = trim($bulb[sat]);
 $support = trim($bulb[support]); 
 
 //получаем список объектов класса
 $objects=getObjectsByClass("Yeelight");
 $searhID = 0;
 foreach($objects as $obj) {
  if ((gg($obj['TITLE'].".id")) == $id){
   $searhID += 1;   
  }     
 }
 if ($searhID) { 
  echo "Устройство с id".$id." в базе MD уже есть"."<br />";
 } else {  
  echo "Устройство с ID".$id." в базе MD не найдено. Добавляем новый объект"."<br />";
  if ($name) {
   $objName = $name;
  } else {
   $objName = "Bulb_".$id; 
  }
  addClassObject('Yeelight', $objName); //создаем объект с новым id
  //заполняем классовые свойства объекта
  setGlobal($objName.".id",$id);
  setGlobal($objName.".model",$model);
  setGlobal($objName.".status",$power);
  setGlobal($objName.".bright",$bright);
  setGlobal($objName.".Location",$Location);
  setGlobal($objName.".name",$name);
  setGlobal($objName.".support",$support);
   
  //создаем свойства объекта с учетом специфики ламп
  if ($model =="stripe" OR $model =="color") {
   echo "Лампа цветная - создаем свойства и методы управления цветом"."<br />";
    
   $result = strpos ($support, 'set_rgb');
   if ($result) {  
    setGlobal($objName.".rgb",$rgb);
    addClassMethod('Yeelight', 'set_rgb');
    echo "Cоздается свойство RGB"."<br />";
   }
   
   $result = strpos ($support, 'set_ct_abx');
   if ($result) {
    setGlobal($objName.".ct",$ct);
    addClassMethod('Yeelight', 'set_ct');
    echo "Cоздается свойство CT"."<br />";
   }
   
   $result = strpos ($support, 'set_hsv');
   if ($result) {
    setGlobal($objName.".hue",$hue);
    setGlobal($objName.".sat",$sat);
    addClassMethod('Yeelight', 'set_hsv');
    echo "Cоздается свойство HSV"."<br />";
   }
  } elseif ($model =="mono") {
   echo "Лампа монохромная - базовые свойства созданы"."<br />";
  }    
 }
}
 
Запускать сценарий надо каждый раз для подключения новой лампочки. Сценарий ищет лампочки, определяет тип лампы, в зависимости от типа создает набор свойств и заполняет свойства значениями.

4. Также сценарий создает пустые методы, которые надо будет заполнить кодом из нижеследующего списка согласно имени метода:

Код: Выделить всё

//========= метод on_off (включение/выключение) ===================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$status = $this->getProperty('status');
if ($status) {$power = 'on'; }
if (!$status) {$power = 'off'; }
$data = [
"Location" => $Location,
"id" => $id, 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setPower($power, 'smooth', 1000); //включить/выключить
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }

//========= метод set_bright (установка яркости) ====================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$bright = (int) ($this->getProperty('bright'));
$data = [
"Location" => $Location,
"id" => $id, 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setBright($bright, 'smooth', 1000);  //установить яркость
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }
//=======метод set_rgb (установка цвета RGB)======================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$rgb = hexdec($this->getProperty('rgb'));
$data = [
"Location" => "$Location",
"id" => "$id", 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setRgb($rgb, 'smooth', 1000);  //установить цвет
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }

//=======метод set_ct (установка цвета CT)=======================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$ct = (int) ($this->getProperty('ct'));
$data = [
"Location" => "$Location",
"id" => "$id", 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setCtAbx($ct, 'smooth', 1000);  //установить цвет
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }

//=======метод set_hsv (установка цвета Hsv)======================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$hue = (int) ($this->getProperty('hue'));
$sat = (int) ($this->getProperty('sat'));
$data = [
"Location" => "$Location",
"id" => "$id", 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setHsv($hue, $sat, 'smooth', 1000);  //установить цвет
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }

//=======метод set_name(установка имени)=======================
include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$Location = $this->getProperty('Location');
$id = $this->getProperty('id');
$name = $this->getProperty('name');
$data = [
"Location" => "$Location",
"id" => "$id", 
];
$socketFactory = new Factory();
$bulbFactory = new BulbFactory($socketFactory);
$bulb = $bulbFactory->create($data);
$res = $bulb->setName($name);  //установить имя
if (array_key_exists('result', $res)) {
    $result = $res [result][0];
    //переменная содержит ответ от лампочки
    }
if (array_key_exists('error', $res)) {
    $result = $res [error][message].". Code ".$res [error][code];
    DebMes("Ошибка Yeelight: ".$result);
    }


5. Здесь у нас возникает вопрос: привязывать ли методы к изменению свойств? Думаю однозначно можно привязать метод set_name(установка имени устройства) к свойству name. Привязка остальных зависит от вашего желания иметь обратную связь.
6. По поводу обратной связи... Конечно лампочка подтверждает исполнение команды и мы это отслеживаем в методах. 
Но полноценной обратной связи в данной реализации управления нет, т.е. если мы включим лампочку из приложения Yeelight со смартфона, статус ее в Majordomo не изменится. Что же делать?
Вариант первый - обратная связь не нужна, мы управляем лампочкой только через Majordomo. Привязываем исполнение всех методов к изменению соответствующих свойств и не паримся больше по этому поводу.
Вариант второй - можно периодически проверять состояние лампочек отдельным сценарием и обновлять свойства. Частоту запуска сценария выбираем исходя из компромисса между временем отображения изменений и нагрузкой сети.

Код сценария:

Код: Выделить всё

include_once(DIR_MODULES.'Yeelight/Yeelight_library.php');
$objects=getObjectsByClass("Yeelight");
foreach($objects as $obj) {
    $objName = $obj['TITLE'];
    $id = gg($objName.".id");
    $Location = gg($objName.".Location");    
    $data = [
    "Location" => $Location,
    "id" => $id, 
    ];    
    $socketFactory = new Factory();
    $bulbFactory = new BulbFactory($socketFactory);
    $bulb = $bulbFactory->create($data);    
    $res = $bulb->getProp([BulbProperties::POWER,BulbProperties::BRIGHT,BulbProperties::RGB,BulbProperties::COLOR_TEMPERATURE,BulbProperties::HUE,BulbProperties::SATURATION]);    
    $powerTXT = $res[result][0];
    if ($powerTXT == "on") { $power = 1; }
    if ($powerTXT = "off") { $power = 0; }
    setGlobal($objName.".status",$power);
    
    $bright = $res[result][1];
    setGlobal($objName.".bright",$bright);
    
    $rgb = dechex($res[result][2]);
    if ($rgb) {setGlobal($objName.".rgb",$rgb); }
    
    $ct = $res[result][3];
    if ($ct) {setGlobal($objName.".ct",$ct); }
    
    $hue = $res[result][4];
    if ($hue) {setGlobal($objName.".hue",$hue); }
    
    $sat = $res[result][5];
    if ($sat) {setGlobal($objName.".sat",$sat); }
    echo ". ";
}
    echo "ok";
 
Вариант третий - который я пока не смог реализовать. В API Yeelight предусмотрена обратная связь - при изменении любого свойства лампы любым контроллером(программой управления), все контроллеры получают сообщения класса Notification. Т.е. все, что нам надо - в цикле слушать все входящие сообщения на TCP порту 55443, и анализировать их. Но моих знаний на это пока не хватает.
В последних двух вариантах привязку методов к свойствам не делаем, так как при работе скрипта обновления получим повторную отработку методов.

7. Далее создаем пункты меню с контролами для управления. Здесь все типовое, единственно следует иметь ввиду, что если методы у нас не привязаны к свойствам, при настройке контрола указываем и свойство и метод. И аналогично при использовании в своих сценариях устанавливаем свойство и запускаем метод. А если привязаны - управляем только изменением свойств.

P.S. Код я протестировал пока только на белой лампочке. Скоро придет цветная - протестирую и на ней.
Вложения
Yeelight_library.zip
Класс Yeelight
(9.65 КБ) 162 скачивания
Yeelight_API перевод.zip
Yeelight API
(157.97 КБ) 155 скачиваний
Последний раз редактировалось erandess Чт авг 24, 2017 8:02 pm, всего редактировалось 3 раза.
За это сообщение автора erandess поблагодарили (всего 5):
VooDooN (Чт июл 06, 2017 5:17 pm) • skysilver (Чт июл 06, 2017 5:32 pm) • chimik (Сб июл 08, 2017 12:46 am) • Gelezako (Сб дек 02, 2017 6:31 pm) • stellhawk (Пт янв 04, 2019 4:46 pm)
Рейтинг: 6.85%
Аватара пользователя
sergejey
Site Admin
Сообщения: 4265
Зарегистрирован: Пн сен 05, 2011 6:48 pm
Откуда: Минск, Беларусь
Благодарил (а): 74 раза
Поблагодарили: 1435 раз
Контактная информация:

Re: Управление лампочками Yeelight

Сообщение sergejey » Вт июл 11, 2017 11:34 am

Это было бы здорово модулем оформить :)
Поставил себе закладку на эту тему, чтобы как руки дойдут до модуля, так ваши наработки использовать -- за это спасибо )

Сергей Джейгало, разработчик MajorDoMo
Идеи, ошибки -- за предложениями по исправлению и развитию слежу только здесь!
Профиль Connect -- информация, сотрудничество, услуги
jiraff
Сообщения: 6
Зарегистрирован: Ср сен 14, 2016 4:58 pm
Благодарил (а): 0
Поблагодарили: 1 раз

Re: Управление лампочками Yeelight

Сообщение jiraff » Пт июл 14, 2017 10:31 am

Спасибо за сценарий поиска лампочек.
Я, для управления лампами, пользуюсь функциями такого типа:

Код: Выделить всё

$ip = $this->getProperty('IP');
$id = $this->getProperty('ID');
$bright = $this->getProperty('bright');
$cart = array(
  "id" => $id,
  "method" => "set_bright",
  "params" => array(
    intval($bright),
    "smooth",
    500)
);
$command = json_encode( $cart );
$con = pfsockopen($ip, 55443, $errno, $errstr, 10);
fwrite($con, $command);
fwrite($con,"\r\n");
$cart=fread($con, 4096); //получаем ответ от лампы
$jstring=json_decode($cart);
//echo $jstring->result[0];
fclose($con);
 
Это метод изменения яркости. По такому же принципу работают остальные методы, меняются только передаваемые параметры переменной $cart. В свойствах параметра объекта ставлю выполнять метод при изменении. И раз в минуту выполняю метод получения параметров на случай изменения их из сторонних источников управления.
За это сообщение автора jiraff поблагодарил:
skysilver (Пт июл 14, 2017 11:52 am)
Рейтинг: 1.37%
Aven
Сообщения: 391
Зарегистрирован: Сб мар 12, 2016 6:33 pm
Откуда: Ухта, Россия
Благодарил (а): 1 раз
Поблагодарили: 78 раз

Re: Управление лампочками Yeelight

Сообщение Aven » Вс июл 30, 2017 5:38 pm

А поиск у кого-то реально работает?
Aven
Сообщения: 391
Зарегистрирован: Сб мар 12, 2016 6:33 pm
Откуда: Ухта, Россия
Благодарил (а): 1 раз
Поблагодарили: 78 раз

Re: Управление лампочками Yeelight

Сообщение Aven » Пн июл 31, 2017 4:41 pm

Сам себе отвечаю.
Библиотека некорректно работает на сервере где несколько сетевых интерфейсов.
Если он один, то поиск работает нормально.

Забавно, что python скрипт от производителя тоже содержит этот баг, а вот Windows программа нет :)
erandess
Сообщения: 50
Зарегистрирован: Пт окт 03, 2014 9:04 am
Благодарил (а): 2 раза
Поблагодарили: 13 раз

Re: Управление лампочками Yeelight

Сообщение erandess » Пн июл 31, 2017 5:51 pm

Aven писал(а): Библиотека некорректно работает на сервере где несколько сетевых интерфейсов.
Если он один, то поиск работает нормально.
Я думаю, это более общая проблема нестыковки UDP и нескольких интерфейсов.
Вот например заметка на хабре: https://habrahabr.ru/post/146922/
Aven
Сообщения: 391
Зарегистрирован: Сб мар 12, 2016 6:33 pm
Откуда: Ухта, Россия
Благодарил (а): 1 раз
Поблагодарили: 78 раз

Re: Управление лампочками Yeelight

Сообщение Aven » Пн июл 31, 2017 6:48 pm

Если бы Вы прочитали последний коммент в статье, поняли бы, что проблема автора оказалась в конкретной реализации программы "nc".
erandess
Сообщения: 50
Зарегистрирован: Пт окт 03, 2014 9:04 am
Благодарил (а): 2 раза
Поблагодарили: 13 раз

Re: Управление лампочками Yeelight

Сообщение erandess » Чт авг 24, 2017 10:57 am

По результатам экспериментов с цветной лампочкой, немного изменил сценарий поиска лампочек, и немного подкорректировал методы. Отредактированные версии в первом сообщении.
Суть изменений: я считал, что цветная лампочка управляется только одним из методов - установкой либо RGB свойств, либо цветовой температурой, либо парой "насыщенность - оттенок". Лампочки на руках не было, проверить не мог. Оказалось, что лампочкой можно управлять любым способом - она сама переключается на требуемый режим работы при установке этих параметров. Исправленный сценарий проверяет поддерживаемость управления лампочкой методами установки цвета и создает необходимые свойства в Majordomo. Методы остались теми же, только поправил пару ошибок.
alibi75
Сообщения: 10
Зарегистрирован: Пт сен 08, 2017 12:37 pm
Благодарил (а): 0
Поблагодарили: 1 раз

Re: Управление лампочками Yeelight

Сообщение alibi75 » Чт окт 05, 2017 9:30 pm

jiraff писал(а):Спасибо за сценарий поиска лампочек.
Я, для управления лампами, пользуюсь функциями такого типа:

Код: Выделить всё

$ip = $this->getProperty('IP');
$id = $this->getProperty('ID');
$bright = $this->getProperty('bright');
$cart = array(
  "id" => $id,
  "method" => "set_bright",
  "params" => array(
    intval($bright),
    "smooth",
    500)
);
$command = json_encode( $cart );
$con = pfsockopen($ip, 55443, $errno, $errstr, 10);
fwrite($con, $command);
fwrite($con,"\r\n");
$cart=fread($con, 4096); //получаем ответ от лампы
$jstring=json_decode($cart);
//echo $jstring->result[0];
fclose($con);
Это метод изменения яркости. По такому же принципу работают остальные методы, меняются только передаваемые параметры переменной $cart. В свойствах параметра объекта ставлю выполнять метод при изменении. И раз в минуту выполняю метод получения параметров на случай изменения их из сторонних источников управления.
Покажите, пожалуйста, подробнее как применяется этот скрипт и передаются переменные? Я начинающий "автоматизатор", добавил лампочку по принципу как в начале этого поста, а дальше как ими управлять пока не разобрался (точнее разобрался как включить и выключить, а как менять яркость цвет и т.д. нет).
Буду очень благодарен!
Аватара пользователя
Gelezako
Сообщения: 921
Зарегистрирован: Чт июн 02, 2016 9:33 pm
Благодарил (а): 200 раз
Поблагодарили: 86 раз
Контактная информация:

Re: Управление лампочками Yeelight

Сообщение Gelezako » Пт ноя 03, 2017 4:51 pm

выполнил сценарий - нашло 2 лампочки как и ожидалось, всё отлично, закинул код во все методы, прямо в классе меняю свойство status на 0 или 1 - лампочка не реагирует, пробовал менять bright, тоже ноль реакции. Помогите продебажить что не так.
фанат Мажордомо
тематический блог http://blog.gelezako.com
плейлист про Мажордомо на ютубе https://www.youtube.com/playlist?list=P ... EdBGtX084E
Ответить