Samir77

 
<<< Back

Увлажнитель воздуха Xiaomi SmartMi Air Humidifier 2 (CJXJSQ04ZM) (с OLED экраном) (Международная версия) zhimi.humidifier.ca4

При подключении Увлажнитель воздуха Xiaomi SmartMi Air Humidifier 2 (CJXJSQ04ZM) через модуль [Модуль] Xiaomi miIO (xiaomimiio), столкнулся с тем , что метрики не получает.((

Только стандартные, скрин приложу...
Перехватывать команды через специальные проги пробовал, даже токен нормально не отдают одни FFFFFFFFFFFFF
Перерыв весь интернет и найдя команды , благо 2 команды нарыл еще на форуме в теме [Модуль] Xiaomi miIO (xiaomimiio)
Отработало....

Как пример:
Вкл -
М: set_properties
P: [{"did":"power","siid":2,"piid":1,"value":true}]

Выкл -
М: set_properties
P: [{"did":"power","siid":2,"piid":1,"value":false}]

Работающие метрики выкладываю!!! брал здесь

https://github.com/rytilahti/python-miio/blob/mast...

 """Container for status reports from the air humidifier.
    Xiaomi Smartmi Evaporation Air Humidifier 2 (zhimi.humidifier.ca4) respone (MIoT format)
    [
        {'did': 'power', 'siid': 2, 'piid': 1, 'code': 0, 'value': True},
        {'did': 'fault', 'siid': 2, 'piid': 2, 'code': 0, 'value': 0},
        {'did': 'mode', 'siid': 2, 'piid': 5, 'code': 0, 'value': 0},
        {'did': 'target_humidity', 'siid': 2, 'piid': 6, 'code': 0, 'value': 50},
        {'did': 'water_level', 'siid': 2, 'piid': 7, 'code': 0, 'value': 127},
        {'did': 'dry', 'siid': 2, 'piid': 8, 'code': 0, 'value': False},
        {'did': 'use_time', 'siid': 2, 'piid': 9, 'code': 0, 'value': 5140816},
        {'did': 'button_pressed', 'siid': 2, 'piid': 10, 'code': 0, 'value': 2},
        {'did': 'speed_level', 'siid': 2, 'piid': 11, 'code': 0, 'value': 790},
        {'did': 'temperature', 'siid': 3, 'piid': 7, 'code': 0, 'value': 22.7},
        {'did': 'fahrenheit', 'siid': 3, 'piid': 8, 'code': 0, 'value': 72.8},
        {'did': 'humidity', 'siid': 3, 'piid': 9, 'code': 0, 'value': 39},
        {'did': 'buzzer', 'siid': 4, 'piid': 1, 'code': 0, 'value': False},
        {'did': 'led_brightness', 'siid': 5, 'piid': 2, 'code': 0, 'value': 2},
        {'did': 'child_lock', 'siid': 6, 'piid': 1, 'code': 0, 'value': False},
        {'did': 'actual_speed', 'siid': 7, 'piid': 1, 'code': 0, 'value': 0},
        {'did': 'power_time', 'siid': 7, 'piid': 3, 'code': 0, 'value': 18520},
        {'did': 'clean_mode', 'siid': 7, 'piid': 5, 'code': 0, 'value': True}
    ]
    """

за что отвечает и как прописывать

# Source http://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:humidifier:0000A00E:zhimi-ca4:2
    # Air Humidifier (siid=2)
    "power": {"siid": 2, "piid": 1},  # bool
    "fault": {"siid": 2, "piid": 2},  # [0, 15] step 1
    "mode": {"siid": 2, "piid": 5},  # 0 - Auto, 1 - lvl1, 2 - lvl2, 3 - lvl3
    "target_humidity": {"siid": 2, "piid": 6},  # [30, 80] step 1
    "water_level": {"siid": 2, "piid": 7},  # [0, 128] step 1
    "dry": {"siid": 2, "piid": 8},  # bool
    "use_time": {"siid": 2, "piid": 9},  # [0, 2147483600], step 1
    "button_pressed": {"siid": 2, "piid": 10},  # 0 - none, 1 - led, 2 - power
    "speed_level": {"siid": 2, "piid": 11},  # [200, 2000], step 10
    # Environment (siid=3)
    "temperature": {"siid": 3, "piid": 7},  # [-40, 125] step 0.1
    "fahrenheit": {"siid": 3, "piid": 8},  # [-40, 257] step 0.1
    "humidity": {"siid": 3, "piid": 9},  # [0, 100] step 1
    # Alarm (siid=4)
    "buzzer": {"siid": 4, "piid": 1},
    # Indicator Light (siid=5)
    "led_brightness": {"siid": 5, "piid": 2},  # 0 - Off, 1 - Dim, 2 - Brightest
    # Physical Control Locked (siid=6)
    "child_lock": {"siid": 6, "piid": 1},  # bool
    # Other (siid=7)
    "actual_speed": {"siid": 7, "piid": 1},  # [0, 2000] step 1
    "power_time": {"siid": 7, "piid": 3},  # [0, 4294967295] step 1
    "clean_mode": {"siid": 7, "piid": 5},  # bool

Дальше больше.. Подключился Тарас , он же @Tarasfrompir https://connect.smartliving.ru/profile/1358
Потраченное время и весь код , создание отдельного ПУ, ПОЛНОСТЬЮ на нем, от меня только пожелания , доступ к оборудованию))

