Skip to content

Commit ed74ea4

Browse files
committed
use socket and promise
1 parent 4244cb3 commit ed74ea4

File tree

8 files changed

+624
-443
lines changed

8 files changed

+624
-443
lines changed

README.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ With this package you can control Xiaomi Mi Home devices that implement the miIO
33
Mi Robot Vacuum and other Smart Home devices.
44
These devices are commonly part of what Xiaomi calls the Mi Ecosystem which is branded as MiJia.
55

6-
# Example
6+
# Example for Laravel
77
Get device info, status and start cleaning of mi robot vacuum
88

9-
$device = new Device();
9+
$miio = app(MiIO::class);
1010

11-
$device
12-
->setDeviceName('mirobot_vacuum')
13-
->setToken('00112233445566778899aabbccddeeff');
14-
15-
$miio = new MiIO();
11+
$device = $miio->createDevice('mirobot_vacuum', '00112233445566778899aabbccddeeff');
1612

17-
$deviceInfoResult = $miio->getInfo($device);
18-
19-
$statusResult = $miio->send($device, 'get_status');
13+
$miio->send($device, 'get_status');
14+
$miio->read($device)->done(function($response) {
15+
$response->getResult();
16+
});
2017

2118
$miio->send($device, 'app_start'); // start cleaning
2219

