При подключении Увлажнитель воздуха 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
Потраченное время и весь код , создание отдельного ПУ, ПОЛНОСТЬЮ на нем, от меня только пожелания , доступ к оборудованию))
Ну что ж по пунктам.
Создаем файл по пути(у меня линукс 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% °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...
Киев, Украина