Ну что ж по пунктам.

  1. Создаем файл по пути(у меня линукс Raspbery pi3B+) html/templates/classes/views/SXIMIHumidifier.html
    Это то, как будет выглядеть отображение..как по мне каждый под себя сам сделает,
    Но когда есть с чего брать, так проще!


<div id="%.object_id%" class="device-widget">
    <div class="container device-icon" style='background-image:url(/modules/devices/addons/SXIMIClass/zhimi.humidifier.ca4.png);background-size: 55px 65px;width:75px;height:75px;margin-right:5px'></div>
   </br>
    <div class="device-details"> 
        <button  style="%.deviseStatus|"none;color:red;"%" onClick='ajaxSetGlobal("%.object_title%.targetdeviseStatus","true");'> Вкл.</button>
        <button  style="%.deviseStatus|"color:red;none;"%" onClick='ajaxSetGlobal("%.object_title%.targetdeviseStatus","false");'> Выкл.</button>
    </div>
    <div class="device-details"> 
        <button  style="%.mode|"color:red;none;none;none;"%" onClick='ajaxSetGlobal("%.object_title%.targetmode", 0);'>Авто.</button>
        <button  style="%.mode|"none;color:red;none;none;"%"  onClick='ajaxSetGlobal("%.object_title%.targetmode", 1);'>Мал.</button>
        <button  style="%.mode|"none;none;color:red;none;"%" onClick='ajaxSetGlobal("%.object_title%.targetmode", 2);'>Сред.</button>
        <button  style="%.mode|"none;none;none;color:red;"%"onClick='ajaxSetGlobal("%.object_title%.targetmode", 3);'>Сильн.</button>
    </div>
     <div class="device-details"> 
        <button  style="%.led_b|"color:red;none;none;"%" onClick='ajaxSetGlobal("%.object_title%.led_btarget", 0);'>Ночь.</button>
        <button  style="%.led_b|"none;color:red;none;"%" onClick='ajaxSetGlobal("%.object_title%.led_btarget", 1);'>Вечер.</button>
        <button  style="%.led_b|"none;none;color:red;"%" onClick='ajaxSetGlobal("%.object_title%.led_btarget", 2);'>День.</button>
    </div>
    <div class="device-details">
        <input type="range" onChange='ajaxSetGlobal("%.object_title%.targethumidity", value);' value=%.targethumidity%  min="10" max="90" step="5" style="width: 175px; height: 25px; display: inline-block;" ></input>
    </div>
    <div class="device-header" style="padding-top:0px;height:22px;"><span>%.object_description%</span></div>
    <div class="device-details">Уровень воды: <progress max="128" value="%.waterLevel%"></progress> <b>
        <text id="waterLev">%.waterLevel% %</text></b>
        </div>
    <div class="device-details">Температура воздуха: <b>
        <text id="temp">%.temperature% &deg;C</text></b></div>
    <div class="device-details">Влажность: <b>
        <text id="humidyty">%.humidity% %</text>
    </b></div>
    <div class="device-details">Режим работы увлажнителя: %.mode%  (%.speedFan% rpm)</div> 
    <div class="device-details">Режим работы подсветки: %.led_b%</div> 

<script type="text/javascript">

$(document).ready(function() {
  var humidyty = %.humidity%;
  if (humidyty<110) {
    $('#humidyty').css('color', '#0099FF');
  } else if (humidyty<100) {
    $('#humidyty').css('color', '#0099FF');
  } else if (humidyty<80) {
    $('#humidyty').css('color', '#00CCFF');
  } else if (humidyty<60) {
    $('#humidyty').css('color', '#00FFFF');
  } else if (humidyty<40) {
    $('#humidyty').css('color', '#33FFFF');
  } else {
    $('#humidyty').css('color', '#66FFFF');
  }

  var temperature = %.temperature%;
  if (temperature > 32) {
    $('#temp').css('color', '#EE1604');
  } else if (temperature > 26) {
    $('#temp').css('color', '#DA820A');
  } else if (temperature > 22) {
    $('#temp').css('color', '#DADA0A');
  } else if (temperature > 16) {
    $('#temp').css('color', '#95B90F');
  } else if (temperature > 12) {
    $('#temp').css('color', '#0FB91F');
  } else if (temperature > 8) {
    $('#temp').css('color', '#18E582');
  } else if (temperature > 0) {
    $('#temp').css('color', '#18DEE5');
  } else {
    $('#temp').css('color', '#0099FF');
  }

  var waterLevel = %.waterLevel%;
  if (waterLevel>90) {
    $('#waterLev').css('color', '#1E90FF');
  } else if (waterLevel>80) {
    $('#waterLev').css('color', '#00CED1');
  } else if (waterLevel>60) {
    $('#waterLev').css('color', '#48D1CC');
  } else if (waterLevel>40) {
    $('#waterLev').css('color', '#40E0D0');
  } else if (waterLevel>20) {
    $('#waterLev').css('color', '#7FFFD4');
  } else if (waterLevel>10) {
    $('#waterLev').css('color', '#E0FFFF');
  } else {
    $('#waterLev').css('color', '#8B0000');
  }

});
</script>

