-
Notifications
You must be signed in to change notification settings - Fork 0
/
googleHomeDevices.py
220 lines (197 loc) · 12.1 KB
/
googleHomeDevices.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# /////////////////////////////////////////////////////////////////////////////////////////
# /////////////////////////////////////////////////////////////////////////////////////////
# Domotics Pad Client Plugin by RogueProeliator <adam@duncanwaredevelopment.com>
# /////////////////////////////////////////////////////////////////////////////////////////
# /////////////////////////////////////////////////////////////////////////////////////////
# /////////////////////////////////////////////////////////////////////////////////////////
# Python imports
# /////////////////////////////////////////////////////////////////////////////////////////
import indigo
import RPFramework
# /////////////////////////////////////////////////////////////////////////////////////////
# GoogleDeviceTypesDefinition
# Dictionary which defines the available Google Home device types and stores the
# recommended traits to support for the device type in order to allow the most complete
# control/user experience "out of the box"
# /////////////////////////////////////////////////////////////////////////////////////////
googleDeviceTypesDefn = {
'action.devices.types.DOOR':
{'Device': 'Door', 'DeviceType': 'action.devices.types.DOOR',
'Description': 'Door can be opened and closed, potentially in more than one direction.',
'Traits': ['action.devices.traits.OpenClose']},
'action.devices.types.FAN':
{'Device': 'Fan', 'DeviceType': 'action.devices.types.FAN',
'Description': 'Fans can typically be turned on and off and have speed settings. Some fans may also have additional supported modes, such as fan direction/orientation (for example, a wall unit may have settings to adjust whether it blows up or down).',
'Traits': ['action.devices.traits.FanSpeed', 'action.devices.traits.Modes', 'action.devices.traits.OnOff', 'action.devices.traits.Toggles']},
'action.devices.types.LIGHT':
{'Device': 'Light', 'DeviceType': 'action.devices.types.LIGHT',
'Description': 'This type indicates that the device gets the light bulb icon and some light synonyms/aliases.',
'Traits': ['action.devices.traits.Brightness', 'action.devices.traits.OnOff']},
'action.devices.types.LOCK':
{'Device': 'Lock', 'DeviceType': 'action.devices.types.LOCK',
'Description': 'Locks can lock, unlock, and report a locked state. Unlocking is a security sensitive action which can require two-factor authentication.',
'Traits': ['action.devices.traits.LockUnlock']},
'action.devices.types.OUTLET':
{'Device': 'Outlet', 'DeviceType': 'action.devices.types.OUTLET',
'Description': 'This type indicates that the device gets the plug icon and some outlet synonyms/aliases.',
'Traits': ['action.devices.traits.OnOff']},
'action.devices.types.SENSOR':
{'Device': 'Sensor', 'DeviceType': 'action.devices.types.SENSOR',
'Description': 'A single sensor can serve multiple functions, such as monitoring both temperature and humidity. Sensors may report either or both quantitative—for example, carbon monoxide and smoke level measured at parts per million—and qualitative measurements (such as whether air quality is healthy or unhealthy).',
'Traits': ['action.devices.traits.HumiditySetting', 'action.devices.traits.Modes', 'action.devices.traits.OnOff', 'action.devices.traits.SensorState']},
'action.devices.types.SWITCH':
{'Device': 'Switch', 'DeviceType': 'action.devices.types.SWITCH',
'Description': 'This type indicates that the device gets the switch icon and some switch synonyms/aliases.',
'Traits': ['action.devices.traits.OnOff']},
'action.devices.types.THERMOSTAT':
{'Device': 'Thermostat', 'DeviceType': 'action.devices.types.THERMOSTAT',
'Description': 'Thermostats are temperature-managing devices, with set points and modes. This separates them from heaters and AC units which may only have modes and settings (for example, high/low) vs a temperature target.',
'Traits': ['action.devices.traits.TemperatureSetting']},
'action.devices.types.WINDOW':
{'Device': 'Window', 'DeviceType': 'action.devices.types.WINDOW',
'Description': 'Windows can be opened and closed, optionally with sections that open in different directions, and may also be locked and unlocked.',
'Traits': ['action.devices.traits.LockUnlock', 'action.devices.traits.OpenClose']}
}
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Maps an Indigo device (object) to the proper/default Google Assistant device type
# that may be found in the types dictionary
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
def mapIndigoDeviceToGoogleType(device):
if isinstance(device, indigo.DimmerDevice):
return 'action.devices.types.LIGHT'
elif isinstance(device, indigo.RelayDevice):
return 'action.devices.types.SWITCH'
elif isinstance(device, indigo.ThermostatDevice):
return 'action.devices.types.THERMOSTAT'
elif isinstance(device, indigo.SensorDevice):
return 'action.devices.types.SENSOR'
else:
return ''
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Builds a Google Device sync definition for the given device utilizing the Global Props
# defined by the user as well as the Indigo type
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
def buildGoogleHomeDeviceDefinition(device):
# do not check the published flag so that this routine may be used without explicit
# options... full implementations should only pass in published devices
global_props = device.sharedProps
# retrieve the device type for the Google Assistant device
google_dev_type = global_props.get('googleClientAsstType', '')
if google_dev_type == '':
google_dev_type = mapIndigoDeviceToGoogleType(device)
# retrieve the name of the device as defined by the user
google_dev_name = global_props.get('googleClientAsstName', '')
if google_dev_name == '':
google_dev_name = device.name
device_defn_dict = {
'id': RPFramework.RPFrameworkUtils.to_unicode(device.id),
'type': googleDeviceTypesDefn[google_dev_type]['DeviceType'],
'traits': list(googleDeviceTypesDefn[google_dev_type]['Traits']),
'name': {
'defaultNames': [google_dev_name],
'name': google_dev_name
},
'willReportState': False,
'deviceInfo': {
'manufacturer': 'Indigo',
'model': device.model
}
}
return device_defn_dict
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Builds the response for Google Assistant in the format required for updating the
# Google Home Graph with the current status/state of the device provided
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
def buildGoogleHomeDeviceStatusUpdate(device):
# configuration information is found in the global properties collection
global_props = device.sharedProps
# retrieve the device type for the Google Assistant device
google_dev_type = global_props.get('googleClientAsstType', '')
if google_dev_type == '':
google_dev_type = mapIndigoDeviceToGoogleType(device)
# the status returned depends on the traits that are defined for this
# device (dependent upon the device type)
device_status_traits = {}
for trait in googleDeviceTypesDefn[google_dev_type]['Traits']:
if trait == 'action.devices.traits.Brightness':
device_status_traits['brightness'] = device.states.get('brightnessLevel', 0)
elif trait == 'action.devices.traits.ColorSetting':
# not yet implemented... could be added to Light type as an RGB state, such
# as for Hue lights
pass
elif trait == 'action.devices.traits.OnOff':
device_status_traits['on'] = device.states.get('onOffState', False)
# the online status will simply be if the device is enabled; should we look at a status
# value?
device_status_traits['online'] = device.configured and device.enabled and device.states.get('errorState', '') == ''
# return the trait/state dictionary back... note that the device ID will need
# to be set as the key to this by the calling procedure
return device_status_traits
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
# Processes the EXECUTE intent against the given device IDs; note that multiple
# commands may be present. The return is the results of the action in the format
# expected by the Google Assistant for this particular execute command
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
def processExecuteRequest(commandsList):
# the response object contains a list of devices that fall into each status; this will
# be a dictionary of status with a value being the list of devices
device_status_results = {}
# there may be multiple commands to execute in the array
for commandDefn in commandsList:
# build the list of devices against which we must execute the
# command(s) provided
devices_list = []
for deviceId in commandDefn['devices']:
indigo_device = indigo.devices[int(deviceId['id'])]
devices_list.append(indigo_device)
if indigo_device.id not in device_status_results:
device_status_results[indigo_device.id] = ''
# loop through each device, executing the requested commands only if they are
# valid for the device type found
for device in devices_list:
# determine if the device is online and configured; otherwise it cannot accept the command
is_device_online = device.configured and device.enabled and device.states.get('errorState', '') == ''
# execute the requested command, if available
if not is_device_online:
device_status_results[device.id] = 'OFFLINE'
else:
for execCommand in commandDefn['execution']:
command_id = execCommand['command']
if command_id == 'action.devices.commands.OnOff':
if execCommand['params']['on']:
indigo.device.turnOn(device.id)
else:
indigo.device.turnOff(device.id)
# mark the execution as pending since the commands are generally asynchronous in
# nature... the statuses will be updated when the device changes
if device_status_results[device.id] == '':
device_status_results[device.id] = 'PENDING'
# formulate the return... this is an array with a new result dictionary for each
# status within the devices results
command_return = []
success_devices = {'ids': [], 'status': 'SUCCESS'}
pending_devices = {'ids': [], 'status': 'PENDING'}
error_devices = {'ids': [], 'status': 'ERROR'}
offline_devices = {'ids': [], 'status': 'OFFLINE'}
# add each device result to the appropriate list
for deviceId, result in device_status_results.items():
if result == 'SUCCESS':
success_devices['ids'].append(str(deviceId))
elif result == 'PENDING':
pending_devices['ids'].append(str(deviceId))
elif result == 'ERROR':
error_devices['ids'].append(str(deviceId))
elif result == 'offlineDevices':
offline_devices['ids'].append(str(deviceId))
# build the composite results array
if len(success_devices['ids']) > 0:
command_return.append(success_devices)
if len(pending_devices['ids']) > 0:
command_return.append(pending_devices)
if len(error_devices['ids']) > 0:
command_return.append(error_devices)
if len(offline_devices['ids']) > 0:
command_return.append(offline_devices)
return command_return