|
| 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 "../json/json.h" |
| 13 | +#include "../webserver/Base64.h" |
| 14 | + |
| 15 | +#define round(a) ( int ) ( a + .5 ) |
| 16 | + |
| 17 | +const std::string HONEYWELL_APIKEY = "atD3jtzXC5z4X8WPbzvo0CBqWi7S81Nh"; |
| 18 | +const std::string HONEYWELL_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 kOutdoorTempDesc = "Outdoor temperature ([devicename])"; |
| 26 | +const std::string kRoomTempDesc = "Room temperature ([devicename])"; |
| 27 | + |
| 28 | +extern http::server::CWebServerHelper m_webservers; |
| 29 | + |
| 30 | +CHoneywell::CHoneywell(const int ID, const std::string &Username, const std::string &Password, const int Mode1, const int Mode2, const int Mode3, const int Mode4, const int Mode5, const int Mode6) { |
| 31 | + if (Username.empty() || Password.empty()) { |
| 32 | + _log.Log(LOG_ERROR, "Honeywell: Please update your access token/request token!..."); |
| 33 | + } |
| 34 | + else { |
| 35 | + mAccessToken = Username; |
| 36 | + mRefreshToken = Password; |
| 37 | + stdstring_trim(mAccessToken); |
| 38 | + stdstring_trim(mRefreshToken); |
| 39 | + } |
| 40 | + |
| 41 | + m_HwdID = ID; |
| 42 | + Init(); |
| 43 | + |
| 44 | +} |
| 45 | + |
| 46 | +CHoneywell::~CHoneywell(void) { |
| 47 | +} |
| 48 | + |
| 49 | +void CHoneywell::Init() { |
| 50 | + mStopRequested = false; |
| 51 | + mNeedsTokenRefresh = true; |
| 52 | +} |
| 53 | + |
| 54 | +bool CHoneywell::StartHardware() { |
| 55 | + Init(); |
| 56 | + mLastMinute = -1; |
| 57 | + //Start worker thread |
| 58 | + mThread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&CHoneywell::Do_Work, this))); |
| 59 | + mIsStarted=true; |
| 60 | + sOnConnected(this); |
| 61 | + return (mThread!=NULL); |
| 62 | +} |
| 63 | + |
| 64 | +bool CHoneywell::StopHardware() { |
| 65 | + if (mThread!=NULL) |
| 66 | + { |
| 67 | + assert(mThread); |
| 68 | + mStopRequested = true; |
| 69 | + mThread->join(); |
| 70 | + } |
| 71 | + |
| 72 | + mIsStarted=false; |
| 73 | + return true; |
| 74 | +} |
| 75 | + |
| 76 | +#define HONEYWELL_POLL_INTERVAL 300 // 5 minutes |
| 77 | + |
| 78 | +// |
| 79 | +// worker thread |
| 80 | +// |
| 81 | +void CHoneywell::Do_Work() { |
| 82 | + _log.Log(LOG_STATUS,"Honeywell: Worker started..."); |
| 83 | + int sec_counter = HONEYWELL_POLL_INTERVAL-5; |
| 84 | + while (!mStopRequested) |
| 85 | + { |
| 86 | + sleep_seconds(1); |
| 87 | + sec_counter++; |
| 88 | + if (sec_counter % 12 == 0) { |
| 89 | + m_LastHeartbeat=mytime(NULL); |
| 90 | + } |
| 91 | + if (sec_counter % HONEYWELL_POLL_INTERVAL == 0) |
| 92 | + { |
| 93 | + GetMeterDetails(); |
| 94 | + } |
| 95 | + } |
| 96 | + _log.Log(LOG_STATUS,"Honeywell: Worker stopped..."); |
| 97 | +} |
| 98 | + |
| 99 | + |
| 100 | +// |
| 101 | +// callback from Domoticz backend to update the Honeywell thermostat |
| 102 | +// |
| 103 | +bool CHoneywell::WriteToHardware(const char *pdata, const unsigned char length) { |
| 104 | + const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata); |
| 105 | + if (pCmd->LIGHTING2.packettype == pTypeLighting2) |
| 106 | + { |
| 107 | + //Light command |
| 108 | + |
| 109 | + int nodeID = pCmd->LIGHTING2.id4; |
| 110 | + int devID = nodeID / 10; |
| 111 | + std::string deviceName = mDeviceList[devID]["name"].asString(); |
| 112 | + |
| 113 | + |
| 114 | + bool bIsOn = (pCmd->LIGHTING2.cmnd == light2_sOn); |
| 115 | + if ((nodeID % 10) == 3) { |
| 116 | + // heating on or off |
| 117 | + SetPauseStatus(devID, bIsOn, nodeID); |
| 118 | + return true; |
| 119 | + } |
| 120 | + |
| 121 | + } |
| 122 | + else if (pCmd->ICMND.packettype == pTypeThermostat && pCmd->LIGHTING2.subtype == sTypeThermSetpoint) |
| 123 | + { |
| 124 | + int nodeID = pCmd->LIGHTING2.id4; |
| 125 | + int devID = nodeID / 10; |
| 126 | + const _tThermostat *therm = reinterpret_cast<const _tThermostat*>(pdata); |
| 127 | + float temp = therm->temp; |
| 128 | + int calculatedTemp = (int)temp; |
| 129 | + |
| 130 | + SetSetpoint(devID, temp, nodeID); |
| 131 | + } |
| 132 | + return false; |
| 133 | +} |
| 134 | + |
| 135 | +// |
| 136 | +// refresh the OAuth2 token through Honeywell API |
| 137 | +// |
| 138 | +bool CHoneywell::refreshToken() { |
| 139 | + if (mRefreshToken.empty()) |
| 140 | + return false; |
| 141 | + |
| 142 | + std::string sResult; |
| 143 | + |
| 144 | + std::string postData = "grant_type=refresh_token&refresh_token=[refreshToken]"; |
| 145 | + stdreplace(postData, "[refreshToken]", mRefreshToken); |
| 146 | + |
| 147 | + std::string auth = HONEYWELL_APIKEY; |
| 148 | + auth += ":"; |
| 149 | + auth += HONEYWELL_APISECRET; |
| 150 | + std::string encodedAuth = base64_encode((const unsigned char *)auth.c_str(), auth.length()); |
| 151 | + |
| 152 | + |
| 153 | + std::vector<std::string> headers; |
| 154 | + std::string authHeader = "Authorization: [auth]"; |
| 155 | + stdreplace(authHeader, "[auth]", encodedAuth); |
| 156 | + headers.push_back(authHeader); |
| 157 | + headers.push_back("Content-Type: application/x-www-form-urlencoded"); |
| 158 | + |
| 159 | + if (!HTTPClient::POST(HONEYWELL_TOKEN_PATH, postData, headers, sResult)) { |
| 160 | + _log.Log(LOG_ERROR, "Honeywell: Error refreshing token"); |
| 161 | + mNeedsTokenRefresh = true; |
| 162 | + return false; |
| 163 | + } |
| 164 | + |
| 165 | + Json::Value root; |
| 166 | + Json::Reader jReader; |
| 167 | + bool ret = jReader.parse(sResult, root); |
| 168 | + if (!ret) { |
| 169 | + _log.Log(LOG_ERROR, "Honeywell: Invalid/no data received..."); |
| 170 | + mNeedsTokenRefresh = true; |
| 171 | + return false; |
| 172 | + } |
| 173 | + |
| 174 | + std::string at = root["access_token"].asString(); |
| 175 | + std::string rt = root["refresh_token"].asString(); |
| 176 | + if (at.length() && rt.length()) { |
| 177 | + mAccessToken = at; |
| 178 | + mRefreshToken = rt; |
| 179 | + _log.Log(LOG_NORM, "Honeywell: Storing received access & refresh token"); |
| 180 | + m_sql.safe_query("UPDATE Hardware SET Username='%q', Password='%q' WHERE (ID==%d)", mAccessToken.c_str(), mRefreshToken.c_str(), m_HwdID); |
| 181 | + mNeedsTokenRefresh = false; |
| 182 | + mSessionHeaders.clear(); |
| 183 | + mSessionHeaders.push_back("Authorization:Bearer " + mAccessToken); |
| 184 | + mSessionHeaders.push_back("Content-Type: application/json"); |
| 185 | + } |
| 186 | + else |
| 187 | + return false; |
| 188 | + |
| 189 | + return true; |
| 190 | +} |
| 191 | + |
| 192 | +// |
| 193 | +// Get honeywell data through Honeywell API |
| 194 | +// |
| 195 | +void CHoneywell::GetMeterDetails() { |
| 196 | + |
| 197 | + if (mNeedsTokenRefresh) { |
| 198 | + if (!refreshToken()) |
| 199 | + return; |
| 200 | + } |
| 201 | + |
| 202 | + std::string sResult; |
| 203 | + std::string sURL = HONEYWELL_LOCATIONS_PATH; |
| 204 | + stdreplace(sURL, "[apikey]", HONEYWELL_APIKEY); |
| 205 | + if (!HTTPClient::GET(sURL, mSessionHeaders, sResult)) { |
| 206 | + _log.Log(LOG_ERROR, "Honeywell: Error getting thermostat data!"); |
| 207 | + mNeedsTokenRefresh = true; |
| 208 | + return; |
| 209 | + } |
| 210 | + |
| 211 | + Json::Value root; |
| 212 | + Json::Reader jReader; |
| 213 | + bool ret = jReader.parse(sResult, root); |
| 214 | + if (!ret) { |
| 215 | + _log.Log(LOG_ERROR, "Honeywell: Invalid/no data received..."); |
| 216 | + mNeedsTokenRefresh = true; |
| 217 | + return; |
| 218 | + } |
| 219 | + |
| 220 | + int devNr = 0; |
| 221 | + mDeviceList.clear(); |
| 222 | + mLocationList.clear(); |
| 223 | + for (int locCnt = 0; locCnt < root.size(); locCnt++) { |
| 224 | + Json::Value location = root[locCnt]; |
| 225 | + Json::Value devices = location["devices"]; |
| 226 | + for (int devCnt = 0; devCnt < devices.size(); devCnt++) { |
| 227 | + Json::Value device = devices[devCnt]; |
| 228 | + std::string deviceName = device["name"].asString(); |
| 229 | + mDeviceList[devNr] = device; |
| 230 | + mLocationList[devNr] = location["locationID"].asString(); |
| 231 | + |
| 232 | + float temperature; |
| 233 | + temperature = (float)device["indoorTemperature"].asFloat(); |
| 234 | + std::string desc = kRoomTempDesc; |
| 235 | + stdreplace(desc, "[devicename]", deviceName); |
| 236 | + SendTempSensor(10*devNr + 1, 255, temperature, desc); |
| 237 | + |
| 238 | + temperature = (float)device["outdoorTemperature"].asFloat(); |
| 239 | + desc = kOutdoorTempDesc; |
| 240 | + stdreplace(desc, "[devicename]", deviceName); |
| 241 | + SendTempSensor(10*devNr + 2, 255, temperature, desc); |
| 242 | + |
| 243 | + std::string mode = device["changeableValues"]["mode"].asString(); |
| 244 | + bool bHeating = (mode == "Heat"); |
| 245 | + desc = kHeatingDesc; |
| 246 | + stdreplace(desc, "[devicename]", deviceName); |
| 247 | + SendSwitch(10*devNr + 3, 1, 255, bHeating, 0, desc); |
| 248 | + |
| 249 | + temperature = (float)device["changeableValues"]["heatSetpoint"].asFloat(); |
| 250 | + desc = kHeatSetPointDesc; |
| 251 | + stdreplace(desc, "[devicename]", deviceName); |
| 252 | + SendSetPointSensor(10*devNr + 4, temperature, desc); |
| 253 | + devNr++; |
| 254 | + } |
| 255 | + } |
| 256 | +} |
| 257 | + |
| 258 | +// |
| 259 | +// send the temperature from honeywell to domoticz backend |
| 260 | +// |
| 261 | +void CHoneywell::SendSetPointSensor(const unsigned char Idx, const float Temp, const std::string &defaultname) |
| 262 | +{ |
| 263 | + _tThermostat thermos; |
| 264 | + thermos.subtype=sTypeThermSetpoint; |
| 265 | + thermos.id1=0; |
| 266 | + thermos.id2=0; |
| 267 | + thermos.id3=0; |
| 268 | + thermos.id4=Idx; |
| 269 | + thermos.dunit=0; |
| 270 | + |
| 271 | + thermos.temp=Temp; |
| 272 | + |
| 273 | + sDecodeRXMessage(this, (const unsigned char *)&thermos, defaultname.c_str(), 255); |
| 274 | +} |
| 275 | + |
| 276 | +// |
| 277 | +// transfer pause status to Honeywell api |
| 278 | +// |
| 279 | +void CHoneywell::SetPauseStatus(const int idx, bool bHeating, const int nodeid) |
| 280 | +{ |
| 281 | + std::string url = HONEYWELL_UPDATE_THERMOSTAT; |
| 282 | + std::string deviceID = mDeviceList[idx]["deviceID"].asString(); |
| 283 | + |
| 284 | + stdreplace(url, "[deviceid]", deviceID); |
| 285 | + stdreplace(url, "[apikey]", HONEYWELL_APIKEY); |
| 286 | + stdreplace(url, "[locationid]", mLocationList[idx]); |
| 287 | + |
| 288 | + Json::Value reqRoot; |
| 289 | + reqRoot["mode"] = bHeating ? "Heat" : "Off"; |
| 290 | + reqRoot["heatSetpoint"] = mDeviceList[idx]["changeableValues"]["coolHeatpoint"].asInt(); |
| 291 | + reqRoot["coolSetpoint"] = mDeviceList[idx]["changeableValues"]["coolSetpoint"].asInt(); |
| 292 | + reqRoot["thermostatSetpointStatus"] = "TemporaryHold"; |
| 293 | + Json::FastWriter writer; |
| 294 | + |
| 295 | + std::string sResult; |
| 296 | + if (!HTTPClient::POST(url, writer.write(reqRoot), mSessionHeaders, sResult, true, true)) { |
| 297 | + _log.Log(LOG_ERROR, "Honeywell: Error setting thermostat data!"); |
| 298 | + return; |
| 299 | + } |
| 300 | + |
| 301 | + std::string desc = kHeatingDesc; |
| 302 | + stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString()); |
| 303 | + SendSwitch(10*idx + 3, 1, 255, bHeating, 0, desc); |
| 304 | +} |
| 305 | + |
| 306 | +// |
| 307 | +// transfer the updated temperature to Honeywell API |
| 308 | +// |
| 309 | +void CHoneywell::SetSetpoint(const int idx, const float temp, const int nodeid) |
| 310 | +{ |
| 311 | + std::string url = HONEYWELL_UPDATE_THERMOSTAT; |
| 312 | + std::string deviceID = mDeviceList[idx]["deviceID"].asString(); |
| 313 | + |
| 314 | + stdreplace(url, "[deviceid]", deviceID); |
| 315 | + stdreplace(url, "[apikey]", HONEYWELL_APIKEY); |
| 316 | + stdreplace(url, "[locationid]", mLocationList[idx]); |
| 317 | + |
| 318 | + Json::Value reqRoot; |
| 319 | + reqRoot["mode"] = "Heat"; |
| 320 | + reqRoot["heatSetpoint"] = temp; |
| 321 | + reqRoot["coolSetpoint"] = mDeviceList[idx]["changeableValues"]["coolSetpoint"].asInt(); |
| 322 | + reqRoot["thermostatSetpointStatus"] = "TemporaryHold"; |
| 323 | + Json::FastWriter writer; |
| 324 | + |
| 325 | + std::string sResult; |
| 326 | + if (!HTTPClient::POST(url, writer.write(reqRoot), mSessionHeaders, sResult, true, true)) { |
| 327 | + _log.Log(LOG_ERROR, "Honeywell: Error setting thermostat data!"); |
| 328 | + return; |
| 329 | + } |
| 330 | + |
| 331 | + std::string desc = kHeatSetPointDesc; |
| 332 | + stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString()); |
| 333 | + SendSetPointSensor(10*idx + 4, temp, desc); |
| 334 | + |
| 335 | + desc = kHeatingDesc; |
| 336 | + stdreplace(desc, "[devicename]", mDeviceList[idx]["name"].asString()); |
| 337 | + SendSwitch(10*idx + 3, 1, 255, true, 0, desc); |
| 338 | +} |
0 commit comments