</div>

2.
Создаем файл по пути html/modules/devices/addons/SXIMIHumidifier_structure.php

<?php
$this->device_types['Xiaomihumidifier'] = array(
    'TITLE'=>'XiaomiHumidifier-CA4',
    'PARENT_CLASS'=>'SDevices',
    'CLASS'=>'SXIMIHumidifier',
    'PROPERTIES'=>array(
        'deviseStatus'=>array('DESCRIPTION'=>'Состояние устройства'),
        'targetdeviseStatus'=>array('DESCRIPTION'=>'Целевое состояние устройства','ONCHANGE'=>'setHumidityPower'),
        'waterLevel'=>array('DESCRIPTION'=>'Уровень воды в увлажнителе'),
        'deviseIp'=>array('DESCRIPTION'=>'IP устройства','_CONFIG_TYPE'=>'text'),
        'timeChek'=>array('DESCRIPTION'=>'Период опроса устройства в секундах','_CONFIG_TYPE'=>'text'),
        'deviseToken'=>array('DESCRIPTION'=>'Токен устройства','_CONFIG_TYPE'=>'text'),
        'humidity'=>array('DESCRIPTION'=>'Текущая влажность','KEEP_HISTORY'=>7),
        'targethumidity'=>array('DESCRIPTION'=>'Целевая влажность', 'ONCHANGE'=>'setHumidityTarget'),
        'temperature'=>array('DESCRIPTION'=>'Текущая температура','KEEP_HISTORY'=>7),
        'mode'=>array('DESCRIPTION'=>'Текущий режим'),
        'targetmode'=>array('DESCRIPTION'=>'Текущий режим','ONCHANGE'=>'setHumidytyMode'),
        'depth'=>array('DESCRIPTION'=>'Текущий уровень'),
        'speedFan'=>array('DESCRIPTION'=>'Текущая скорость вентилятора'),
        'dry'=>array('DESCRIPTION'=>'Драй','_CONFIG_TYPE'=>'yesno'),
        'use_time'=>array('DESCRIPTION'=>'Установка времени работы увлажнителя'),
        'led_b'=>array('DESCRIPTION'=>'Текущая подсветка'),
        'led_btarget'=>array('DESCRIPTION'=>'Подсветка','ONCHANGE'=>'setHumidityLed'),
        'buzzer'=>array('DESCRIPTION'=>'Звуковой индикатор','_CONFIG_TYPE'=>'yesno'),
        'child_lock'=>array('DESCRIPTION'=>'Защита от детей','_CONFIG_TYPE'=>'yesno'),
        'limit_hum'=>array('DESCRIPTION'=>'Максимальная влажность','_CONFIG_TYPE'=>'select','_CONFIG_OPTIONS'=>'20,30,40,50,60,70,80,90'),
    ),
    'METHODS'=>array(
        'getHumidityStatus'=>array('DESCRIPTION'=>'Получение статуса увлажнителя','_CONFIG_SHOW'=>1),
        'setHumidityPower'=>array('DESCRIPTION'=>'Установка состояния увлажнителя','_CONFIG_SHOW'=>1),
        'setHumidityTarget'=>array('DESCRIPTION'=>'Установки уровня влажности','_CONFIG_SHOW'=>1),
        'setHumidytyMode'=>array('DESCRIPTION'=>'Включение режима увлажнителя','_CONFIG_SHOW'=>1),
        'setHumidityLed'=>array('DESCRIPTION'=>'Установка уровня подсветки','_CONFIG_SHOW'=>1),
        )
);

3.
в папке addons создаем папку SXIMIClass , получаем такой путь html/modules/devices/addons/SXIMIClass/
в ней создаем два файла

html/modules/devices/addons/SXIMIClass/miio.class.php
html/modules/devices/addons/SXIMIClass/mipacket.class.php

4.
html/modules/devices/addons/SXIMIClass/miio.class.php

<?php
/**
*   Класс для работы с wifi-устройствами из экосистемы xiaomi по протоколу miIO.
*
*   + прием udp-пакетов из сокета
*   + отправка udp-пакетов в сокет
*   + процедура рукопожатия (handshake)
*   + отправка сообщений устройству
*   + прием ответов от устройства
*   + поиск устройств (handshake-discovery)
*
*   https://github.com/aholstenson/miio
*   https://github.com/rytilahti/python-miio
*   https://github.com/marcelrv/XiaomiRobotVacuumProtocol
*
*   Copyright (C) 2017-2019 Agaphonov Dmitri aka skysilver <skysilver.da@gmail.com>
*/
require('mipacket.class.php');
const   MIIO_PORT = '54321';
const   HELLO_MSG = '21310020ffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
class miIO {
    public  $ip = '';
    public  $token = '';
    public  $debug = '';
    public  $send_timeout = 2;
    public  $disc_timeout = 10;

    public  $msg_id = '1';
    public  $useAutoMsgID = false;

    public  $data = '';
    public  $sock = NULL;

    private $miPacket = NULL;

