1 #include "stdafx.h"
2 #include "Honeywell.h"
3 #include "../main/Helper.h"
4 #include "../main/Logger.h"
5 #include "hardwaretypes.h"
6 #include "../main/localtime_r.h"
7 #include "../main/WebServerHelper.h"
8 #include "../main/RFXtrx.h"
9 #include "../main/SQLHelper.h"
10 #include "../httpclient/HTTPClient.h"
11 #include "../main/mainworker.h"
12 #include "../main/json_helper.h"
13 #include "../webserver/Base64.h"
14 
15 #define round(a) ( int ) ( a + .5 )
16 
17 const std::string HONEYWELL_DEFAULT_APIKEY = "atD3jtzXC5z4X8WPbzvo0CBqWi7S81Nh";
18 const std::string HONEYWELL_DEFAULT_APISECRET = "TXDzy2aHpAJw6YiO";
19 const std::string HONEYWELL_LOCATIONS_PATH = "https://api.honeywell.com/v2/locations?apikey=[apikey]";
20 const std::string HONEYWELL_UPDATE_THERMOSTAT = "https://api.honeywell.com/v2/devices/thermostats/[deviceid]?apikey=[apikey]&locationId=[locationid]";
21 const std::string HONEYWELL_TOKEN_PATH = "https://api.honeywell.com/oauth2/token";
22 
23 const std::string kHeatSetPointDesc = "Target temperature ([devicename])";
24 const std::string kHeatingDesc = "Heating ([devicename])";
25 const std::string kOperationStatusDesc = "Heating state ([devicename])";
26 const std::string kOutdoorTempDesc = "Outdoor temperature ([devicename])";
27 const std::string kRoomTempDesc = "Room temperature ([devicename])";
28 const std::string kAwayDesc = "Away ([name])";
29 
30 extern http::server::CWebServerHelper m_webservers;
31 
CHoneywell(const int ID,const std::string & Username,const std::string & Password,const std::string & Extra)32 CHoneywell::CHoneywell(const int ID, const std::string &Username, const std::string &Password, const std::string &Extra)
33 {
34 	m_HwdID = ID;
35 	mAccessToken = Username;
36 	mRefreshToken = Password;
37 	stdstring_trim(mAccessToken);
38 	stdstring_trim(mRefreshToken);
39 
40         // get the data from the extradata field
41         std::vector<std::string> strextra;
42         StringSplit(Extra, "|", strextra);
43 	if (strextra.size() == 2)
44 	{
45 		mApiKey = base64_decode(strextra[0]);
46 		mApiSecret = base64_decode(strextra[1]);
47 	}
48 	if (mApiKey.empty()) {
49 		_log.Log(LOG_STATUS, "Honeywell: No API key was set. Using default API key. This will result in many errors caused by quota limitations.");
50 		mApiKey = HONEYWELL_DEFAULT_APIKEY;
51 		mApiSecret = HONEYWELL_DEFAULT_APISECRET;
52 	}
53 	if (Username.empty() || Password.empty()) {
54 		_log.Log(LOG_ERROR, "Honeywell: Please update your access token/request token!...");
55 	}
56 	mLastMinute = -1;
57 	Init();
58 }
59 
~CHoneywell(void)60 CHoneywell::~CHoneywell(void)
61 {
62 }
63 
Init()64 void CHoneywell::Init()
65 {
66 	mTokenExpires = mytime(NULL);
67 }
68 
StartHardware()69 bool CHoneywell::StartHardware()
70 {
71 	RequestStart();
72 
73 	Init();
74 	mLastMinute = -1;
75 	//Start worker thread
76 	m_thread = std::make_shared<std::thread>(&CHoneywell::Do_Work, this);
77 	SetThreadNameInt(m_thread->native_handle());
78 	mIsStarted = true;
79 	sOnConnected(this);
80 	return (m_thread != nullptr);
81 }
82 
StopHardware()83 bool CHoneywell::StopHardware()
84 {
85 	if (m_thread)
86 	{
87 		RequestStop();
88 		m_thread->join();
89 		m_thread.reset();
90 	}
91 
92 	mIsStarted = false;
93 	return true;
94 }
95 
96 #define HONEYWELL_POLL_INTERVAL 300 // 5 minutes
97 #define HWAPITIMEOUT 30 // 30 seconds
98 
99 //
100 // worker thread
101 //
Do_Work()102 void CHoneywell::Do_Work()
103 {
104 	_log.Log(LOG_STATUS, "Honeywell: Worker started...");
105 	int sec_counter = HONEYWELL_POLL_INTERVAL - 5;
106 	while (!IsStopRequested(1000))
107 	{
108 		sec_counter++;
109 		if (sec_counter % 12 == 0) {
110 			m_LastHeartbeat = mytime(NULL);
111 		}
112 		if (sec_counter % HONEYWELL_POLL_INTERVAL == 0)
113 		{
114 			GetThermostatData();
115 		}
116 	}
117 	_log.Log(LOG_STATUS, "Honeywell: Worker stopped...");
118 }
119 
120 
121 //
122 // callback from Domoticz backend to update the Honeywell thermostat
123 //
WriteToHardware(const char * pdata,const unsigned char)124 bool CHoneywell::WriteToHardware(const char *pdata, const unsigned char /*length*/)
125 {
126 	const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata);
127 	if (pCmd->LIGHTING2.packettype == pTypeLighting2)
128 	{
129 		//Light command
130 
131 		int nodeID = pCmd->LIGHTING2.id4;
132 		int devID = nodeID / 10;
133 		std::string deviceName = mDeviceList[devID]["name"].asString();
134 
135 
136 		bool bIsOn = (pCmd->LIGHTING2.cmnd == light2_sOn);
137 		if ((nodeID % 10) == 3) {
138 			// heating on or off
139 			SetPauseStatus(devID, bIsOn, nodeID);
140 			return true;
141 		}
142 
143 	}
144 	else if (pCmd->ICMND.packettype == pTypeThermostat && pCmd->LIGHTING2.subtype == sTypeThermSetpoint)
145 	{
146 		int nodeID = pCmd->LIGHTING2.id4;
147 		int devID = nodeID / 10;
148 		const _tThermostat *therm = reinterpret_cast<const _tThermostat*>(pdata);
149 		SetSetpoint(devID, therm->temp, nodeID);
150 	}
151 	return false;
152 }
153 
154 //
155 // refresh the OAuth2 token through Honeywell API
156 //
refreshToken()157 bool CHoneywell::refreshToken()
158 {
159 	if (mRefreshToken.empty())
160 		return false;
161 
162 	if (mTokenExpires > mytime(NULL))
163 		return true;
164 
165 	std::string sResult;
166 
167 	std::string postData = "grant_type=refresh_token&refresh_token=[refreshToken]";
168 	stdreplace(postData, "[refreshToken]", mRefreshToken);
169 
170 	std::string auth = mApiKey;
171 	auth += ":";
172 	auth += mApiSecret;
173 	std::string encodedAuth = base64_encode(auth);
174 
175 
176 	std::vector<std::string> headers;
177 	std::string authHeader = "Authorization: [auth]";
178 	stdreplace(authHeader, "[auth]", encodedAuth);
179 	headers.push_back(authHeader);
180 	headers.push_back("Content-Type: application/x-www-form-urlencoded");
181 
182 	HTTPClient::SetConnectionTimeout(HWAPITIMEOUT);
183 	HTTPClient::SetTimeout(HWAPITIMEOUT);
184 	if (!HTTPClient::POST(HONEYWELL_TOKEN_PATH, postData, headers, sResult)) {
185 		_log.Log(LOG_ERROR, "Honeywell: Error refreshing token");
186 		return false;
187 	}
188 
189 	Json::Value root;
190 	bool ret = ParseJSon(sResult, root);
191 	if (!ret) {
192 		_log.Log(LOG_ERROR, "Honeywell: Invalid/no data received...");
193 		return false;
194 	}
195 
196 	std::string at = root["access_token"].asString();
197 	std::string rt = root["refresh_token"].asString();
198 	std::string ei = root["expires_in"].asString();
199 	if (at.length() && rt.length() && ei.length()) {
200 		int expires_in = std::stoi(ei);
201 		mTokenExpires = mytime(NULL) + (expires_in > 0 ? expires_in : 600) - HWAPITIMEOUT;
202 		mAccessToken = at;
203 		mRefreshToken = rt;
204 		_log.Log(LOG_NORM, "Honeywell: Storing received access & refresh token. Token expires after %d seconds.",expires_in);
205 		m_sql.safe_query("UPDATE Hardware SET Username='%q', Password='%q' WHERE (ID==%d)", mAccessToken.c_str(), mRefreshToken.c_str(), m_HwdID);
206 		mSessionHeaders.clear();
207 		mSessionHeaders.push_back("Authorization:Bearer " + mAccessToken);
208 		mSessionHeaders.push_back("Content-Type: application/json");
209 	}
210 	else
211 		return false;
212 
213 	return true;
214 }
215 
216 //
217 // Get honeywell data through Honeywell API
218 //
GetThermostatData()219 void CHoneywell::GetThermostatData()
220 {
221 	if (!refreshToken())
222 		return;
223 
224 	std::string sResult;
225 	std::string sURL = HONEYWELL_LOCATIONS_PATH;
226 	stdreplace(sURL, "[apikey]", mApiKey);
227 
228 	HTTPClient::SetConnectionTimeout(HWAPITIMEOUT);
229 	HTTPClient::SetTimeout(HWAPITIMEOUT);
230 	if (!HTTPClient::GET(sURL, mSessionHeaders, sResult)) {
231 		_log.Log(LOG_ERROR, "Honeywell: Error getting thermostat data!");
232 		return;
233 	}
234 
235 	Json::Value root;
236 	bool ret = ParseJSon(sResult, root);
237 	if (!ret) {
238 		_log.Log(LOG_ERROR, "Honeywell: Invalid/no data received...");
239 		return;
240 	}
241 
242 	int devNr = 0;
243 	mDeviceList.clear();
244 	mLocationList.clear();
245 	for (int locCnt = 0; locCnt < (int)root.size(); locCnt++)
246 	{
247 		Json::Value location = root[locCnt];
248 		Json::Value devices = location["devices"];
249 		for (int devCnt = 0; devCnt < (int)devices.size(); devCnt++)
250 		{
251 			Json::Value device = devices[devCnt];
252 			std::string deviceName = device["name"].asString();
253 			mDeviceList[devNr] = device;
254 			mLocationList[devNr] = location["locationID"].asString();
255 
256 			float temperature;
257 			temperature = (float)device["indoorTemperature"].asFloat();
258 			std::string desc = kRoomTempDesc;
259 			stdreplace(desc, "[devicename]", deviceName);
260 			SendTempSensor(10 * devNr + 1, 255, temperature, desc);
261 
262 			temperature = (float)device["outdoorTemperature"].asFloat();
263 			desc = kOutdoorTempDesc;
264 			stdreplace(desc, "[devicename]", deviceName);
265 			SendTempSensor(10 * devNr + 2, 255, temperature, desc);
266 
267 			std::string mode = device["changeableValues"]["mode"].asString();
268 			bool bHeating = (mode == "Heat");
269 			desc = kHeatingDesc;
270 			stdreplace(desc, "[devicename]", deviceName);
271 			SendSwitch(10 * devNr + 3, 1, 255, bHeating, 0, desc);
272 
273 			temperature = (float)device["changeableValues"]["heatSetpoint"].asFloat();
274 			desc = kHeatSetPointDesc;
275 			stdreplace(desc, "[devicename]", deviceName);
276 			SendSetPointSensor((uint8_t)(10 * devNr + 4), temperature, desc);
277 			devNr++;
278 
279 			std::string operationstatus = device["operationStatus"]["mode"].asString();
280 			bool bStatus = (operationstatus != "EquipmentOff");
281 			desc = kOperationStatusDesc;
282 			stdreplace(desc, "[devicename]", deviceName);
283 			SendSwitch(10 * devNr + 5, 1, 255, bStatus, 0, desc);
284 		}
285 
286 		bool geoFenceEnabled = location["geoFenceEnabled"].asBool();
287 		if (geoFenceEnabled) {
288 
289 			Json::Value geofences = location["geoFences"];
290 			bool bAway = true;
291 			for (int geofCnt = 0; geofCnt < (int)geofences.size(); geofCnt++)
292 			{
293 				int withinFence = geofences[geofCnt]["geoOccupancy"]["withinFence"].asInt();
294 				if (withinFence > 0) {
295 					bAway = false;
296 					break;
297 				}
298 			}
299 			std::string desc = kAwayDesc;
300 			stdreplace(desc, "[name]", location["name"].asString());
301 			SendSwitch(10 * devNr + 6, 1, 255, bAway, 0, desc);
302 		}
303 	}
304 }
305 
306 //
307 // send the temperature from honeywell to domoticz backend
308 //
SendSetPointSensor(const unsigned char Idx,const float Temp,const std::string & defaultname)309 void CHoneywell::SendSetPointSensor(const unsigned char Idx, const float Temp, const std::string &defaultname)
310 {
311 	_tThermostat thermos;
312 	thermos.subtype = sTypeThermSetpoint;
313 	thermos.id1 = 0;
314 	thermos.id2 = 0;
315 	thermos.id3 = 0;
316 	thermos.id4 = Idx;
317 	thermos.dunit = 0;
318 
319 	thermos.temp = Temp;
320 
321 	sDecodeRXMessage(this, (const unsigned char *)&thermos, defaultname.c_str(), 255);
322 }
323 
324 //
325 // transfer pause status to Honeywell api
326 //
SetPauseStatus(const int idx,bool bHeating,const int)327 void CHoneywell::SetPauseStatus(const int idx, bool bHeating, const int /*nodeid*/)
328 {
329 	if (!refreshToken()) {
330 		_log.Log(LOG_ERROR,"Honeywell: No token available. Failed setting thermostat data");
331 		return;
332 	}
333 
334 	std::string url = HONEYWELL_UPDATE_THERMOSTAT;
335 	std::string deviceID = mDeviceList[idx]["deviceID"].asString();
336 
337 	stdreplace(url, "[deviceid]", deviceID);
338 	stdreplace(url, "[apikey]", mApiKey);
339 	stdreplace(url, "[locationid]", mLocationList[idx]);
340 
341 	Json::Value reqRoot;
342 	reqRoot["mode"] = bHeating ? "Heat" : "Off";
343 	reqRoot["heatSetpoint"] = mDeviceList[idx]["changeableValues"]["coolHeatpoint"].asInt();
344 	reqRoot["coolSetpoint"] = mDeviceList[idx]["changeableValues"]["coolSetpoint"].asInt();
345 	reqRoot["thermostatSetpointStatus"] = "TemporaryHold";
346 
347 	std::string sResult;
348 	HTTPClient::SetConnectionTimeout(HWAPITIMEOUT);
349 	HTTPClient::SetTimeout(HWAPITIMEOUT);
350 	if (!HTTPClient::POST(url, JSonToRawString(reqRoot), mSessionHeaders, sResult, true, true)) {
351 		_log.Log(LOG_ERROR, "Honeywell: Error setting thermostat data!");
352 		return;
353 	}
354 
355 	std::string desc = kHeatingDesc;
356 	stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString());
357 	SendSwitch(10 * idx + 3, 1, 255, bHeating, 0, desc);
358 }
359 
360 //
361 // transfer the updated temperature to Honeywell API
362 //
SetSetpoint(const int idx,const float temp,const int)363 void CHoneywell::SetSetpoint(const int idx, const float temp, const int /*nodeid*/)
364 {
365 	if (!refreshToken()) {
366 		_log.Log(LOG_ERROR, "Honeywell: No token available. Error setting thermostat data!");
367 		return;
368 	}
369 
370 	std::string url = HONEYWELL_UPDATE_THERMOSTAT;
371 	std::string deviceID = mDeviceList[idx]["deviceID"].asString();
372 
373 	stdreplace(url, "[deviceid]", deviceID);
374 	stdreplace(url, "[apikey]", mApiKey);
375 	stdreplace(url, "[locationid]", mLocationList[idx]);
376 
377 	Json::Value reqRoot;
378 	reqRoot["mode"] = "Heat";
379 	reqRoot["heatSetpoint"] = temp;
380 	reqRoot["coolSetpoint"] = mDeviceList[idx]["changeableValues"]["coolSetpoint"].asInt();
381 	reqRoot["thermostatSetpointStatus"] = "TemporaryHold";
382 
383 	std::string sResult;
384 	HTTPClient::SetConnectionTimeout(HWAPITIMEOUT);
385 	HTTPClient::SetTimeout(HWAPITIMEOUT);
386 	if (!HTTPClient::POST(url, JSonToRawString(reqRoot), mSessionHeaders, sResult, true, true)) {
387 		_log.Log(LOG_ERROR, "Honeywell: Error setting thermostat data!");
388 		return;
389 	}
390 
391 	std::string desc = kHeatSetPointDesc;
392 	stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString());
393 	SendSetPointSensor((uint8_t)(10 * idx + 4), temp, desc);
394 
395 	desc = kHeatingDesc;
396 	stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString());
397 	SendSwitch(10 * idx + 3, 1, 255, true, 0, desc);
398 }
399