|
| 1 | +import json |
| 2 | +import requests |
| 3 | +from bd_connect.connect_bd import get_json |
| 4 | +from config.setting import Setting |
| 5 | +from pprint import pprint |
| 6 | + |
| 7 | +''' |
| 8 | +About: |
| 9 | +This module is a connector that connects any Nest device to the |
| 10 | +Building DepotV3.1 with the help of the bd connect program. |
| 11 | +
|
| 12 | +Configuration: |
| 13 | +To be able to have the program access your Nest data, you have |
| 14 | +to register your program as a Nest app in your Netatmo account at |
| 15 | +https://develper.nest.com. You have to go on create new product, choose |
| 16 | +the appropriate permissions and enter the required details in the |
| 17 | +nest_config.json file. The pin code can be received by clicking on |
| 18 | +the authorization url. |
| 19 | +
|
| 20 | +''' |
| 21 | + |
| 22 | +class ClientAuth(object): |
| 23 | + ''' |
| 24 | + Class definition to get the access token, it does not need to be |
| 25 | + refreshed |
| 26 | + ''' |
| 27 | + def __init__(self, credentials_file = "nest"): |
| 28 | + ''' |
| 29 | + Recieves the credentials from the config file and executes the |
| 30 | + setup function to get the access token |
| 31 | +
|
| 32 | + Args: |
| 33 | + credentials_file: The json file containing the Product ID, Product |
| 34 | + Secret, PIN and the access token (which is |
| 35 | + obtained from the other information if not |
| 36 | + present). It is in the config folder with the |
| 37 | + default name "nest". |
| 38 | + ''' |
| 39 | + self.credentials_file = credentials_file |
| 40 | + self.setup() |
| 41 | + |
| 42 | + def setup(self): |
| 43 | + ''' |
| 44 | + Obtains the access token if present and calls the access_token() |
| 45 | + function if the access_token is not present, using the Product Id, |
| 46 | + Product Secret, and PIN. |
| 47 | +
|
| 48 | + Args as Data: |
| 49 | + Product_id: #product id of the registered product |
| 50 | + Product_secret: #product secret of the registered product |
| 51 | + PIN: #generated by clicking on the authorization url, |
| 52 | + must be updated after every use |
| 53 | + access_token: #needed for access. If it is not present, the |
| 54 | + other three args are used to generate it. |
| 55 | + ''' |
| 56 | + credentials = Setting(self.credentials_file) |
| 57 | + self.urls = credentials.setting["url"] |
| 58 | + self._accessToken = credentials.setting["credentials"]["access_token"] |
| 59 | + # get the access token |
| 60 | + |
| 61 | + if (self._accessToken == ""): |
| 62 | + # generate access token if it is not present |
| 63 | + self.code, self.client_id, self.client_secret = ( |
| 64 | + credentials.setting["credentials"]["PIN"], |
| 65 | + credentials.setting["credentials"]["Product_id"], |
| 66 | + credentials.setting["credentials"]["Product_secret"]) |
| 67 | + self.access_token() |
| 68 | + self.devices = dict() # generate an empty dictionary for all NEST |
| 69 | + # devices linked to the account |
| 70 | + |
| 71 | + def access_token(self): |
| 72 | + ''' |
| 73 | + Update the access_token if it is not present in the config file |
| 74 | + ''' |
| 75 | + |
| 76 | + url = self.urls["_AUTH_REQ"] |
| 77 | + params = {"code": self.code, "client_id": self.client_id, |
| 78 | + "client_secret": self.client_secret, |
| 79 | + "grant_type": "authorization_code"} |
| 80 | + |
| 81 | + response = requests.post(url, params = params) |
| 82 | + try: |
| 83 | + self._accessToken = response.json()["access_token"] |
| 84 | + except: |
| 85 | + self._accessToken = None |
| 86 | + print(response) |
| 87 | + |
| 88 | +class DeviceList(object): |
| 89 | + ''' |
| 90 | + Class definition of Nest to obtain the Nest device data and |
| 91 | + update the data in the Building depot. |
| 92 | + ''' |
| 93 | + |
| 94 | + def __init__(self, authData): |
| 95 | + ''' |
| 96 | + Initilize the auth token and obtain the device modules data |
| 97 | + of NEST |
| 98 | + Args as data: |
| 99 | + "authData": class object of ClientAuth provides |
| 100 | + access token. |
| 101 | + ''' |
| 102 | + self.getAuthToken = authData._accessToken |
| 103 | + url = (authData.urls["_DEVICELIST_REQ"] + self.getAuthToken) |
| 104 | + header = {"Authorization": "Bearer " + self.getAuthToken} |
| 105 | + response = requests.get(url, headers = header, allow_redirects=True) |
| 106 | + try: |
| 107 | + self.devices = response.json() |
| 108 | + except: |
| 109 | + print(response) |
| 110 | + |
| 111 | + def deviceByType(self, deviceType = "smoke_co_alarms"): |
| 112 | + ''' |
| 113 | + Find the Nest data by device type (eg. thermostats, |
| 114 | + smoke_co_alarms). If none given finds all the data of the each |
| 115 | + Nest device connected to the given account |
| 116 | +
|
| 117 | + Args as data: |
| 118 | + "deviceType": device type (eg. thermostats, smoke_co_alarms) |
| 119 | + ''' |
| 120 | + if deviceType == None: return self.devices |
| 121 | + try: |
| 122 | + return self.devices[deviceType] |
| 123 | + except: |
| 124 | + print("No devices of such type") |
| 125 | + return None |
| 126 | + |
| 127 | + def deviceByID(self, deviceType = "smoke_co_alarms", deviceID = None): |
| 128 | + ''' |
| 129 | + Find the Nest data by device ID, i.e. a specific device. |
| 130 | + If none given finds all the data of the each |
| 131 | + Nest device connected to the given account |
| 132 | +
|
| 133 | + Args as data: |
| 134 | + "deviceType": device type |
| 135 | + "deviceID": unique ID to identify the device |
| 136 | + ''' |
| 137 | + if deviceType == None or deviceID == None: return self.devices |
| 138 | + try: |
| 139 | + return self.devices[deviceType][deviceID] |
| 140 | + except: |
| 141 | + print("Device ID does not exist or is not connected to this \ |
| 142 | + account") |
| 143 | + return None |
| 144 | + |
| 145 | + def deviceByName(self, deviceName = None): |
| 146 | + ''' |
| 147 | + Find the Nest data by device Name, i.e. the name give to |
| 148 | + a specific Nest Device.If none given finds all the data of the each |
| 149 | + Nest device connected to the given account. |
| 150 | +
|
| 151 | + Nest has two names, name_long and name, we recommend using |
| 152 | + name_long as multiple devices of different types can have the |
| 153 | + same name. |
| 154 | +
|
| 155 | + Args as data: |
| 156 | + "deviceName": Name given to the specific device |
| 157 | + ''' |
| 158 | + if deviceName == None: return self.devices |
| 159 | + for deviceType in self.devices: |
| 160 | + typeList = self.devices[deviceType] |
| 161 | + for deviceID in typeList: |
| 162 | + deviceData = typeList[deviceID] |
| 163 | + if (deviceData["name"] == deviceName or |
| 164 | + deviceData["name_long"] == deviceName): |
| 165 | + return deviceData |
| 166 | + print("No devices with given name") |
| 167 | + return None |
| 168 | + |
| 169 | + def get_device_data(self, deviceType = "smoke_co_alarms", |
| 170 | + deviceID = None, deviceName = None): |
| 171 | + ''' |
| 172 | + Obtain the data of all NEST devices of a particular device in the |
| 173 | + form of a dictionary. |
| 174 | +
|
| 175 | + Args as data: |
| 176 | + deviceType: the type of nest device (eg. thermostats, |
| 177 | + smoke_co_alarms) |
| 178 | + deviceID: unique ID of the device. The device type must be |
| 179 | + that of the device. |
| 180 | + deviceName: Name given to the device. |
| 181 | +
|
| 182 | + Returns: |
| 183 | +
|
| 184 | + { |
| 185 | + u"co_alarm_state": #whether the state is ok or not. |
| 186 | +
|
| 187 | + u"smoke_alarm_state": #whether the state is ok or not |
| 188 | +
|
| 189 | + U"battery_health": #whether the state is ok or not |
| 190 | + } |
| 191 | +
|
| 192 | + ''' |
| 193 | + |
| 194 | + if (deviceID != None and deviceType != None): |
| 195 | + data = self.deviceByID(deviceType, deviceID) |
| 196 | + elif (deviceName != None): |
| 197 | + data = self.deviceByName(deviceName) |
| 198 | + else: |
| 199 | + print("Please enter a valid deviceID or deviceName") |
| 200 | + data = None |
| 201 | + |
| 202 | + if (data == None): |
| 203 | + return |
| 204 | + |
| 205 | + nest_data = {} |
| 206 | + nest_data["co_alarm_state"] = data["co_alarm_state"] |
| 207 | + nest_data["smoke_alarm_state"] = data["smoke_alarm_state"] |
| 208 | + nest_data["battery_health"] = data["battery_health"] |
| 209 | + nest_data["mac_id"] = data["device_id"] |
| 210 | + return nest_data |
| 211 | + |
| 212 | + def post_to_bd(self, deviceData): |
| 213 | + """ |
| 214 | + Post json object information to BD_Connect in this format |
| 215 | + data={"sensor_data":{<all sensor data>}} |
| 216 | +
|
| 217 | + Args as data: |
| 218 | + {<netatmo station data>} |
| 219 | + Returns: |
| 220 | + { |
| 221 | + "success": "True" |
| 222 | + "HTTP Error 400": "Bad Request" |
| 223 | + } |
| 224 | + """ |
| 225 | + data = {'sensor_data': {}} |
| 226 | + data['sensor_data'].update(deviceData) |
| 227 | + response = get_json(json.dumps(data)) |
| 228 | + return response |
| 229 | + |
| 230 | +if __name__ == "__main__": |
| 231 | + """ |
| 232 | + Reads the data from Nest Protect Devices and writes it to the building |
| 233 | + depot. |
| 234 | +
|
| 235 | + Returns: |
| 236 | + { |
| 237 | + "success": "True" |
| 238 | + "HTTP Error 400": "Bad Request" |
| 239 | + } |
| 240 | + else |
| 241 | + { |
| 242 | + "Error in Device Connection" |
| 243 | + } |
| 244 | + """ |
| 245 | + from sys import exit, stderr |
| 246 | + |
| 247 | + try: |
| 248 | + auth = ClientAuth() # Get authentication key |
| 249 | + if (auth._accessToken == None): |
| 250 | + stderr.write("Please Enter the valid credentials to get\ |
| 251 | + the access token") |
| 252 | + exit(1) |
| 253 | + |
| 254 | + devList = DeviceList(auth) # Obtain the DEVICELIST |
| 255 | + nest_data = devList.deviceByType() # Get the data for the type of |
| 256 | + # devices |
| 257 | + for device_id in nest_data: |
| 258 | + try: |
| 259 | + device_data = devList.get_device_data(deviceID = device_id) |
| 260 | + resp = devList.post_to_bd(device_data) |
| 261 | + # Send Data to the BuildingDepot |
| 262 | + print "Response from connect_bd.py:\n", resp |
| 263 | + except Exception as e: |
| 264 | + print "Error in Sending data to connect_bd.py", e |
| 265 | + |
| 266 | + except Exception as e: |
| 267 | + print "Error in Device Connection", e |
0 commit comments