    public function __construct($ip = NULL, $bind_ip = NULL, $token = NULL, $debug = false) {

        $this->debug = $debug;

        $this->miPacket = new miPacket();

        if ($ip != NULL) $this->ip = $ip;

        if ($bind_ip != NULL) $this->bind_ip = $bind_ip;
         else $this->bind_ip = '0.0.0.0';

        if ($token != NULL) $this->token = $token;

        if ($this->debug) {
            if ($this->ip == NULL) echo "Broadband discovery mode" . PHP_EOL;
             else echo "Connection to device by IP $this->ip" . PHP_EOL;
            echo "Debug status [$this->debug]" . PHP_EOL;
        }

        $this->sockCreate();

    }

    public function __destruct() {

        @socket_shutdown($this->sock, 2);
        @socket_close($this->sock);

    }

    /*
        Создание udp4 сокета.
    */

    public function sockCreate() {

        if (!($this->sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP))) {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);
            if ($this->debug) echo "Error socket create - [socket_create()] [$errorcode] $errormsg" . PHP_EOL;
            die("Error socket create - [socket_create()] [$errorcode] $errormsg \n");
        } else { if ($this->debug) echo 'Socked created' . PHP_EOL; }

    }

    /*
        Установка параметров сокета - таймаут.
    */

    public function sockSetTimeout($timeout = 2) {

        if (!socket_set_option($this->sock, SOL_SOCKET, SO_RCVTIMEO, array("sec" => $timeout, "usec" => 0))) {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);
            if ($this->debug) echo "Error setting timeout SO_RCVTIMEO - [socket_create()] [$errorcode] $errormsg" . PHP_EOL;
        } else { if ($this->debug) echo 'Timeout SO_RCVTIMEO successfully set' . PHP_EOL; }

    }

    /*
        Установка параметров сокета - броадкаст.
    */

    public function sockSetBroadcast() {

        if (!socket_set_option($this->sock, SOL_SOCKET, SO_BROADCAST, 1)) {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);
            if ($this->debug) echo "Error setting broadcast SO_BROADCAST - [socket_create()] [$errorcode] $errormsg" . PHP_EOL;
        } else { if ($this->debug) echo 'Broadcast SO_BROADCAST successfully set' . PHP_EOL; }

    }

    /*
        Поиск устройства и начало сессии с ним.
    */

    public function discover($ip = NULL) {

        if ($ip != NULL) {

            if ($this->debug) echo "Checking device status by $ip" . PHP_EOL;

            $this->sockSetTimeout($this->send_timeout);

            if ($this->debug) echo " >>>>> Sending hello-packet to $ip with timeout $this->send_timeout" . PHP_EOL;

            $helloPacket = hex2bin(HELLO_MSG);

            if(!($bytes = socket_sendto($this->sock, $helloPacket, strlen($helloPacket), 0, $ip, MIIO_PORT))) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "Cannot send data to socket [$errorcode] $errormsg" . PHP_EOL;
            } else { if ($this->debug) echo " >>>>> Sent $bytes bytes to socket" . PHP_EOL; }
            $buf = '';
            if (($bytes = @socket_recvfrom($this->sock, $buf, 4096, 0, $remote_ip, $remote_port)) !== false) {
                if ($buf != '') {
                    if ($this->debug) {
                        echo " <<<<< Reply received from IP $remote_ip , port $remote_port" . PHP_EOL;
                        if ($this->debug) echo "$bytes bytes received" . PHP_EOL;
                    }
                    $this->miPacket->msgParse(bin2hex($buf));
                    if ($this->debug) {
                        $this->miPacket->printHead();
                        $ts_server = time();
                        echo 'ts_server: ' . dechex($ts_server) . ' --> ' . $ts_server . ' seconds' . ' --> ' . date('Y-m-d H:i:s', $ts_server) . PHP_EOL;
                    }
                    return true;
                }
            } else if ($bytes === 0 || $bytes === false) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "Error reading socket [$errorcode] $errormsg" . PHP_EOL;
                return false;
            }
        } else {

            if ($this->debug) echo PHP_EOL . 'Looking available devices in the network (handshake discovery)' . PHP_EOL;

            $this->sockSetTimeout($this->disc_timeout);

            $this->sockSetBroadcast();

            if( !@socket_bind($this->sock, $this->bind_ip , 0) ) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "IP bind failed $this->bind_ip [$errorcode] $errormsg" . PHP_EOL;
            } else { if ($this->debug) echo "Socket binded to IP $this->bind_ip" . PHP_EOL; }

            $ip = '255.255.255.255';

            if ($this->debug) echo " >>>>> Sending hello-packet to $ip with timeout $this->disc_timeout" . PHP_EOL;

            $helloPacket = hex2bin(HELLO_MSG);

            if(!($bytes = socket_sendto($this->sock, $helloPacket, strlen($helloPacket), 0, $ip, MIIO_PORT))) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "Error sending to socket [$errorcode] $errormsg" . PHP_EOL;
            } else { if ($this->debug) echo " >>>>> $bytes bytes sent" . PHP_EOL; }

            $buf = '';
            $count = 0;
            $devinfo = array();
            $devices = array();

            while ($bytes = @socket_recvfrom($this->sock, $buf, 4096, 0, $remote_ip, $remote_port)) {
                if ($buf != '') {
                    if ($this->debug) {
                        echo ($count+1) . " <<<<< Reply received from IP $remote_ip , port $remote_port" . PHP_EOL;
                        if ($this->debug) echo "$bytes received" . PHP_EOL;
                    }
                    $this->miPacket->msgParse(bin2hex($buf));

                    if ($this->debug) {
                        $this->miPacket->printHead();
                        $ts_server = time();
                        echo 'ts_server: ' . dechex($ts_server) . ' --> ' . $ts_server . ' seconds' . ' --> ' . date('Y-m-d H:i:s', $ts_server) . PHP_EOL;
                    }

                    $devinfo = $this->miPacket->info;
                    $devinfo += ["ip" => $remote_ip];
                    $devices[] = json_encode($devinfo);
                }
                $count += 1;
                if ($bytes === 0 || $bytes === false) {
                    $errorcode = socket_last_error();
                    $errormsg = socket_strerror($errorcode);
                    if ($this->debug) echo "Error reading socket [$errorcode] $errormsg" . PHP_EOL;
                }
            }

            if(!empty($devices)) $this->data = '{"devices":'. json_encode($devices) .'}';

            if ($count != 0 || !empty($this->data)) return true;
             else return false;
        }
    }

    public function fastDiscover() {

        $timeout = 2;

        $this->sockSetTimeout($timeout);
        $this->sockSetBroadcast();

        if( !@socket_bind($this->sock, $this->bind_ip , 0) ) {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);
            if ($this->debug) echo " --> Could not bind ip to socket $this->bind_ip [$errorcode] $errormsg" . PHP_EOL;
        } else { if ($this->debug) echo " --> Socket ip binded $this->bind_ip" . PHP_EOL; }

        $ip = '255.255.255.255';

        if ($this->debug) echo " --> Sending hello-packet to $ip with timeout $timeout" . PHP_EOL;

        $helloPacket = hex2bin(HELLO_MSG);

        if(!($bytes = socket_sendto($this->sock, $helloPacket, strlen($helloPacket), 0, $ip, MIIO_PORT))) {
            $errorcode = socket_last_error();
            $errormsg = socket_strerror($errorcode);
            if ($this->debug) echo " --> Error sending data to socket [$errorcode] $errormsg" . PHP_EOL . PHP_EOL;
        } else { if ($this->debug) echo " --> $bytes bytes sent to socket" . PHP_EOL . PHP_EOL; }

    }

    /*
        Сокеты. Запись и чтение.
    */

    public function socketWriteRead($msg) {

        if ($this->discover($this->ip)) {

            if ($this->debug) echo "Device $this->ip available" . PHP_EOL;

            $this->sockSetTimeout($this->send_timeout);

            if ($this->token != NULL) {
                if(!$this->miPacket->setToken($this->token)) {
                    if ($this->debug) echo 'Incorrect tokent format!' . PHP_EOL;
                } else {
                    if ($this->debug) echo 'Using manually set token - ' . $this->token . PHP_EOL;
                }
            } else {
                if ($this->debug) echo 'Using token received automatically - ' . $this->miPacket->getToken() . PHP_EOL;
            }

            if ($this->debug) echo " >>>>> Sending packet to $this->ip with timeout $this->send_timeout" . PHP_EOL;

            $packet = hex2bin($this->miPacket->msgBuild($msg));

            if ($this->debug) {
                $this->miPacket->printHead();
                $ts_server = time();
                echo 'ts_server: ' . dechex($ts_server) . ' --> ' . $ts_server . ' seconds' . ' --> ' . date('Y-m-d H:i:s', $ts_server) . PHP_EOL;
                echo 'data: ' . $this->miPacket->data . PHP_EOL;
            }

            if(!($bytes = socket_sendto($this->sock, $packet, strlen($packet), 0, $this->ip, MIIO_PORT))) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "Cannot send data to socket [$errorcode] $errormsg" . PHP_EOL;
            } else { if ($this->debug) echo " >>>>> Sent $bytes bytes to socket" . PHP_EOL; }

            $this->miPacket->data = '';

            $buf = '';
            if (($bytes = @socket_recvfrom($this->sock, $buf, 4096, 0, $remote_ip, $remote_port)) !== false) {
                if ($buf != '') {
                    if ($this->debug) {
                        echo " <<<<< Reply from IP $remote_ip , port $remote_port" . PHP_EOL;
                        if ($this->debug) echo "Read $bytes bytes from socket" . PHP_EOL;
                    }
                    $this->miPacket->msgParse(bin2hex($buf));
                    if ($this->debug) $this->miPacket->printPacket();
                    $data_dec = $this->miPacket->decryptData($this->miPacket->data);
                    if ($this->debug) echo "Data decrypted: $data_dec" . PHP_EOL;
                    //проверить json на валидность
                    json_decode($data_dec);
                    if ($jsonErrCode = json_last_error() !== JSON_ERROR_NONE) {
                        $jsonErrMsg = $this->jsonLastErrorMsg();
                        if ($this->debug) echo "Invalid JSON data. Error: $jsonErrMsg" . PHP_EOL;
                        if ($jsonErrCode == JSON_ERROR_CTRL_CHAR) {
                            // если ошибка в управляющих символах, то удаляем хвосты в начале и в конце и возвращаем
                            if ($this->debug) echo 'Executing trim()' . PHP_EOL;
                            $this->data = trim($data_dec);
                            return true;
                        } else {
                            // если иная ошибка, возвращаем как есть для обработки на верхнем уровне
                            $this->data = $data_dec;
                        }
                    } else {
                        // если ошибок нет, то возвращаем как есть
                        if ($this->debug) echo 'JSON data is vaild.' . PHP_EOL;
                        $this->data = $data_dec;
                    }
                    return true;
                }
            } else if ($bytes === 0 || $bytes === false) {
                $errorcode = socket_last_error();
                $errormsg = socket_strerror($errorcode);
                if ($this->debug) echo "Error reading from socket [$errorcode] $errormsg" . PHP_EOL;
                return false;
            }
        } else {
            if ($this->debug) echo "Device from $this->ip did not reply to hello-request!" . PHP_EOL;
            return false;
        }
    }

    /*
        Отправка сообщения (метод и параметры раздельно) устройству и прием ответа.
    */

    public function msgSendRcv($command, $parameters = NULL, $id = 1) {

        if (isset($id) && ($id > 0) && !$this->useAutoMsgID) $this->msg_id = $id;
         else if ($this->useAutoMsgID) $this->msg_id = $this->getMsgID($this->ip);

        $msg = '{"id":' . $this->msg_id . ',"method":"'. $command . '"}';

        if ($parameters != NULL) {
            $msg = '{"id":' . $this->msg_id . ',"method":"'. $command . '","params":' . $parameters . '}';
        }

        if ($this->debug) echo "Command to send - $msg" . PHP_EOL;

        return $this->socketWriteRead($msg);
    }
    /*
        Отправка сообщения (как есть) устройству и прием ответа.
    */

    public function msgSendRcvRaw($msg) {

        if (substr_count($msg, "'") > 0 ) $msg = str_replace("'", '"', $msg);

        if ($this->debug) echo "Command to send - $msg" . PHP_EOL;

        return $this->socketWriteRead($msg);

    }

    /*
        Получить новый идентификатор для команды.
    */

    public function getMsgID($ip) {

        if (file_exists ('id.json')) {
            $file = file_get_contents('id.json');
            $ids = json_decode($file, TRUE);
        } else {
            file_put_contents('id.json', '');
            $ids = array();
        }

        if (!empty($ids)) {
            if (array_key_exists($ip, $ids)) {
                if ($ids[$ip] > 1000) $ids[$ip] = 1;
                 else $ids[$ip] += 1;
            } else {
                $ids += [$ip => 1];
            }
        } else {
            $ids = [$ip => 1];
        }

        file_put_contents('id.json', json_encode($ids));

        return $ids[$ip];
    }

    /*
        Получить описание ошибки JSON.
        (определяем функцию, если старая версия PHP)
    */

    public function jsonLastErrorMsg() {

        if (!function_exists('json_last_error_msg')) {

            function json_last_error_msg() {

                static $ERRORS = array(JSON_ERROR_NONE => 'No error has occurred',
                                        JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded',
                                        JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
                                        JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
                                        JSON_ERROR_SYNTAX => 'Syntax error',
                                        JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded');

                $error = json_last_error();
                return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error';
            }
        }

        return json_last_error_msg();

    }

    /*
        Получить miIO-сведения об устройстве.
    */

    public function getInfo($msg_id = 1) {

        return $this->msgSendRcv('miIO.info', '[]', $msg_id);

    }

}

