1 #include "stdafx.h"
2 #include "../hardware/hardwaretypes.h"
3 #include "../main/localtime_r.h"
4 #include "../main/Logger.h"
5 #include "../main/mainworker.h"
6 #include "../main/Helper.h"
7 #include "../main/SQLHelper.h"
8 #include "../main/WebServer.h"
9 #include "../webserver/cWebem.h"
10 #include "../main/json_helper.h"
11 #include "XiaomiGateway.h"
12 #include "XiaomiHardware.h"
13 #include <openssl/aes.h>
14 #include <boost/asio.hpp>
15 #include <boost/bind.hpp>
16
17 #ifndef WIN32
18 #include <ifaddrs.h>
19 #endif
20
21 /*
22 Xiaomi (Aqara) makes a smart home gateway/hub that has support
23 for a variety of Xiaomi sensors.
24 They can be purchased on AliExpress or other stores at very
25 competitive prices.
26 Protocol is Zigbee and WiFi, and the gateway and
27 Domoticz need to be in the same network/subnet with multicast working
28 */
29
30 #define round(a) ( int ) ( a + .5 )
31 // Removing this vector and use unitcode to tell what kind of device each is
32 //std::vector<std::string> arrAqara_Wired_ID;
33
34 std::list<XiaomiGateway*> gatewaylist;
35 std::mutex gatewaylist_mutex;
36
GatewayByIp(std::string ip)37 XiaomiGateway * XiaomiGateway::GatewayByIp(std::string ip)
38 {
39 XiaomiGateway * ret = NULL;
40 {
41 std::unique_lock<std::mutex> lock(gatewaylist_mutex);
42 std::list<XiaomiGateway*>::iterator it = gatewaylist.begin();
43 for (; it != gatewaylist.end(); it++)
44 {
45 if ((*it)->GetGatewayIp() == ip)
46 {
47 ret = (*it);
48 break;
49 };
50 };
51 }
52 return ret;
53 }
54
AddGatewayToList()55 void XiaomiGateway::AddGatewayToList()
56 {
57 XiaomiGateway * maingw = NULL;
58 {
59 std::unique_lock<std::mutex> lock(gatewaylist_mutex);
60 std::list<XiaomiGateway*>::iterator it = gatewaylist.begin();
61 for (; it != gatewaylist.end(); it++)
62 {
63 if ((*it)->IsMainGateway())
64 {
65 maingw = (*it);
66 break;
67 };
68 };
69
70 if (!maingw)
71 {
72 SetAsMainGateway();
73 }
74 else
75 {
76 maingw->UnSetMainGateway();
77 }
78
79 gatewaylist.push_back(this);
80 }
81
82 if (maingw)
83 {
84 maingw->Restart();
85 }
86 }
87
RemoveFromGatewayList()88 void XiaomiGateway::RemoveFromGatewayList()
89 {
90 XiaomiGateway * maingw = NULL;
91 {
92 std::unique_lock<std::mutex> lock(gatewaylist_mutex);
93 gatewaylist.remove(this);
94 if (IsMainGateway())
95 {
96 UnSetMainGateway();
97
98 if (gatewaylist.begin() != gatewaylist.end())
99 {
100 std::list<XiaomiGateway*>::iterator it = gatewaylist.begin();
101 maingw = (*it);
102 }
103 }
104 }
105
106 if (maingw)
107 {
108 maingw->Restart();
109 }
110 }
111
112 // Use this function to get local ip addresses via getifaddrs when Boost.Asio approach fails
113 // Adds the addresses found to the supplied vector and returns the count
114 // Code from Stack Overflow - https://stackoverflow.com/questions/2146191
get_local_ipaddr(std::vector<std::string> & ip_addrs)115 int XiaomiGateway::get_local_ipaddr(std::vector<std::string>& ip_addrs)
116 {
117 #ifdef WIN32
118 return 0;
119 #else
120 struct ifaddrs *myaddrs, *ifa;
121 void *in_addr;
122 char buf[64];
123 int count = 0;
124
125 if (getifaddrs(&myaddrs) != 0)
126 {
127 _log.Log(LOG_ERROR, "getifaddrs failed! (when trying to determine local ip address)");
128 perror("getifaddrs");
129 return 0;
130 }
131
132 for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next)
133 {
134 if (ifa->ifa_addr == NULL)
135 continue;
136 if (!(ifa->ifa_flags & IFF_UP))
137 continue;
138
139 switch (ifa->ifa_addr->sa_family)
140 {
141 case AF_INET:
142 {
143 struct sockaddr_in *s4 = (struct sockaddr_in *)ifa->ifa_addr;
144 in_addr = &s4->sin_addr;
145 break;
146 }
147
148 case AF_INET6:
149 {
150 struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ifa->ifa_addr;
151 in_addr = &s6->sin6_addr;
152 break;
153 }
154
155 default:
156 continue;
157 }
158
159 if (!inet_ntop(ifa->ifa_addr->sa_family, in_addr, buf, sizeof(buf)))
160 {
161 _log.Log(LOG_ERROR, "Could not convert to IP address, inet_ntop failed for interface %s", ifa->ifa_name);
162 }
163 else
164 {
165 ip_addrs.push_back(buf);
166 count++;
167 }
168 }
169
170 freeifaddrs(myaddrs);
171 return count;
172 #endif
173 }
174
XiaomiGateway(const int ID)175 XiaomiGateway::XiaomiGateway(const int ID)
176 {
177 m_HwdID = ID;
178 m_bDoRestart = false;
179 m_ListenPort9898 = false;
180 }
181
~XiaomiGateway(void)182 XiaomiGateway::~XiaomiGateway(void)
183 {
184 }
185
WriteToHardware(const char * pdata,const unsigned char length)186 bool XiaomiGateway::WriteToHardware(const char * pdata, const unsigned char length)
187 {
188 const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata);
189 unsigned char packettype = pCmd->ICMND.packettype;
190 unsigned char subtype = pCmd->ICMND.subtype;
191 bool result = true;
192 std::string message = "";
193
194 if (m_GatewaySID == "") {
195 m_GatewaySID = XiaomiGatewayTokenManager::GetInstance().GetSID(m_GatewayIp);
196 }
197
198 if (packettype == pTypeGeneralSwitch) {
199 _tGeneralSwitch *xcmd = (_tGeneralSwitch*)pdata;
200
201 char szTmp[50];
202 sprintf(szTmp, "%08X", (unsigned int)xcmd->id);
203 std::string ID = szTmp;
204 std::stringstream s_strid2;
205 s_strid2 << std::hex << ID;
206 std::string sid = s_strid2.str();
207 std::transform(sid.begin(), sid.end(), sid.begin(), ::tolower);
208 std::string cmdchannel = "";
209 std::string cmdcommand = "";
210 std::string cmddevice = "";
211 std::string sidtemp = sid;
212 sidtemp.insert(0, "158d00");
213
214 cmdchannel = DetermineChannel(xcmd->unitcode);
215 cmddevice = DetermineDevice(xcmd->unitcode);
216 cmdcommand = DetermineCommand(xcmd->cmnd);
217
218 if (xcmd->unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_SINGLE || xcmd->unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_0 ||
219 xcmd->unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_1 || ((xcmd->subtype == sSwitchGeneralSwitch) && (xcmd->unitcode == XiaomiUnitCode::ACT_ONOFF_PLUG))) {
220 message = "{\"cmd\":\"write\",\"model\":\"" + cmddevice + "\",\"sid\":\"158d00" + sid + "\",\"short_id\":0,\"data\":\"{\\\"" + cmdchannel + "\\\":\\\"" + cmdcommand + "\\\",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
221 }
222 else if ((xcmd->subtype == sSwitchTypeSelector) && (xcmd->unitcode >= XiaomiUnitCode::GATEWAY_SOUND_ALARM_RINGTONE && xcmd->unitcode <= XiaomiUnitCode::GATEWAY_SOUND_DOORBELL) || (xcmd->subtype == sSwitchGeneralSwitch) && (xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_MP3)) {
223 std::stringstream ss;
224 if (xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_MP3) {
225 if (xcmd->cmnd == 1) {
226 std::vector<std::vector<std::string> > result;
227 result = m_sql.safe_query("SELECT Value FROM UserVariables WHERE (Name == 'XiaomiMP3')");
228 ss << result[0][0];
229 }
230 else {
231 ss << "10000";
232 }
233 }
234 else {
235 int level = xcmd->level;
236 if (level == 0) { level = 10000; }
237 else {
238 if (xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_ALARM_RINGTONE) {
239 if (level > 0) { level = (level / 10) - 1; }
240 }
241 else if (xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_ALARM_CLOCK) {
242 if (level > 0) { level = (level / 10) + 19; }
243 }
244 else if (xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_DOORBELL) {
245 if (level > 0) { level = (level / 10) + 9; }
246 }
247 }
248 ss << level;
249 }
250 m_GatewayMusicId = ss.str();
251 //sid.insert(0, m_GatewayPrefix);
252 message = "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" + m_GatewaySID + "\",\"short_id\":0,\"data\":\"{\\\"mid\\\":" + m_GatewayMusicId.c_str() + ",\\\"vol\\\":" + m_GatewayVolume.c_str() + ",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
253 }
254 else if (xcmd->subtype == sSwitchGeneralSwitch && xcmd->unitcode == XiaomiUnitCode::GATEWAY_SOUND_VOLUME_CONTROL) {
255 m_GatewayVolume = std::to_string(xcmd->level);
256 //sid.insert(0, m_GatewayPrefix);
257 message = "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" + m_GatewaySID + "\",\"short_id\":0,\"data\":\"{\\\"mid\\\":" + m_GatewayMusicId.c_str() + ",\\\"vol\\\":" + m_GatewayVolume.c_str() + ",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
258 }
259 else if (xcmd->subtype == sSwitchBlindsT2) {
260 int level = xcmd->level;
261 if (xcmd->cmnd == 1) {
262 level = 100;
263 }
264 message = "{\"cmd\":\"write\",\"model\":\"curtain\",\"sid\":\"158d00" + sid + "\",\"short_id\":9844,\"data\":\"{\\\"curtain_level\\\":\\\"" + std::to_string(level) + "\\\",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
265 }
266 }
267 else if (packettype == pTypeColorSwitch) {
268 // Gateway RGB Controller
269 const _tColorSwitch *xcmd = reinterpret_cast<const _tColorSwitch*>(pdata);
270
271 if (xcmd->command == Color_LedOn) {
272 m_GatewayBrightnessInt = 100;
273 message = "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" + m_GatewaySID + "\",\"short_id\":0,\"data\":\"{\\\"rgb\\\":4294967295,\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
274 }
275 else if (xcmd->command == Color_LedOff) {
276 m_GatewayBrightnessInt = 0;
277 message = "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" + m_GatewaySID + "\",\"short_id\":0,\"data\":\"{\\\"rgb\\\":0,\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
278 }
279 else if (xcmd->command == Color_SetColor) {
280 if (xcmd->color.mode == ColorModeRGB)
281 {
282 m_GatewayRgbR = xcmd->color.r;
283 m_GatewayRgbG = xcmd->color.g;
284 m_GatewayRgbB = xcmd->color.b;
285 m_GatewayBrightnessInt = xcmd->value; //TODO: What is the valid range for XiaomiGateway, 0..100 or 0..255?
286
287 uint32_t value = (m_GatewayBrightnessInt << 24) | (m_GatewayRgbR << 16) | (m_GatewayRgbG << 8) | (m_GatewayRgbB);
288
289 std::stringstream ss;
290 ss << "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" << m_GatewaySID << "\",\"short_id\":0,\"data\":\"{\\\"rgb\\\":" << value << ",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
291 message = ss.str();
292 }
293 else
294 {
295 _log.Log(LOG_STATUS, "XiaomiGateway: SetRGBColour - Color mode '%d' is unhandled, if you have a suggestion for what it should do, please post on the Domoticz forum", xcmd->color.mode);
296 }
297 }
298 else if ((xcmd->command == Color_SetBrightnessLevel) || (xcmd->command == Color_SetBrightUp) || (xcmd->command == Color_SetBrightDown)) {
299 // Add the brightness
300 if (xcmd->command == Color_SetBrightUp) {
301 //m_GatewayBrightnessInt = std::min(m_GatewayBrightnessInt + 10, 100);
302 }
303 else if (xcmd->command == Color_SetBrightDown) {
304 //m_GatewayBrightnessInt = std::max(m_GatewayBrightnessInt - 10, 0);
305 }
306 else {
307 m_GatewayBrightnessInt = (int)xcmd->value; //TODO: What is the valid range for XiaomiGateway, 0..100 or 0..255?
308 }
309
310 uint32_t value = (m_GatewayBrightnessInt << 24) | (m_GatewayRgbR << 16) | (m_GatewayRgbG << 8) | (m_GatewayRgbB);
311
312 std::stringstream ss;
313 ss << "{\"cmd\":\"write\",\"model\":\"gateway\",\"sid\":\"" << m_GatewaySID << "\",\"short_id\":0,\"data\":\"{\\\"rgb\\\":" << value << ",\\\"key\\\":\\\"@gatewaykey\\\"}\" }";
314 message = ss.str();
315 }
316 else if (xcmd->command == Color_SetColorToWhite) {
317 // Ignore Color_SetColorToWhite
318 }
319 else {
320 _log.Log(LOG_ERROR, "XiaomiGateway: Unknown command %d", xcmd->command);
321 }
322 }
323 if (!message.empty()) {
324 _log.Debug(DEBUG_HARDWARE, "XiaomiGateway: message: '%s'", message.c_str());
325 result = SendMessageToGateway(message);
326 if (result == false) {
327 // Retry, send the message again
328 _log.Log(LOG_STATUS, "XiaomiGateway: SendMessageToGateway failed on first attempt, will try again");
329 sleep_milliseconds(100);
330 result = SendMessageToGateway(message);
331 }
332 }
333 return result;
334 }
335
SendMessageToGateway(const std::string & controlmessage)336 bool XiaomiGateway::SendMessageToGateway(const std::string &controlmessage) {
337 std::string message = controlmessage;
338 bool result = true;
339 boost::asio::io_service io_service;
340 boost::asio::ip::udp::socket socket_(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0));
341 stdreplace(message, "@gatewaykey", GetGatewayKey());
342 std::shared_ptr<std::string> message1(new std::string(message));
343 boost::asio::ip::udp::endpoint remote_endpoint_;
344 remote_endpoint_ = boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string(m_GatewayIp), 9898);
345 socket_.send_to(boost::asio::buffer(*message1), remote_endpoint_);
346 sleep_milliseconds(150);
347 boost::array<char, 512> recv_buffer_;
348 memset(&recv_buffer_[0], 0, sizeof(recv_buffer_));
349 #ifdef _DEBUG
350 _log.Log(LOG_STATUS, "XiaomiGateway: request to %s - %s", m_GatewayIp.c_str(), message.c_str());
351 #endif
352 while (socket_.available() > 0) {
353 socket_.receive_from(boost::asio::buffer(recv_buffer_), remote_endpoint_);
354 std::string receivedString(recv_buffer_.data());
355
356 Json::Value root;
357 bool ret = ParseJSon(receivedString, root);
358 if ((ret) && (root.isObject()))
359 {
360 std::string data = root["data"].asString();
361 Json::Value root2;
362 ret = ParseJSon(data.c_str(), root2);
363 if ((ret) && (root2.isObject()))
364 {
365 std::string error = root2["error"].asString();
366 if (error != "") {
367 _log.Log(LOG_ERROR, "XiaomiGateway: unable to write command - %s", error.c_str());
368 result = false;
369 }
370 }
371 }
372
373 #ifdef _DEBUG
374 _log.Log(LOG_STATUS, "XiaomiGateway: response %s", receivedString.c_str());
375 #endif
376 }
377 socket_.close();
378 return result;
379 }
380
InsertUpdateTemperature(const std::string & nodeid,const std::string & Name,const float Temperature,const int battery)381 void XiaomiGateway::InsertUpdateTemperature(const std::string &nodeid, const std::string &Name, const float Temperature, const int battery)
382 {
383 unsigned int sID = GetShortID(nodeid);
384 if (sID > 0) {
385 SendTempSensor(sID, battery, Temperature, Name);
386 }
387 }
388
InsertUpdateHumidity(const std::string & nodeid,const std::string & Name,const int Humidity,const int battery)389 void XiaomiGateway::InsertUpdateHumidity(const std::string &nodeid, const std::string &Name, const int Humidity, const int battery)
390 {
391 unsigned int sID = GetShortID(nodeid);
392 if (sID > 0) {
393 SendHumiditySensor(sID, battery, Humidity, Name);
394 }
395 }
396
InsertUpdatePressure(const std::string & nodeid,const std::string & Name,const float Pressure,const int battery)397 void XiaomiGateway::InsertUpdatePressure(const std::string &nodeid, const std::string &Name, const float Pressure, const int battery)
398 {
399 unsigned int sID = GetShortID(nodeid);
400 if (sID > 0) {
401 SendPressureSensor(sID, 1, battery, Pressure, Name);
402 }
403 }
404
InsertUpdateTempHumPressure(const std::string & nodeid,const std::string & Name,const float Temperature,const int Humidity,const float Pressure,const int battery)405 void XiaomiGateway::InsertUpdateTempHumPressure(const std::string &nodeid, const std::string &Name, const float Temperature, const int Humidity, const float Pressure, const int battery)
406 {
407 unsigned int sID = GetShortID(nodeid);
408 int barometric_forcast = baroForecastNoInfo;
409 if (Pressure < 1000)
410 barometric_forcast = baroForecastRain;
411 else if (Pressure < 1020)
412 barometric_forcast = baroForecastCloudy;
413 else if (Pressure < 1030)
414 barometric_forcast = baroForecastPartlyCloudy;
415 else
416 barometric_forcast = baroForecastSunny;
417
418 if (sID > 0) {
419 SendTempHumBaroSensor(sID, battery, Temperature, Humidity, Pressure, barometric_forcast, Name);
420 }
421 }
422
InsertUpdateTempHum(const std::string & nodeid,const std::string & Name,const float Temperature,const int Humidity,const int battery)423 void XiaomiGateway::InsertUpdateTempHum(const std::string &nodeid, const std::string &Name, const float Temperature, const int Humidity, const int battery)
424 {
425 unsigned int sID = GetShortID(nodeid);
426 if (sID > 0) {
427 SendTempHumSensor(sID, battery, Temperature, Humidity, Name);
428 }
429 }
430
InsertUpdateRGBGateway(const std::string & nodeid,const std::string & Name,const bool bIsOn,const int brightness,const int hue)431 void XiaomiGateway::InsertUpdateRGBGateway(const std::string & nodeid, const std::string & Name, const bool bIsOn, const int brightness, const int hue)
432 {
433 if (nodeid.length() < 12) {
434 _log.Log(LOG_ERROR, "XiaomiGateway: Node ID %s is too short", nodeid.c_str());
435 return;
436 }
437 std::string str = nodeid.substr(4, 8);
438 unsigned int sID;
439 std::stringstream ss;
440 ss << std::hex << str.c_str();
441 ss >> sID;
442
443 char szDeviceID[300];
444 if (sID == 1)
445 sprintf(szDeviceID, "%d", 1);
446 else
447 sprintf(szDeviceID, "%08X", (unsigned int)sID);
448
449 int lastLevel = 0;
450 int nvalue = 0;
451 bool tIsOn = !(bIsOn);
452 std::vector<std::vector<std::string> > result;
453 result = m_sql.safe_query("SELECT nValue, LastLevel FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q') AND (Type==%d) AND (SubType==%d)", m_HwdID, szDeviceID, pTypeColorSwitch, sTypeColor_RGB_W);
454
455 if (result.empty())
456 {
457 _log.Log(LOG_STATUS, "XiaomiGateway: New Gateway Found (%s/%s)", str.c_str(), Name.c_str());
458 //int value = atoi(brightness.c_str());
459 //int value = hue; // atoi(hue.c_str());
460 int cmd = Color_LedOn;
461 if (!bIsOn) {
462 cmd = Color_LedOff;
463 }
464 _tColorSwitch ycmd;
465 ycmd.subtype = sTypeColor_RGB_W;
466 ycmd.id = sID;
467 //ycmd.dunit = 0;
468 ycmd.value = brightness;
469 ycmd.command = cmd;
470 m_mainworker.PushAndWaitRxMessage(this, (const unsigned char *)&ycmd, NULL, -1);
471 m_sql.safe_query("UPDATE DeviceStatus SET Name='%q', SwitchType=%d, LastLevel=%d WHERE(HardwareID == %d) AND (DeviceID == '%s') AND (Type == %d)", Name.c_str(), (STYPE_Dimmer), brightness, m_HwdID, szDeviceID, pTypeColorSwitch);
472 }
473 else {
474 nvalue = atoi(result[0][0].c_str());
475 tIsOn = (nvalue != 0);
476 lastLevel = atoi(result[0][1].c_str());
477 //int value = atoi(brightness.c_str());
478 if ((bIsOn != tIsOn) || (brightness != lastLevel))
479 {
480 int cmd = Color_LedOn;
481 if (!bIsOn) {
482 cmd = Color_LedOff;
483 }
484 _tColorSwitch ycmd;
485 ycmd.subtype = sTypeColor_RGB_W;
486 ycmd.id = sID;
487 //ycmd.dunit = 0;
488 ycmd.value = brightness;
489 ycmd.command = cmd;
490 m_mainworker.PushAndWaitRxMessage(this, (const unsigned char *)&ycmd, NULL, -1);
491 }
492 }
493 }
494
InsertUpdateSwitch(const std::string & nodeid,const std::string & Name,const bool bIsOn,const _eSwitchType switchtype,const int unitcode,const int level,const std::string & messagetype,const std::string & load_power,const std::string & power_consumed,const int battery)495 void XiaomiGateway::InsertUpdateSwitch(const std::string &nodeid, const std::string &Name, const bool bIsOn, const _eSwitchType switchtype, const int unitcode, const int level, const std::string &messagetype, const std::string &load_power, const std::string &power_consumed, const int battery)
496 {
497 unsigned int sID = GetShortID(nodeid);
498
499 char szTmp[300];
500 if (sID == 1)
501 sprintf(szTmp, "%d", 1);
502 else
503 sprintf(szTmp, "%08X", (unsigned int)sID);
504 std::string ID = szTmp;
505
506 _tGeneralSwitch xcmd;
507 xcmd.len = sizeof(_tGeneralSwitch) - 1;
508 xcmd.id = sID;
509 xcmd.type = pTypeGeneralSwitch;
510 xcmd.subtype = sSwitchGeneralSwitch;
511 xcmd.unitcode = unitcode;
512 int customimage = 0;
513
514 if ((xcmd.unitcode >= XiaomiUnitCode::GATEWAY_SOUND_ALARM_RINGTONE) && (xcmd.unitcode <= XiaomiUnitCode::GATEWAY_SOUND_VOLUME_CONTROL)) {
515 customimage = 8; // Speaker
516 }
517
518 if (bIsOn) {
519 xcmd.cmnd = gswitch_sOn;
520 }
521 else {
522 xcmd.cmnd = gswitch_sOff;
523 }
524 if (switchtype == STYPE_Selector) {
525 xcmd.subtype = sSwitchTypeSelector;
526 if (level > 0) {
527 xcmd.level = level;
528 }
529 }
530 else if (switchtype == STYPE_SMOKEDETECTOR) {
531 xcmd.level = level;
532 }
533 else if (switchtype == STYPE_BlindsPercentage) {
534 xcmd.level = level;
535 xcmd.subtype = sSwitchBlindsT2;
536 xcmd.cmnd = gswitch_sSetLevel;
537 }
538
539 // Check if this switch is already in the database
540 std::vector<std::vector<std::string> > result;
541
542 // Block this device if it is already added for another gateway hardware id
543 result = m_sql.safe_query("SELECT nValue FROM DeviceStatus WHERE (HardwareID!=%d) AND (DeviceID=='%q') AND (Type==%d) AND (Unit == '%d')", m_HwdID, ID.c_str(), xcmd.type, xcmd.unitcode);
544 if (!result.empty()) {
545 return;
546 }
547
548 result = m_sql.safe_query("SELECT nValue, BatteryLevel FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q') AND (Type==%d) AND (Unit == '%d')", m_HwdID, ID.c_str(), xcmd.type, xcmd.unitcode);
549 if (result.empty())
550 {
551 _log.Log(LOG_STATUS, "XiaomiGateway: New %s Found (%s)", Name.c_str(), nodeid.c_str());
552 m_mainworker.PushAndWaitRxMessage(this, (const unsigned char *)&xcmd, NULL, battery);
553 if (customimage == 0) {
554 if (switchtype == STYPE_OnOff) {
555 customimage = 1; // Wall socket
556 }
557 else if (switchtype == STYPE_Selector) {
558 customimage = 9;
559 }
560 }
561
562 m_sql.safe_query("UPDATE DeviceStatus SET Name='%q', SwitchType=%d, CustomImage=%i WHERE(HardwareID == %d) AND (DeviceID == '%q') AND (Unit == '%d')", Name.c_str(), (switchtype), customimage, m_HwdID, ID.c_str(), xcmd.unitcode);
563
564 if (switchtype == STYPE_Selector) {
565 result = m_sql.safe_query("SELECT ID FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q') AND (Type==%d) AND (Unit == '%d')", m_HwdID, ID.c_str(), xcmd.type, xcmd.unitcode);
566 if (!result.empty()) {
567 std::string Idx = result[0][0];
568 if (Name == NAME_SELECTOR_WIRELESS_SINGLE) {
569 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Click|Double Click|Long Click|Long Click Release", false));
570 }
571 else if (Name == NAME_SELECTOR_WIRELESS_SINGLE_SQUARE) {
572 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Click|Double Click", false));
573 }
574 else if (Name == NAME_SELECTOR_WIRELESS_SINGLE_SMART_PUSH) {
575 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Click|Shake", false));
576 }
577 else if (Name == NAME_SELECTOR_CUBE_V1) {
578 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|flip90|flip180|move|tap_twice|shake_air|swing|alert|free_fall|clock_wise|anti_clock_wise", false));
579 }
580 else if (Name == NAME_SELECTOR_CUBE_AQARA) {
581 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|flip90|flip180|move|tap_twice|shake_air|swing|alert|free_fall|rotate", false));
582 }
583 else if (Name == NAME_SENSOR_VIBRATION) {
584 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Tilt|Vibrate|Free Fall", false));
585 }
586 else if (Name == NAME_SELECTOR_WIRELESS_WALL_DUAL) {
587 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Switch 1|Switch 2|Both Click|Switch 1 Double Click|Switch 2 Double Click|Both Double Click|Switch 1 Long Click|Switch 2 Long Click|Both Long Click", false));
588 }
589 else if (Name == NAME_SELECTOR_WIRED_WALL_SINGLE) {
590 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Switch1 On|Switch1 Off", false));
591 }
592 else if (Name == NAME_SELECTOR_WIRELESS_WALL_SINGLE) {
593 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:0;LevelNames:Off|Click|Double Click|Long Click", false));
594 }
595 else if (Name == NAME_GATEWAY_SOUND_ALARM_RINGTONE) {
596 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:1;LevelNames:Off|Police siren 1|Police siren 2|Accident tone|Missle countdown|Ghost|Sniper|War|Air Strike|Barking dogs", false));
597 }
598 else if (Name == NAME_GATEWAY_SOUND_ALARM_CLOCK) {
599 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:1;LevelNames:Off|MiMix|Enthusiastic|GuitarClassic|IceWorldPiano|LeisureTime|Childhood|MorningStreamlet|MusicBox|Orange|Thinker", false));
600 }
601 else if (Name == NAME_GATEWAY_SOUND_DOORBELL) {
602 m_sql.SetDeviceOptions(atoi(Idx.c_str()), m_sql.BuildDeviceOptions("SelectorStyle:1;LevelNames:Off|Doorbell ring tone|Knock on door|Hilarious|Alarm clock", false));
603 }
604 }
605 }
606 else if (switchtype == STYPE_OnOff && Name == NAME_GATEWAY_SOUND_MP3) {
607 std::string errorMessage;
608 m_sql.AddUserVariable("XiaomiMP3", USERVARTYPE_INTEGER, "10001", errorMessage);
609 }
610 }
611 else {
612 int nvalue = atoi(result[0][0].c_str());
613 int BatteryLevel = atoi(result[0][1].c_str());
614
615 if (messagetype == "heartbeat") {
616 if (battery != 255) {
617 BatteryLevel = battery;
618 m_sql.safe_query("UPDATE DeviceStatus SET BatteryLevel=%d WHERE(HardwareID == %d) AND (DeviceID == '%q') AND (Unit == '%d')", BatteryLevel, m_HwdID, ID.c_str(), xcmd.unitcode);
619 }
620 }
621 else {
622 if ((bIsOn == false && nvalue >= 1) || (bIsOn == true) || (Name == NAME_SELECTOR_WIRED_WALL_DUAL) || (Name == NAME_SELECTOR_WIRED_WALL_SINGLE) || (Name == NAME_ACT_BLINDS_CURTAIN)) {
623 m_mainworker.PushAndWaitRxMessage(this, (const unsigned char *)&xcmd, NULL, BatteryLevel);
624 }
625 }
626 if ((Name == NAME_ACT_ONOFF_PLUG) || (Name == NAME_ACT_ONOFF_PLUG_WALL)) {
627 if (load_power != "" && power_consumed != "") {
628 int power = atoi(load_power.c_str());
629 int consumed = atoi(power_consumed.c_str()) / 1000;
630 SendKwhMeter(sID, 1, 255, power, consumed, "Xiaomi Smart Plug Usage");
631 }
632 }
633 }
634 }
635
InsertUpdateCubeText(const std::string & nodeid,const std::string & Name,const std::string & degrees)636 void XiaomiGateway::InsertUpdateCubeText(const std::string & nodeid, const std::string & Name, const std::string °rees)
637 {
638 unsigned int sID = GetShortID(nodeid);
639 if (sID > 0) {
640 SendTextSensor(sID, sID, 255, degrees.c_str(), Name);
641 }
642 }
643
InsertUpdateVoltage(const std::string & nodeid,const std::string & Name,const int VoltageLevel)644 void XiaomiGateway::InsertUpdateVoltage(const std::string & nodeid, const std::string & Name, const int VoltageLevel)
645 {
646 if (VoltageLevel < 3600) {
647 unsigned int sID = GetShortID(nodeid);
648 if (sID > 0) {
649 int percent = ((VoltageLevel - 2200) / 10);
650 float voltage = (float)VoltageLevel / 1000;
651 SendVoltageSensor(sID, sID, percent, voltage, "Xiaomi Voltage");
652 }
653 }
654 }
655
InsertUpdateLux(const std::string & nodeid,const std::string & Name,const int Illumination,const int battery)656 void XiaomiGateway::InsertUpdateLux(const std::string & nodeid, const std::string & Name, const int Illumination, const int battery)
657 {
658 unsigned int sID = GetShortID(nodeid);
659 if (sID > 0) {
660 float lux = (float)Illumination;
661 SendLuxSensor(sID, sID, battery, lux, Name);
662 }
663 }
664
StartHardware()665 bool XiaomiGateway::StartHardware()
666 {
667 RequestStart();
668
669 m_bDoRestart = false;
670
671 // Force connect the next first time
672 m_bIsStarted = true;
673
674 m_GatewayMusicId = "10000";
675 m_GatewayVolume = "20";
676
677 std::vector<std::vector<std::string> > result;
678 result = m_sql.safe_query("SELECT Password, Address FROM Hardware WHERE Type=%d AND ID=%d AND Enabled=1", HTYPE_XiaomiGateway, m_HwdID);
679
680 if (result.empty())
681 return false;
682
683 m_GatewayPassword = result[0][0].c_str();
684 m_GatewayIp = result[0][1].c_str();
685
686 m_GatewayRgbR = 255;
687 m_GatewayRgbG = 255;
688 m_GatewayRgbB = 255;
689 m_GatewayBrightnessInt = 100;
690 // Check for presence of Xiaomi user variable to enable message output
691 m_OutputMessage = false;
692 result = m_sql.safe_query("SELECT Value FROM UserVariables WHERE (Name == 'XiaomiMessage')");
693 if (!result.empty()) {
694 m_OutputMessage = true;
695 }
696 // Check for presence of Xiaomi user variable to enable additional voltage devices
697 m_IncludeVoltage = false;
698 result = m_sql.safe_query("SELECT Value FROM UserVariables WHERE (Name == 'XiaomiVoltage')");
699 if (!result.empty()) {
700 m_IncludeVoltage = true;
701 }
702 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Delaying worker startup...", m_HwdID);
703 sleep_seconds(5);
704
705 XiaomiGatewayTokenManager::GetInstance();
706
707 AddGatewayToList();
708
709 if (m_ListenPort9898)
710 {
711 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Selected as main Gateway", m_HwdID);
712 }
713
714 // Start worker thread
715 m_thread = std::shared_ptr<std::thread>(new std::thread(&XiaomiGateway::Do_Work, this));
716 SetThreadNameInt(m_thread->native_handle());
717
718 return (m_thread != nullptr);
719 }
720
StopHardware()721 bool XiaomiGateway::StopHardware()
722 {
723 if (m_thread)
724 {
725 RequestStop();
726 m_thread->join();
727 m_thread.reset();
728 }
729 m_bIsStarted = false;
730 return true;
731 }
732
Do_Work()733 void XiaomiGateway::Do_Work()
734 {
735 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Worker started...", m_HwdID);
736 boost::asio::io_service io_service;
737 // Find the local ip address that is similar to the xiaomi gateway
738 try {
739 boost::asio::ip::udp::resolver resolver(io_service);
740 boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), m_GatewayIp, "");
741 boost::asio::ip::udp::resolver::iterator endpoints = resolver.resolve(query);
742 boost::asio::ip::udp::endpoint ep = *endpoints;
743 boost::asio::ip::udp::socket socket(io_service);
744 socket.connect(ep);
745 boost::asio::ip::address addr = socket.local_endpoint().address();
746 std::string compareIp = m_GatewayIp.substr(0, (m_GatewayIp.length() - 3));
747 std::size_t found = addr.to_string().find(compareIp);
748 if (found != std::string::npos) {
749 m_LocalIp = addr.to_string();
750 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Using %s for local IP address.", m_HwdID, m_LocalIp.c_str());
751 }
752 }
753 catch (std::exception& e) {
754 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Could not detect local IP address using Boost.Asio: %s", m_HwdID, e.what());
755 }
756
757 // Try finding local ip using ifaddrs when Boost.Asio fails
758 if (m_LocalIp == "") {
759 try {
760 // Get first 2 octets of Xiaomi gateway ip to search for similar ip address
761 std::string compareIp = m_GatewayIp.substr(0, (m_GatewayIp.length() - 3));
762 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): XiaomiGateway IP address starts with: %s", m_HwdID, compareIp.c_str());
763
764 std::vector<std::string> ip_addrs;
765 if (XiaomiGateway::get_local_ipaddr(ip_addrs) > 0)
766 {
767 for (const std::string &addr : ip_addrs)
768 {
769 std::size_t found = addr.find(compareIp);
770 if (found != std::string::npos)
771 {
772 m_LocalIp = addr;
773 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Using %s for local IP address.", m_HwdID, m_LocalIp.c_str());
774 break;
775 }
776 }
777 }
778 else
779 {
780 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Could not find local IP address with ifaddrs", m_HwdID);
781 }
782 }
783 catch (std::exception& e) {
784 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): Could not find local IP address with ifaddrs: %s", m_HwdID, e.what());
785 }
786 }
787
788 XiaomiGateway::xiaomi_udp_server udp_server(io_service, m_HwdID, m_GatewayIp, m_LocalIp, m_ListenPort9898, m_OutputMessage, m_IncludeVoltage, this);
789 boost::thread bt;
790 if (m_ListenPort9898) {
791 bt = boost::thread(boost::bind(&boost::asio::io_service::run, &io_service));
792 SetThreadName(bt.native_handle(), "XiaomiGatewayIO");
793 }
794
795 int sec_counter = 0;
796 while (!IsStopRequested(1000))
797 {
798 sec_counter++;
799 if (sec_counter % 12 == 0) {
800 m_LastHeartbeat = mytime(NULL);
801 }
802 if (sec_counter % 60 == 0)
803 {
804 //_log.Log(LOG_STATUS, "sec_counter %d", sec_counter);
805 }
806 }
807 io_service.stop();
808 RemoveFromGatewayList();
809 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): stopped", m_HwdID);
810 }
811
GetGatewayKey()812 std::string XiaomiGateway::GetGatewayKey()
813 {
814 #ifdef WWW_ENABLE_SSL
815 const unsigned char *key = (unsigned char *)m_GatewayPassword.c_str();
816 unsigned char iv[AES_BLOCK_SIZE] = { 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e };
817 std::string token = XiaomiGatewayTokenManager::GetInstance().GetToken(m_GatewayIp);
818 unsigned char *plaintext = (unsigned char *)token.c_str();
819 unsigned char ciphertext[128];
820
821 AES_KEY encryption_key;
822 AES_set_encrypt_key(key, 128, &(encryption_key));
823 AES_cbc_encrypt((unsigned char *)plaintext, ciphertext, sizeof(plaintext) * 8, &encryption_key, iv, AES_ENCRYPT);
824
825 char gatewaykey[128];
826 for (int i = 0; i < 16; i++)
827 {
828 sprintf(&gatewaykey[i * 2], "%02X", ciphertext[i]);
829 }
830 #ifdef _DEBUG
831 _log.Log(LOG_STATUS, "XiaomiGateway: GetGatewayKey Password - %s", m_GatewayPassword.c_str());
832 _log.Log(LOG_STATUS, "XiaomiGateway: GetGatewayKey key - %s", gatewaykey);
833 #endif
834 return gatewaykey;
835 #else
836 _log.Log(LOG_ERROR, "XiaomiGateway: GetGatewayKey NO SSL AVAILABLE");
837 return std::string("");
838 #endif
839 }
840
GetShortID(const std::string & nodeid)841 unsigned int XiaomiGateway::GetShortID(const std::string & nodeid)
842 {
843 if (nodeid.length() < 12) {
844 _log.Log(LOG_ERROR, "XiaomiGateway: Node ID %s is too short", nodeid.c_str());
845 return -1;
846 }
847 std::string str;
848 if (nodeid.length() < 14) {
849 // Gateway
850 str = nodeid.substr(4, 8);
851 }
852 else {
853 // Device
854 str = nodeid.substr(6, 8);
855 }
856 unsigned int sID;
857 std::stringstream ss;
858 ss << std::hex << str.c_str();
859 ss >> sID;
860 return sID;
861 }
862
xiaomi_udp_server(boost::asio::io_service & io_service,int m_HwdID,const std::string & gatewayIp,const std::string & localIp,const bool listenPort9898,const bool outputMessage,const bool includeVoltage,XiaomiGateway * parent)863 XiaomiGateway::xiaomi_udp_server::xiaomi_udp_server(boost::asio::io_service& io_service, int m_HwdID, const std::string &gatewayIp, const std::string &localIp, const bool listenPort9898, const bool outputMessage, const bool includeVoltage, XiaomiGateway *parent)
864 : socket_(io_service, boost::asio::ip::udp::v4())
865 {
866 m_HardwareID = m_HwdID;
867 m_XiaomiGateway = parent;
868 m_gatewayip = gatewayIp;
869 m_localip = localIp;
870 m_OutputMessage = outputMessage;
871 m_IncludeVoltage = includeVoltage;
872 if (listenPort9898) {
873 try {
874 socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
875 if (m_localip != "") {
876 boost::system::error_code ec;
877 boost::asio::ip::address listen_addr = boost::asio::ip::address::from_string(m_localip, ec);
878 boost::asio::ip::address mcast_addr = boost::asio::ip::address::from_string("224.0.0.50", ec);
879 boost::asio::ip::udp::endpoint listen_endpoint(mcast_addr, 9898);
880
881 socket_.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 9898));
882 std::shared_ptr<std::string> message(new std::string("{\"cmd\":\"whois\"}"));
883 boost::asio::ip::udp::endpoint remote_endpoint;
884 remote_endpoint = boost::asio::ip::udp::endpoint(mcast_addr, 4321);
885 socket_.send_to(boost::asio::buffer(*message), remote_endpoint);
886 socket_.set_option(boost::asio::ip::multicast::join_group(mcast_addr.to_v4(), listen_addr.to_v4()), ec);
887 socket_.bind(listen_endpoint, ec);
888 }
889 else {
890 socket_.bind(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 9898));
891 std::shared_ptr<std::string> message(new std::string("{\"cmd\":\"whois\"}"));
892 boost::asio::ip::udp::endpoint remote_endpoint;
893 remote_endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string("224.0.0.50"), 4321);
894 socket_.send_to(boost::asio::buffer(*message), remote_endpoint);
895 socket_.set_option(boost::asio::ip::multicast::join_group(boost::asio::ip::address::from_string("224.0.0.50")));
896 }
897 }
898 catch (const boost::system::system_error& ex) {
899 _log.Log(LOG_ERROR, "XiaomiGateway: %s", ex.code().category().name());
900 m_XiaomiGateway->StopHardware();
901 return;
902 }
903 start_receive();
904 }
905 else {
906 }
907 }
908
~xiaomi_udp_server()909 XiaomiGateway::xiaomi_udp_server::~xiaomi_udp_server()
910 {
911 }
912
start_receive()913 void XiaomiGateway::xiaomi_udp_server::start_receive()
914 {
915 //_log.Log(LOG_STATUS, "start_receive");
916 memset(&data_[0], 0, sizeof(data_));
917 socket_.async_receive_from(boost::asio::buffer(data_, max_length), remote_endpoint_, boost::bind(&xiaomi_udp_server::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
918 }
919
handle_receive(const boost::system::error_code & error,std::size_t bytes_recvd)920 void XiaomiGateway::xiaomi_udp_server::handle_receive(const boost::system::error_code & error, std::size_t bytes_recvd)
921 {
922 if (!error || error == boost::asio::error::message_size)
923 {
924 XiaomiGateway * TrueGateway = m_XiaomiGateway->GatewayByIp(remote_endpoint_.address().to_v4().to_string());
925
926 if (!TrueGateway)
927 {
928 _log.Log(LOG_ERROR, "XiaomiGateway: received data from unregisted gateway!");
929 start_receive();
930 return;
931 }
932 #ifdef _DEBUG
933 _log.Log(LOG_STATUS, data_);
934 #endif
935 Json::Value root;
936 bool showmessage = true;
937 bool ret = ParseJSon(data_, root);
938 if ((!ret) || (!root.isObject()))
939 {
940 _log.Log(LOG_ERROR, "XiaomiGateway: invalid data received!");
941 start_receive();
942 return;
943 }
944 else {
945 std::string cmd = root["cmd"].asString();
946 std::string model = root["model"].asString();
947 std::string sid = root["sid"].asString();
948 std::string data = root["data"].asString();
949 int unitcode = 1;
950 if ((cmd == COMMAND_REPORT) || (cmd == COMMAND_READ_ACK) || (cmd == COMMAND_HEARTBEAT))
951 {
952 Json::Value root2;
953 ret = ParseJSon(data.c_str(), root2);
954 if ((ret) || (!root2.isObject()))
955 {
956 _eSwitchType type = STYPE_END;
957 std::string name = NAME_UNKNOWN_XIAOMI;
958 if (model == MODEL_SENSOR_MOTION_XIAOMI) {
959 type = STYPE_Motion;
960 name = NAME_SENSOR_MOTION_XIAOMI;
961 }
962 else if (model == MODEL_SENSOR_MOTION_AQARA) {
963 type = STYPE_Motion;
964 name = NAME_SENSOR_MOTION_AQARA;
965 }
966 else if ((model == MODEL_SELECTOR_WIRELESS_SINGLE_1) || (model == MODEL_SELECTOR_WIRELESS_SINGLE_2)) {
967 type = STYPE_Selector;
968 name = NAME_SELECTOR_WIRELESS_SINGLE;
969 }
970 else if (model == MODEL_SELECTOR_WIRELESS_SINGLE_SQUARE) {
971 type = STYPE_Selector;
972 name = NAME_SELECTOR_WIRELESS_SINGLE_SQUARE;
973 }
974 else if (model == MODEL_SELECTOR_WIRELESS_SINGLE_SMART_PUSH) {
975 type = STYPE_Selector;
976 name = NAME_SELECTOR_WIRELESS_SINGLE_SMART_PUSH;
977 }
978 else if ((model == MODEL_SENSOR_DOOR) || (model == MODEL_SENSOR_DOOR_AQARA)) {
979 type = STYPE_Contact;
980 name = NAME_SENSOR_DOOR;
981 }
982 else if (model == MODEL_ACT_ONOFF_PLUG) {
983 type = STYPE_OnOff;
984 name = NAME_ACT_ONOFF_PLUG;
985 }
986 else if (model == MODEL_ACT_ONOFF_PLUG_WALL_1 || model == MODEL_ACT_ONOFF_PLUG_WALL_2) {
987 type = STYPE_OnOff;
988 name = NAME_ACT_ONOFF_PLUG_WALL;
989 }
990 else if (model == MODEL_SENSOR_TEMP_HUM_V1) {
991 name = NAME_SENSOR_TEMP_HUM_V1;
992 }
993 else if (model == MODEL_SENSOR_TEMP_HUM_AQARA) {
994 name = NAME_SENSOR_TEMP_HUM_AQARA;
995 }
996 else if (model == MODEL_SELECTOR_CUBE_V1) {
997 type = STYPE_Selector;
998 name = NAME_SELECTOR_CUBE_V1;
999 }
1000 else if (model == MODEL_SELECTOR_CUBE_AQARA) {
1001 type = STYPE_Selector;
1002 name = NAME_SELECTOR_CUBE_AQARA;
1003 }
1004 else if (model == MODEL_SENSOR_VIBRATION) {
1005 type = STYPE_Selector;
1006 name = NAME_SENSOR_VIBRATION;
1007 }
1008 else if (model == MODEL_GATEWAY_1 || model == MODEL_GATEWAY_2 || model == MODEL_GATEWAY_3) {
1009 name = NAME_GATEWAY;
1010 }
1011 else if (model == MODEL_SELECTOR_WIRED_WALL_SINGLE_1 || model == MODEL_SELECTOR_WIRED_WALL_SINGLE_2 || model == MODEL_SELECTOR_WIRED_WALL_SINGLE_3) {
1012 type = STYPE_END; //type = STYPE_OnOff; // TODO: fix this hack
1013 name = NAME_SELECTOR_WIRED_WALL_SINGLE;
1014 }
1015 else if (model == MODEL_SELECTOR_WIRED_WALL_DUAL_1 || model == MODEL_SELECTOR_WIRED_WALL_DUAL_2 || model == MODEL_SELECTOR_WIRED_WALL_DUAL_3) {
1016 type = STYPE_END; //type = STYPE_OnOff; // TODO: fix this hack
1017 name = NAME_SELECTOR_WIRED_WALL_DUAL;
1018 }
1019 else if (model == MODEL_SELECTOR_WIRELESS_WALL_SINGLE_1 || model == MODEL_SELECTOR_WIRELESS_WALL_SINGLE_2) {
1020 type = STYPE_Selector;
1021 name = NAME_SELECTOR_WIRELESS_WALL_SINGLE;
1022 }
1023 else if (model == MODEL_SELECTOR_WIRELESS_WALL_DUAL_1 || model == MODEL_SELECTOR_WIRELESS_WALL_DUAL_2) {
1024 type = STYPE_Selector;
1025 name = NAME_SELECTOR_WIRELESS_WALL_DUAL;
1026 }
1027 else if (model == MODEL_SENSOR_SMOKE) {
1028 type = STYPE_SMOKEDETECTOR;
1029 name = NAME_SENSOR_SMOKE;
1030 }
1031 else if (model == MODEL_SENSOR_GAS) {
1032 type = STYPE_SMOKEDETECTOR;
1033 name = NAME_SENSOR_GAS;
1034 }
1035 else if (model == MODEL_SENSOR_WATER) {
1036 type = STYPE_SMOKEDETECTOR;
1037 name = NAME_SENSOR_WATER;
1038 }
1039 else if (model == MODEL_ACT_BLINDS_CURTAIN) {
1040 type = STYPE_BlindsPercentage;
1041 name = NAME_ACT_BLINDS_CURTAIN;
1042 }
1043
1044 std::string voltage = root2["voltage"].asString();
1045 int battery = 255;
1046 if (voltage != "" && voltage != "3600") {
1047 battery = ((atoi(voltage.c_str()) - 2200) / 10);
1048 }
1049 if (type != STYPE_END)
1050 {
1051 std::string status = root2["status"].asString();
1052 std::string no_close = root2["no_close"].asString();
1053 std::string no_motion = root2["no_motion"].asString();
1054 // Aqara's Wireless switch reports per channel
1055 std::string aqara_wireless1 = root2[NAME_CHANNEL_0].asString();
1056 std::string aqara_wireless2 = root2[NAME_CHANNEL_1].asString();
1057 std::string aqara_wireless3 = root2["dual_channel"].asString();
1058 // Smart plug usage
1059 std::string load_power = root2["load_power"].asString();
1060 std::string power_consumed = root2["power_consumed"].asString();
1061 // Smoke or Gas Detector
1062 std::string density = root2["density"].asString();
1063 std::string alarm = root2["alarm"].asString();
1064 // Aqara motion sensor
1065 std::string lux = root2["lux"].asString();
1066 // Curtain
1067 std::string curtain = root2["curtain_level"].asString();
1068 bool on = false;
1069 int level = -1;
1070 if (model == MODEL_SELECTOR_WIRELESS_SINGLE_1) {
1071 level = 0;
1072 }
1073 else if (model == MODEL_SENSOR_SMOKE || model == MODEL_SENSOR_GAS|| model == MODEL_SENSOR_WATER || model == MODEL_SELECTOR_CUBE_AQARA) {
1074 if (battery != 255 && (model == MODEL_SENSOR_WATER || model == MODEL_SELECTOR_CUBE_AQARA)) {
1075 level = 0;
1076 }
1077 if ((alarm == "1") || (alarm == "2") || (status == STATE_WATER_LEAK_YES)) {
1078 level = 0;
1079 on = true;
1080 }
1081 else if ((alarm == "0") || (status == STATE_WATER_LEAK_NO) || (status == "iam")) {
1082 level = 0;
1083 }
1084 if (density != "")
1085 level = atoi(density.c_str());
1086 }
1087 if ((status == STATE_MOTION_YES) || (status == STATE_OPEN) || (status == "no_close") || (status == STATE_ON) || (no_close != "")) {
1088 level = 0;
1089 on = true;
1090 }
1091 else if ((status == STATE_MOTION_NO) || (status == STATE_CLOSE) || (status == STATE_OFF) || (no_motion != "")) {
1092 level = 0;
1093 on = false;
1094 }
1095 else if ((status == "click") || (status == "flip90") || (aqara_wireless1 == "click") || (status == "tilt")) {
1096 level = 10;
1097 on = true;
1098 }
1099 else if ((status == "double_click") || (status == "flip180") || (aqara_wireless2 == "click") || (status == "shake") || (status == "vibrate") ||
1100 (name == "Xiaomi Wireless Single Wall Switch" && aqara_wireless1 == "double_click")) {
1101 level = 20;
1102 on = true;
1103 }
1104 else if ((status == "long_click_press") || (status == "move") || (aqara_wireless3 == "both_click") ||
1105 (name == "Xiaomi Wireless Single Wall Switch" && aqara_wireless1 == "long_click")) {
1106 level = 30;
1107 on = true;
1108 }
1109 else if ((status == "tap_twice") || (status == "long_click_release") ||
1110 (name == "Xiaomi Wireless Dual Wall Switch" && aqara_wireless1 == "double_click")) {
1111 level = 40;
1112 on = true;
1113 }
1114 else if ((status == "shake_air") || (aqara_wireless2 == "double_click")) {
1115 level = 50;
1116 on = true;
1117 }
1118 else if ((status == "swing") || (aqara_wireless3 == "double_both_click")) {
1119 level = 60;
1120 on = true;
1121 }
1122 else if ((status == "alert") || (name == "Xiaomi Wireless Dual Wall Switch" && aqara_wireless1 == "long_click")) {
1123 level = 70;
1124 on = true;
1125 }
1126 else if ((status == "free_fall") || (aqara_wireless2 == "long_click")) {
1127 level = 80;
1128 on = true;
1129 }
1130 else if (aqara_wireless3 == "long_both_click") {
1131 level = 90;
1132 on = true;
1133 }
1134 std::string rotate = root2["rotate"].asString();
1135 if (rotate != "") {
1136 int amount = atoi(rotate.c_str());
1137 if (amount > 0) {
1138 level = 90;
1139 }
1140 else {
1141 level = 100;
1142 }
1143 on = true;
1144 TrueGateway->InsertUpdateCubeText(sid.c_str(), name, rotate.c_str());
1145 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, on, type, unitcode, level, cmd, "", "", battery);
1146 }
1147 else {
1148 if (model == MODEL_ACT_ONOFF_PLUG || model == MODEL_ACT_ONOFF_PLUG_WALL_1 || model == MODEL_ACT_ONOFF_PLUG_WALL_2) {
1149 sleep_milliseconds(100); // Need to sleep here as the gateway will send 2 update messages, and need time for the database to update the state so that the event is not triggered twice
1150 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, on, type, unitcode, level, cmd, load_power, power_consumed, battery);
1151 }
1152 else if ((model == MODEL_ACT_BLINDS_CURTAIN) && (curtain != "")) {
1153 level = atoi(curtain.c_str());
1154 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, on, type, unitcode, level, cmd, "", "", battery);
1155 }
1156 else {
1157 if (level > -1) { // This should stop false updates when empty 'data' is received
1158 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, on, type, unitcode, level, cmd, "", "", battery);
1159 }
1160 if (lux != "") {
1161 TrueGateway->InsertUpdateLux(sid.c_str(), name, atoi(lux.c_str()), battery);
1162 }
1163 if (voltage != "" && m_IncludeVoltage) {
1164 TrueGateway->InsertUpdateVoltage(sid.c_str(), name, atoi(voltage.c_str()));
1165 }
1166 }
1167 }
1168 }
1169 else if ((name == NAME_SELECTOR_WIRED_WALL_SINGLE) || (name == NAME_SELECTOR_WIRED_WALL_DUAL))
1170 {
1171 // Aqara wired dual switch, bidirectional communication support
1172 type = STYPE_OnOff; // TODO: Needs to be set above but need different way of executing this code without hack
1173 std::string aqara_wired1 = root2[NAME_CHANNEL_0].asString();
1174 std::string aqara_wired2 = root2[NAME_CHANNEL_1].asString();
1175 bool state = (aqara_wired1 == STATE_ON) || (aqara_wired2 == STATE_ON);
1176
1177 unitcode = XiaomiUnitCode::SELECTOR_WIRED_WALL_SINGLE;
1178 if (name == NAME_SELECTOR_WIRED_WALL_SINGLE) {
1179 unitcode = XiaomiUnitCode::SELECTOR_WIRED_WALL_SINGLE;
1180 }
1181 else {
1182 unitcode = XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_0;
1183 name = NAME_SELECTOR_WIRED_WALL_DUAL_CHANNEL_0;
1184 }
1185 if (aqara_wired1 != "") {
1186 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, state, type, unitcode, 0, cmd, "", "", battery);
1187 }
1188 else if (aqara_wired2 != "") {
1189 unitcode = XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_1;
1190 name = NAME_SELECTOR_WIRED_WALL_DUAL_CHANNEL_1;
1191 TrueGateway->InsertUpdateSwitch(sid.c_str(), name, state, type, unitcode, 0, cmd, "", "", battery);
1192 }
1193 }
1194 else if ((name == NAME_SENSOR_TEMP_HUM_V1) || (name == NAME_SENSOR_TEMP_HUM_AQARA))
1195 {
1196 std::string temperature = root2["temperature"].asString();
1197 std::string humidity = root2["humidity"].asString();
1198 float pressure = 0;
1199
1200 if (name == NAME_SENSOR_TEMP_HUM_AQARA) {
1201 std::string szPressure = root2["pressure"].asString();
1202 pressure = static_cast<float>(atof(szPressure.c_str())) / 100.0f;
1203 }
1204
1205 if ((!temperature.empty()) && (!humidity.empty()) && (pressure != 0))
1206 {
1207 // Temp+Hum+Baro
1208 float temp = std::stof(temperature) / 100.0f;
1209 int hum = static_cast<int>((std::stof(humidity) / 100));
1210 TrueGateway->InsertUpdateTempHumPressure(sid.c_str(), "Xiaomi TempHumBaro", temp, hum, pressure, battery);
1211 }
1212 else if ((!temperature.empty()) && (!humidity.empty()))
1213 {
1214 // Temp+Hum
1215 float temp = std::stof(temperature) / 100.0f;
1216 int hum = static_cast<int>((std::stof(humidity) / 100));
1217 TrueGateway->InsertUpdateTempHum(sid.c_str(), "Xiaomi TempHum", temp, hum, battery);
1218 }
1219 else if (temperature != "") {
1220 float temp = std::stof(temperature) / 100.0f;
1221 if (temp < 99) {
1222 TrueGateway->InsertUpdateTemperature(sid.c_str(), "Xiaomi Temperature", temp, battery);
1223 }
1224 }
1225 else if (humidity != "") {
1226 int hum = static_cast<int>((std::stof(humidity) / 100));
1227 if (hum > 1) {
1228 TrueGateway->InsertUpdateHumidity(sid.c_str(), "Xiaomi Humidity", hum, battery);
1229 }
1230 }
1231 }
1232 else if (name == NAME_GATEWAY)
1233 {
1234 std::string rgb = root2["rgb"].asString();
1235 std::string illumination = root2["illumination"].asString();
1236 if (rgb != "") {
1237 // Only add in the gateway that matches the SID for this hardware.
1238 if (TrueGateway->GetGatewaySid() == sid)
1239 {
1240 std::stringstream ss;
1241 ss << std::hex << atoi(rgb.c_str());
1242 std::string hexstring(ss.str());
1243 if (hexstring.length() == 7) {
1244 hexstring.insert(0, "0");
1245 }
1246 std::string bright_hex = hexstring.substr(0, 2);
1247 std::stringstream ss2;
1248 ss2 << std::hex << bright_hex.c_str();
1249 int brightness = strtoul(bright_hex.c_str(), NULL, 16);
1250 bool on = false;
1251 if (rgb != "0") {
1252 on = true;
1253 }
1254 TrueGateway->InsertUpdateRGBGateway(sid.c_str(), name + " (" + TrueGateway->GetGatewayIp() + ")", on, brightness, 0);
1255 TrueGateway->InsertUpdateLux(sid.c_str(), NAME_GATEWAY_LUX, atoi(illumination.c_str()), 255);
1256 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_ALARM_RINGTONE, false, STYPE_Selector, 3, 0, cmd, "", "", 255);
1257 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_ALARM_CLOCK, false, STYPE_Selector, 4, 0, cmd, "", "", 255);
1258 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_DOORBELL, false, STYPE_Selector, 5, 0, cmd, "", "", 255);
1259 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_MP3, false, STYPE_OnOff, 6, 0, cmd, "", "", 255);
1260 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_VOLUME_CONTROL, false, STYPE_Dimmer, 7, 0, cmd, "", "", 255);
1261 }
1262 }
1263 else {
1264 // Check for token
1265 std::string token = root["token"].asString();
1266 std::string ip = root2["ip"].asString();
1267
1268 if ((token != "") && (ip != "")) {
1269 XiaomiGatewayTokenManager::GetInstance().UpdateTokenSID(ip, token, sid);
1270 showmessage = false;
1271 }
1272 }
1273 }
1274 else
1275 {
1276 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): unhandled model: '%s', name: '%s'", TrueGateway->GetGatewayHardwareID(), model.c_str(), name.c_str());
1277 }
1278 }
1279 }
1280 else if (cmd == "get_id_list_ack")
1281 {
1282 Json::Value root2;
1283 ret = ParseJSon(data.c_str(), root2);
1284 if ((ret) || (!root2.isObject()))
1285 {
1286 for (int i = 0; i < (int)root2.size(); i++) {
1287 std::string message = "{\"cmd\" : \"read\",\"sid\":\"";
1288 message.append(root2[i].asString().c_str());
1289 message.append("\"}");
1290 std::shared_ptr<std::string> message1(new std::string(message));
1291 boost::asio::ip::udp::endpoint remote_endpoint;
1292 remote_endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string(TrueGateway->GetGatewayIp().c_str()), 9898);
1293 socket_.send_to(boost::asio::buffer(*message1), remote_endpoint);
1294 }
1295 }
1296 showmessage = false;
1297 }
1298 else if (cmd == "iam")
1299 {
1300 if (model == MODEL_GATEWAY_1 || model == MODEL_GATEWAY_2 || model == MODEL_GATEWAY_3)
1301 {
1302 std::string ip = root["ip"].asString();
1303 // Only add in the gateway that matches the IP address for this hardware.
1304 if (ip == TrueGateway->GetGatewayIp())
1305 {
1306 _log.Log(LOG_STATUS, "XiaomiGateway: RGB Gateway Detected");
1307 TrueGateway->InsertUpdateRGBGateway(sid.c_str(), "Xiaomi RGB Gateway (" + ip + ")", false, 0, 100);
1308 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_ALARM_RINGTONE, false, STYPE_Selector, 3, 0, cmd, "", "", 255);
1309 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_ALARM_CLOCK, false, STYPE_Selector, 4, 0, cmd, "", "", 255);
1310 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_DOORBELL, false, STYPE_Selector, 5, 0, cmd, "", "", 255);
1311 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_MP3, false, STYPE_OnOff, 6, 0, cmd, "", "", 255);
1312 TrueGateway->InsertUpdateSwitch(sid.c_str(), NAME_GATEWAY_SOUND_VOLUME_CONTROL, false, STYPE_Dimmer, 7, 0, cmd, "", "", 255);
1313
1314 // Query for list of devices
1315 std::string message = "{\"cmd\" : \"get_id_list\"}";
1316 std::shared_ptr<std::string> message2(new std::string(message));
1317 boost::asio::ip::udp::endpoint remote_endpoint;
1318 remote_endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::address::from_string(TrueGateway->GetGatewayIp().c_str()), 9898);
1319 socket_.send_to(boost::asio::buffer(*message2), remote_endpoint);
1320 }
1321 }
1322 showmessage = false;
1323 }
1324 else
1325 {
1326 _log.Log(LOG_STATUS, "XiaomiGateway (ID=%d): unknown cmd received: '%s', model: '%s'", TrueGateway->GetGatewayHardwareID(), cmd.c_str(), model.c_str());
1327 }
1328 }
1329 if (showmessage && m_OutputMessage) {
1330 _log.Log(LOG_STATUS, "%s", data_);
1331 }
1332 start_receive();
1333 }
1334 else {
1335 _log.Log(LOG_ERROR, "XiaomiGateway: error in handle_receive '%s'", error.message().c_str());
1336 }
1337 }
1338
1339
GetInstance()1340 XiaomiGateway::XiaomiGatewayTokenManager& XiaomiGateway::XiaomiGatewayTokenManager::GetInstance()
1341 {
1342 static XiaomiGateway::XiaomiGatewayTokenManager instance;
1343 return instance;
1344 }
1345
UpdateTokenSID(const std::string & ip,const std::string & token,const std::string & sid)1346 void XiaomiGateway::XiaomiGatewayTokenManager::UpdateTokenSID(const std::string & ip, const std::string & token, const std::string & sid)
1347 {
1348 bool found = false;
1349 std::unique_lock<std::mutex> lock(m_mutex);
1350 for (unsigned i = 0; i < m_GatewayTokens.size(); i++) {
1351 if (boost::get<0>(m_GatewayTokens[i]) == ip) {
1352 boost::get<1>(m_GatewayTokens[i]) = token;
1353 boost::get<2>(m_GatewayTokens[i]) = sid;
1354 found = true;
1355 }
1356 }
1357 if (!found) {
1358 m_GatewayTokens.push_back(boost::make_tuple(ip, token, sid));
1359 }
1360
1361 }
1362
GetToken(const std::string & ip)1363 std::string XiaomiGateway::XiaomiGatewayTokenManager::GetToken(const std::string & ip)
1364 {
1365 std::string token = "";
1366 bool found = false;
1367 std::unique_lock<std::mutex> lock(m_mutex);
1368 for (unsigned i = 0; i < m_GatewayTokens.size(); i++) {
1369 if (boost::get<0>(m_GatewayTokens[i]) == ip) {
1370 token = boost::get<1>(m_GatewayTokens[i]);
1371 }
1372 }
1373 return token;
1374 }
1375
GetSID(const std::string & ip)1376 std::string XiaomiGateway::XiaomiGatewayTokenManager::GetSID(const std::string & ip)
1377 {
1378 std::string sid = "";
1379 bool found = false;
1380 std::unique_lock<std::mutex> lock(m_mutex);
1381 for (unsigned i = 0; i < m_GatewayTokens.size(); i++) {
1382 if (boost::get<0>(m_GatewayTokens[i]) == ip) {
1383 sid = boost::get<2>(m_GatewayTokens[i]);
1384 }
1385 }
1386 return sid;
1387 }
1388
DetermineChannel(int32_t unitcode)1389 std::string XiaomiGateway::DetermineChannel(int32_t unitcode)
1390 {
1391 std::string cmdchannel = "";
1392 if (unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_SINGLE || unitcode == XiaomiUnitCode::ACT_ONOFF_PLUG ||
1393 unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_0) {
1394 cmdchannel = NAME_CHANNEL_0;
1395 }
1396 else if (unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_1) {
1397 cmdchannel = NAME_CHANNEL_1;
1398 }
1399 return cmdchannel;
1400 }
1401
DetermineDevice(int32_t unitcode)1402 std::string XiaomiGateway::DetermineDevice(int32_t unitcode)
1403 {
1404 std::string cmddevice = "";
1405 if (unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_SINGLE) {
1406 cmddevice = "ctrl_neutral1";
1407 }
1408 else if (unitcode == XiaomiUnitCode::ACT_ONOFF_PLUG) {
1409 cmddevice = "plug";
1410 }
1411 else if (unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_0 || unitcode == XiaomiUnitCode::SELECTOR_WIRED_WALL_DUAL_CHANNEL_1) {
1412 cmddevice = "ctrl_neutral2";
1413 }
1414 return cmddevice;
1415 }
1416
DetermineCommand(uint8_t commandcode)1417 std::string XiaomiGateway::DetermineCommand(uint8_t commandcode)
1418 {
1419 std::string command = "";
1420 switch (commandcode) {
1421 case gswitch_sOff:
1422 command = STATE_OFF;
1423 break;
1424 case gswitch_sOn:
1425 command = STATE_ON;
1426 break;
1427 default:
1428 command = "unknown command";
1429 break;
1430 }
1431 return command;
1432 }