composer.json

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
{
2-
"name": "ggottwald/miio",
3-
"description": "Control Xiaomi Mi Home devices",
4-
"type": "library",
5-
"require": {
6-
"php": ">=7.0"
7-
},
8-
"license": "GNU AFFERO GENERAL PUBLIC LICENSE",
9-
"authors": [
10-
{
11-
"name": "Götz Gottwald",
12-
"email": "goetz.gottwald@gmx.net"
13-
}
14-
],
15-
"autoload": {
16-
"psr-4": {
17-
"MiIO\\": "src/"
18-
}
2+
"name": "ggottwald/miio",
3+
"description": "Control Xiaomi Mi Home devices",
4+
"type": "library",
5+
"require": {
6+
"php": ">=7.0",
7+
"clue/socket-raw": "~1.2",
8+
"react/promise": "^2.5"
9+
},
10+
"license": "GNU AFFERO GENERAL PUBLIC LICENSE",
11+
"authors": [
12+
{
13+
"name": "Götz Gottwald",
14+
"email": "goetz.gottwald@gmx.net"
1915
}
16+
],
17+
"autoload": {
18+
"psr-4": {
19+
"MiIO\\": "src/"
20+
}
21+
}
2022
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace MiIO\Exceptions;
4+
5+
class DeviceCommandException extends Exception
6+
{
7+
/**
8+
* @var int
9+
*/
10+
private $deviceId;
11+
12+
/**
13+
* DeviceCommandException constructor.
14+
*
15+
* @param string $message
16+
* @param int $code
17+
* @param int $deviceId
18+
*/
19+
public function __construct(string $message, int $code, int $deviceId)
20+
{
21+
parent::__construct($message, $code);
22+
$this->deviceId = $deviceId;
23+
}
24+
25+
/**
26+
* @return int
27+
*/
28+
public function getDeviceId(): int
29+
{
30+
return $this->deviceId;
31+
}
32+
}

src/Exceptions/Exception.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace MiIO\Exceptions;
4+
5+
class Exception extends \Exception
6+
{
7+
8+
}

src/MiIO.php

Lines changed: 71 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
use MiIO\Models\Device;
66
use MiIO\Models\Packet;
77
use MiIO\Models\Request;
8+
use MiIO\Models\Response;
9+
use React\Promise\Promise;
10+
use Socket\Raw\Factory;
811

912
/**
1013
* Class MiIO
@@ -15,19 +18,37 @@ class MiIO
1518
{
1619
const CACHE_KEY = 'MiIO';
1720

18-
const PORT = 54321;
21+
const INFO = 'miIO.info';
1922

20-
const TIMEOUT = 5;
23+
/**
24+
* @var Factory
25+
*/
26+
protected $socketFactory;
2127

22-
const INFO = 'miIO.info';
28+
public function __construct(Factory $socketFactory)
29+
{
30+
$this->socketFactory = $socketFactory;
31+
}
32+
33+
/**
34+
* @param string $deviceName
35+
* @param string $token
36+
* @return Device
37+
*/
38+
public function createDevice(string $deviceName, string $token)
39+
{
40+
return new Device($this->socketFactory->createUdp4(), $deviceName, $token);
41+
}
2342

2443
/**
2544
* @param Device $device
26-
* @return array
45+
* @return Promise
2746
*/
2847
public function getInfo(Device $device)
2948
{
30-
return $this->send($device, static::INFO);
49+
$this->send($device, static::INFO);
50+
51+
return $this->read($device);
3152
}
3253

3354
/**
@@ -36,77 +57,51 @@ public function getInfo(Device $device)
3657
*/
3758
private function init(Device &$device)
3859
{
39-
if (strlen($device->getIpAddress())) {
40-
$packet = new Packet();
41-
$helo = $packet->getHelo();
42-
43-
$start = time();
44-
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
45-
46-
while (time() < ($start + 10)) {
47-
$response = $this->getSocketResponse($device->getIpAddress(), $socket, $helo);
48-
49-
if (!empty($response)) {
50-
$response = bin2hex($response);
51-
}
52-
53-
if (!empty($response)) {
54-
$packet = new Packet($response);
55-
if ($packet->getDeviceType()
56-
&& $packet->getSerial()) {
57-
$device->setDeviceType($packet->getDeviceType());
58-
$device->setSerial($packet->getSerial());
59-
$device->setTimeDelta(hexdec($packet->getTimestamp()) - time());
60-
socket_close($socket);
61-
62-
return $device;
63-
}
64-
}
65-
}
66-
if ($socket) {
67-
socket_close($socket);
60+
$packet = new Packet();
61+
$helo = $packet->getHelo();
62+
63+
$device->send($helo);
64+
$response = $device->read();
65+
66+
if (!empty($response)) {
67+
$response = bin2hex($response);
68+
}
69+
70+
if (!empty($response)) {
71+
72+
$packet = new Packet($response);
73+
74+
if ($packet->getDeviceType()
75+
&& $packet->getSerial()) {
76+
77+
$device->setDeviceType($packet->getDeviceType());
78+
$device->setSerial($packet->getSerial());
79+
$device->setTimeDelta(hexdec($packet->getTimestamp()) - time());
6880
}
6981
}
7082

71-
return null;
83+
return $device;
7284
}
7385

7486
/**
7587
* @param Device $device
7688
* @param string $command
7789
* @param array $params
78-
* @return array
7990
*/
8091
public function send(Device $device, $command, $params = [])
8192
{
8293
if (!$device->isInitialized()) {
8394
$this->init($device);
8495
}
8596

86-
if (!$device->isInitialized()) {
87-
return [];
88-
}
97+
$cacheKey = static::CACHE_KEY . $device->getIpAddress();
98+
$requestId = \Cache::increment($cacheKey);
8999

90100
$request = new Request();
91101
$request
92102
->setMethod($command)
93-
->setParams($params);
94-
95-
return $this->getResponse($device, $request);
96-
}
97-
98-
/**
99-
* @param Device $device
100-
* @param Request $request
101-
* @return array
102-
*/
103-
public function getResponse(Device $device, Request $request)
104-
{
105-
$cacheKey = static::CACHE_KEY . $device->getIpAddress();
106-
$requestId = (int)\Cache::get($cacheKey);
107-
\Cache::forever($cacheKey, $requestId + 1);
108-
109-
$request->setId($requestId);
103+
->setParams($params)
104+
->setId($requestId);
110105

111106
$data = $device->encrypt($request);
112107

@@ -115,73 +110,35 @@ public function getResponse(Device $device, Request $request)
115110
->setData($data)
116111
->setDevice($device);
117112

118-
$start = time();
119-
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
120-
121-
while (time() < ($start + 10)) {
122-
$packet->setTimestamp($device->getTimeDelta() + time());
123-
$response = $this->getSocketResponse($device->getIpAddress(), $socket, (string)$packet);
124-
125-
if (!empty($response)) {
126-
$response = bin2hex($response);
127-
}
128-
129-
$result = $this->decrypt($device, $response);
130-
131-
if (strlen($result)) {
132-
$response = json_decode(preg_replace('/[\x00-\x1F\x7F]/', '', $result), true);
133-
134-
if (!empty($response['id']) && $response['id'] === $requestId) {
135-
socket_close($socket);
136-
137-
return $response;
138-
}
139-
}
140-
}
141-
142-
return [];
113+
$device->send((string)$packet);
143114
}
144115

145116
/**
146-
* @param string $ip
147-
* @param \Resource $socket
148-
* @param string $data
149-
* @return string|null
117+
* @param Device $device
118+
* @return Promise
150119
*/
151-
private function getSocketResponse($ip, $socket, $data)
120+
public function read(Device $device)
152121
{
153-
if (ctype_xdigit($data)) {
154-
$data = hex2bin($data);
155-
}
156-
157-
$buf = null;
122+
return new Promise(function (callable $resolve, callable $reject) use ($device) {
123+
$buf = $device->read();
158124

159-
try {
160-
socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
161-
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 5, 'usec' => 0]);
125+
if (!empty($buf)) {
126+
$buf = bin2hex($buf);
127+
}
162128

163-
socket_sendto($socket, $data, strlen($data), 0, $ip, static::PORT);
129+
$packet = new Packet($buf);
130+
$result = $device->decrypt($packet->getData());
164131

165-
socket_recvfrom($socket, $buf, 1024, 0, $name, $port);
166-
} catch (\Throwable $e) {
167-
}
132+
$response = new Response(
133+
json_decode(preg_replace('/[\x00-\x1F\x7F]/', '', $result), true)
134+
);
168135

169-
return $buf;
170-
}
136+
if ($response->isSuccess()) {
137+
$resolve($response);
171138

172-
/**
173-
* @param Device $device
174-
* @param string $response
175-
* @return string
176-
*/
177-
private function decrypt(Device $device, $response)
178-
{
179-
if (!empty($response)) {
180-
$packet = new Packet($response);
181-
182-
return $device->decrypt($packet->getData());
183-
}
184-
185-
return '';
139+
return;
140+
}
141+
$reject($response->getException());
142+
});
186143
}
187144
}

0 commit comments

Comments
 (0)