5.
html/modules/devices/addons/SXIMIClass/mipacket.class.php

<?php
/**
*   Класс для работы с сетевыми udp-пакетами по протоколу miIO.
*
*   + генерация ключа и вектора инициализации из токена
*   + расшифровка
*   + шифрование
*   + парсинг udp-пакета
*   + сборка udp-пакета
*
*   https://github.com/OpenMiHome/mihome-binary-protocol
*
*   Copyright (C) 2017-2019 Agaphonov Dmitri aka skysilver <skysilver.da@gmail.com>
*/

class miPacket {

    private $magic = '2131';
    private $length = '';
    private $unknown1 = '00000000';
    private $devicetype = '';
    private $serial = '';
    public  $ts = '';
    private $checksum = '';
    public  $timediff = 0;

    public  $data = '';
    public  $info = array('devicetype' => '',
                          'serial' => '',
                          'token' => '');

    private $token = '';
    private $key = '';
    private $iv = '';

    /*
        Сохранение токена.
    */

    public function setToken($token) {

        if ($this->verifyToken($token)) {
            $this->token = $token;
            $this->getKeyIv();
            return true;
        } else {
            return false;
        };

    }

    /*
        Вывод токена.
    */

    public function getToken() {

        return $this->token;

    }

    /*
        Проверка длины токена.
    */

    private function verifyToken($token) {

        if (strlen($token) == 32) return true;
         else return false;

    }

    /*
        Вычисление ключа шифрования и вектора первичной инициализации на основе токена.
    */

    private function getKeyIv() {

        $this->key = md5(hex2bin($this->token));
        $this->iv = md5(hex2bin($this->key.$this->token));

    }

    /*
        Шифрование данных.
    */

    public function encryptData($data) {

        if ($this->verifyToken($this->token)) {
            return bin2hex(openssl_encrypt($data, 'AES-128-CBC', hex2bin($this->key), OPENSSL_RAW_DATA, hex2bin($this->iv)));
        } else return false;

    }

    /*
        Расшифровка данных.
    */

    public function decryptData($data) {

        if ($this->verifyToken($this->token)) {
            return openssl_decrypt(hex2bin($data), 'AES-128-CBC', hex2bin($this->key), OPENSSL_RAW_DATA, hex2bin($this->iv));
        } else return false;

    }

    /*
        Формирование пакета.
    */

    public function msgBuild($cmd) {

        $this->data = $this->encryptData($cmd);

        $this->length = sprintf('%04x', (int)strlen($this->data)/2 + 32);

        $this->ts = sprintf('%08x', time() + $this->timediff);  // с учетом разницы времени между устройством и сервером

        $packet = $this->magic.$this->length.$this->unknown1.$this->devicetype.$this->serial.$this->ts.$this->token.$this->data;

        $this->checksum = md5(hex2bin($packet));

        $packet = $this->magic.$this->length.$this->unknown1.$this->devicetype.$this->serial.$this->ts.$this->checksum.$this->data;

        return $packet;

    }

    /*
        Разбор пакета по полям.
    */

    public function msgParse($msg) {

        $this->magic = substr($msg, 0, 4);
        $this->length = substr($msg, 4, 4);
        $this->unknown1 = substr($msg, 8, 8);
        $this->devicetype = substr($msg, 16, 4);
        $this->serial = substr($msg, 20, 4);
        $this->ts = substr($msg, 24, 8);
        $this->checksum = substr($msg, 32, 32);

        if ( ($this->length == '0020') && (strlen($msg)/2 == 32) ) {
            $this->setToken(substr($msg, 32, 32));
            // запомним разницу времени между устройством и сервером
            $this->timediff = hexdec($this->ts) - time();
        } else {
            $data_length = strlen($msg) - 64;
            if ($data_length > 0) {
                $this->data = substr($msg, 64, $data_length);
            }
        }

        $this->info['devicetype'] = $this->devicetype;
        $this->info['serial'] = $this->serial;
        $this->info['token'] = $this->token;

    }

    /*
        Вывод заголовка пакета.
    */

    public function printHead() {

        echo 'magic: ' . $this->magic . PHP_EOL;
        echo 'length: ' . $this->length . ' --> ' . hexdec($this->length) . ' bytes' . PHP_EOL;
        echo 'unknown1: ' . $this->unknown1 . PHP_EOL;
        echo 'devicetype: ' . $this->devicetype . PHP_EOL;
        echo 'serial: ' . $this->serial . PHP_EOL;
        echo 'ts: ' . $this->ts . ' --> ' . hexdec($this->ts) . ' seconds' . ' --> ' . date('Y-m-d H:i:s', hexdec($this->ts)) . PHP_EOL;
        echo 'timediff: ' . $this->timediff . PHP_EOL;
        echo 'checksum: ' . $this->checksum . PHP_EOL;

    }

    /*
        Вывод полей пакета и данных.
    */

    public function printPacket() {

        $this->printHead();
        echo 'data: ' . $this->data . PHP_EOL;

    }
}

6.
создаем файл html/modules/devices/SXIMIHumidifier_getHumidityStatus.php

<?php
$device_ip = $this->getProperty('deviseIp');

if (!ping($device_ip)) {
    $this->setProperty('deviseStatus', 'NOT answer from device');
    $this->setProperty('temperature', 'unknown');
    $this->setProperty('waterLevel', 'unknown');
    $this->setProperty('humidity', 'unknown');
    $this->setProperty('speedFan', 'unknown');   
    $this->setProperty('mode', 'unknown');
    $this->setProperty('led_b', 'unknown');
}
include_once(ROOT . '/modules/devices/addons/SXIMIClass/miio.class.php');
$miio_debug = false;
$dev = new miIO($device_ip, '0.0.0.0', $this->getProperty('deviseToken'), $miio_debug);
$cmd = 'get_properties';
$opt = '[{"did":"temperature","siid":3,"piid":7,"value":0},{"did":"water_level","siid":2,"piid":7,"value":0},{"did":"humidity","siid":3,"piid":9,"value":0},{"did":"speed_level","siid":7,"piid":1,"value":0},{"did":"power","siid":2,"piid":1,"value":0},{"did":"mode","siid":2,"piid":5,"value":0},{"did":"led_brightnes","siid":5,"piid":2,"value":0}]';
if ($dev->msgSendRcv($cmd, $opt, time())) {
    if ($dev->data == '') {
            $this->setProperty('deviseStatus', 'NOT answer from device');
            $this->setProperty('temperature', 'unknown');
            $this->setProperty('waterLevel', 'unknown');
            $this->setProperty('humidity', 'unknown');
            $this->setProperty('speedFan', 'unknown');
            $this->setProperty('mode', 'unknown');
            $this->setProperty('led_b', 'unknown');
    } else {
        $out=json_decode($dev->data, true);
        foreach ($out['result'] as $value) {
            if ($value['did'] == 'temperature') $this->setProperty('temperature', $value['value']);
            if ($value['did'] == 'water_level') $this->setProperty('waterLevel', $value['value']);
            if ($value['did'] == 'humidity') $this->setProperty('humidity', $value['value']);
            if ($value['did'] == 'speed_level') $this->setProperty('speedFan', $value['value']);
            if ($value['did'] == 'power') $this->setProperty('deviseStatus', $value['value']);
            if ($value['did'] == 'mode') $this->setProperty('mode', $value['value']);
            if ($value['did'] == 'led_brightnes') $this->setProperty('led_b', $value['value']);
            //DebMes($value['did'] . $value['value']); led_brightnes
        }
    }
} else {
    $this->setProperty('deviseStatus', 'NOT answer from device');
    $this->setProperty('temperature', 'unknown');
    $this->setProperty('waterLevel', 'unknown');
    $this->setProperty('humidity', 'unknown');
    $this->setProperty('speedFan', 'unknown');
    $this->setProperty('mode', 'unknown');
    $this->setProperty('led_b', 'unknown');
}

// Тут надо вызывать по таймеру этот метод
if (!$time_to_chek =  $this->getProperty('timeChek') ) $time_to_chek = 600;
SetTimeOut("Restart timer for humidity getHumidityStatus","callMethod('".$this->object_title.".getHumidityStatus');", $time_to_chek );

7.
создаем файл

html/modules/devices/SXIMIHumidifier_setHumidityLed.php

<?php

include_once(ROOT . '/modules/devices/addons/SXIMIClass/miio.class.php');
$miio_debug = false;
//DebMes($this->getProperty('deviseIp'));
//DebMes($this->getProperty('deviseToken'));
$dev = new miIO($this->getProperty('deviseIp'), '0.0.0.0', $this->getProperty('deviseToken'), $miio_debug);
$cmd = 'set_properties';
$opt = '[{"did":"led_brightnes","siid":5,"piid":2,"value":'.$this->getProperty('led_btarget').'}]';
//DebMes('OPT = '.$opt);
if ($dev->msgSendRcv($cmd, $opt, time())) {
    if ($dev->data == '') $info = 'Результат выполнения команды не получен. Вероятно, указан неверный токен.';
    else $info = $dev->data;
//debmes($dev->data);
}
cm($this->object_title.'.getHumidityStatus');

8.
создаем файл
html/modules/devices/SXIMIHumidifier_setHumidityPower.php

<?php

include_once(ROOT . '/modules/devices/addons/SXIMIClass/miio.class.php');
$miio_debug = false;
//DebMes($this->getProperty('deviseIp'));

$dev = new miIO($this->getProperty('deviseIp'), '0.0.0.0', $this->getProperty('deviseToken'), $miio_debug);
$cmd = 'set_properties';
$opt = '[{"did":"power","siid":2,"piid":1,"value":'.$this->getProperty("targetdeviseStatus") . '}]';
if ($dev->msgSendRcv($cmd, $opt, time())) {
    if ($dev->data == '') $info = 'Результат выполнения команды не получен. Вероятно, указан неверный токен.';
    else $info = $dev->data;
//debmes($dev->data);
}
cm($this->object_title.'.getHumidityStatus');

9.
создаем файл
html/modules/devices/SXIMIHumidifier_setHumidityTarget.php

<?php

include_once(ROOT . '/modules/devices/addons/SXIMIClass/miio.class.php');
$miio_debug = false;
//DebMes($this->getProperty('deviseIp'));
//DebMes($this->getProperty('deviseToken'));
$dev = new miIO($this->getProperty('deviseIp'), '0.0.0.0', $this->getProperty('deviseToken'), $miio_debug);
$cmd = 'set_properties';
$opt = '[{"did":"target_humidity","siid":2,"piid":6,"value":'.$this->getProperty("targethumidity").'}]';
//DebMes('OPT = '.$opt);
if ($dev->msgSendRcv($cmd, $opt, time())) {
    if ($dev->data == '') $info = 'Результат выполнения команды не получен. Вероятно, указан неверный токен.';
    else $info = $dev->data;
//debmes($dev->data);
}
cm($this->object_title.'.getHumidityStatus');

10.
создаем файл html/modules/devices/SXIMIHumidifier_setHumidytyMode.php

<?php

include_once(ROOT . '/modules/devices/addons/SXIMIClass/miio.class.php');
$miio_debug = false;
//DebMes($this->getProperty('deviseIp'));
//DebMes($this->getProperty('deviseToken'));
$dev = new miIO($this->getProperty('deviseIp'), '0.0.0.0', $this->getProperty('deviseToken'), $miio_debug);
$cmd = 'set_properties';
$opt = '[{"did":"mode","siid":2,"piid":5,"value":'.$this->getProperty("targetmode").'}]';
//DebMes('OPT = '.$opt);
if ($dev->msgSendRcv($cmd, $opt, time())) {
    if ($dev->data == '') $info = 'Результат выполнения команды не получен. Вероятно, указан неверный токен.';
    else $info = $dev->data;
//debmes($dev->data);
}
cm($this->object_title.'.getHumidityStatus');

11.
сылка на файлы https://drive.google.com/file/d/1vt_cI8osAHH7zkBZT...

Discuss (2) (5)

See also:
2021-08-09 Где мой телефон
2021-04-09 Запуск и выключение компьютера с помощью Majordomo
2021-03-24 Bad gateway 504
2021-02-15 ИНФОРМЕР ПОГОДЫ НА САЙТ
2021-02-07 Запрос в телегу о состояние аккумулятора устройств
2021-01-11 Отправка и удаление старых сообщений в телеграмм
2020-12-15 Как сканировать BLE устройства и Bluetooth в модуле Устройства Online

Киев, Украина