From 6872da2cb1af8c880573f3f183b89b9edd5f40d8 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 23 Jan 2018 18:39:36 +0000 Subject: [PATCH] Define Echo Devices + Minor Enhancements Allows definition of Amazon ECHO devices as Indigo devices in order to increase visibility of how commands are processed. To further facilitate Alexa Routines support, you can specify to treat dim commands as simple on/off i.e. dim to zero = off otherwise on. Added slight timing delay to maybe help with non-responding commands. --- .../Contents/Info.plist | 2 +- .../Contents/Server Plugin/Devices.xml | 25 ++++ .../Server Plugin/amazon_echo_device_timer.py | 79 +++++++++++ .../Contents/Server Plugin/constants.py | 2 + .../Contents/Server Plugin/discovery.py | 3 +- .../Server Plugin/discovery_logging.py | 6 +- .../Contents/Server Plugin/hue_listener.py | 44 ++++-- .../Contents/Server Plugin/plugin.py | 134 ++++++++++++++---- README.md | 2 +- 9 files changed, 253 insertions(+), 44 deletions(-) create mode 100644 Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/amazon_echo_device_timer.py diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Info.plist b/Alexa-Hue Bridge.indigoPlugin/Contents/Info.plist index 23e2136..9588ad0 100755 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Info.plist +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Info.plist @@ -3,7 +3,7 @@ PluginVersion - 3.0.20 + 3.0.23 ServerApiVersion 2.0 IwsApiVersion diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/Devices.xml b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/Devices.xml index 5f5f0ca..9fef4ee 100644 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,4 +1,29 @@ + + Amazon Echo Device + + + + + + Boolean + Activity Detected + Activity Detected + + + String + Last Activity Detected + Last Activity Detected + + + + activityDetected + + + + Emulated Hue Bridge [Devices] diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/amazon_echo_device_timer.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/amazon_echo_device_timer.py new file mode 100644 index 0000000..b6690ff --- /dev/null +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/amazon_echo_device_timer.py @@ -0,0 +1,79 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +####################### +# +# Alexa-Hue Bridge + +# Note the "indigo" module is automatically imported and made available inside +# our global name space by the host process. We add it here so that the various +# Python IDEs will not show errors on each usage of the indigo module. +try: + import indigo +except ImportError, e: + pass + +from constants import * +import datetime +import Queue +import sys +import threading +import traceback + +PLUGIN = None + + +class ThreadAmazonEchoDeviceTimer(threading.Thread): + + def __init__(self, plugin): + threading.Thread.__init__(self) + + global PLUGIN + PLUGIN = plugin + + def run(self): + try: + PLUGIN.serverLogger.debug(u"Amazon Echo Device Timer thread initialised.") + + while True: + + try: + aeDevId = PLUGIN.globals['queues']['amazonEchoDeviceTimer'].get(True, 5) + + try: + PLUGIN.globals['amazonEchoDeviceTimers'][aeDevId].cancel() + del PLUGIN.globals['amazonEchoDeviceTimers'][aeDevId] + except: + pass + + PLUGIN.globals['amazonEchoDeviceTimers'][aeDevId] = threading.Timer(float(ECHO_DEVICE_TIMER_LIMIT), self.handleAmazonEchoDeviceTimer, [aeDevId]) + PLUGIN.globals['amazonEchoDeviceTimers'][aeDevId].start() + + except Queue.Empty: + pass + except StandardError, e: + PLUGIN.serverLogger.error(u"StandardError detected in Amazon Echo Device Timer") + errorLines = traceback.format_exc().splitlines() + for errorLine in errorLines: + PLUGIN.serverLogger.error(u"{}".format(errorLine)) + + except StandardError, e: + PLUGIN.serverLogger.error(u"StandardError detected in Amazon Echo Device Timer thread. Line '{}' has error='{}'".format(sys.exc_traceback.tb_lineno, e)) + + PLUGIN.serverLogger.debug(u"Amazon Echo Device Timer thread ended.") + + def handleAmazonEchoDeviceTimer(self, aeDevId): + + try: + PLUGIN.serverLogger.debug(u'handleAmazonEchoDeviceTimer invoked for {}'.format(indigo.devices[aeDevId].name)) + + try: + del PLUGIN.globals['amazonEchoDeviceTimers'][aeDevId] + except: + pass + + indigo.devices[aeDevId].updateStateOnServer("activityDetected", False, uiValue="No Activity") + indigo.devices[aeDevId].updateStateImageOnServer(indigo.kStateImageSel.SensorOff) + + except StandardError, e: + PLUGIN.serverLogger.error(u"handleAmazonEchoDeviceTimer error detected. Line '%s' has error='%s'" % (sys.exc_traceback.tb_lineno, e)) + diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/constants.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/constants.py index e56fb11..7900223 100644 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/constants.py +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/constants.py @@ -15,6 +15,8 @@ # DEVICE_LIMIT = 5 # TESTING (30-JUL-2017) DEVICE_LIMIT = 20 # Imposed by the built-in Hue support in Alexa EMULATED_HUE_BRIDGE_TYPEID = 'emulatedHueBridge' # See definition in Devices.xml +ECHO_DEVICE_TYPEID = 'echoDevice' # See definition in Devices.xml +ECHO_DEVICE_TIMER_LIMIT = 15.0 # In seconds - the amount of time an Echo device will show active after a command is received NETWORK_AVAILABLE_CHECK_REMOTE_SERVER = 'www.google.com' NETWORK_AVAILABLE_CHECK_LIMIT_ONE = 6 # Retry Count diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery.py index 4398228..c806604 100755 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery.py +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery.py @@ -93,7 +93,7 @@ def run(self): while True: sock.sendto(self.broadcast_packet, (BCAST_IP, UPNP_PORT)) for x in range(BROADCAST_INTERVAL): - time.sleep(1) + time.sleep(1.5) # Following code will only time out the Broadcaster Thread if PLUGIN.globals['alexaHueBridge'][self.ahbDevId]['discoveryExpiration'] > 0 (valid values 0 thru 10 inclusive) # A value of zero means 'always on' if PLUGIN.globals['alexaHueBridge'][self.ahbDevId]['discoveryExpiration'] and time.time() > end_time: @@ -190,6 +190,7 @@ def stop(self): self.interrupted = True def respond(self, addr): + time.sleep(1.5) PLUGIN.responderLogger.debug("Responder.respond called from address {}\n{}".format(str(addr), self.response_packet)) PLUGIN.responderLogger.debug("Responder.respond: creating output_socket") output_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery_logging.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery_logging.py index 7e8db05..53b8768 100644 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery_logging.py +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/discovery_logging.py @@ -12,6 +12,7 @@ except ImportError, e: pass +import datetime import Queue import sys import threading @@ -35,10 +36,11 @@ def run(self): while True: try: - discoveryId, client_address, discoveryName, discoveryList = PLUGIN.globals['queues']['discoveryLogging'].get(True, 5) + discoveryId, client_name_address, discoveryName, discoveryList = PLUGIN.globals['queues']['discoveryLogging'].get(True, 5) + if len(discoveryList) > 0: discoveryList.sort() - PLUGIN.serverLogger.info(u"Alexa-Hue Bridge '{}' responding to Alexa discovery from {} [request id: {}] ...".format(discoveryName, client_address, discoveryId)) + PLUGIN.serverLogger.info(u"Alexa-Hue Bridge '{}' responding to Alexa discovery from {} [request id: {}] ...".format(discoveryName, client_name_address, discoveryId)) deviceCount = 0 for deviceName in discoveryList: deviceCount += 1 diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/hue_listener.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/hue_listener.py index 7852283..4d6a451 100755 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/hue_listener.py +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/hue_listener.py @@ -11,6 +11,8 @@ import indigo except ImportError, e: pass + +import datetime import json import mimetypes import re @@ -108,9 +110,11 @@ def run(self): except SocketServer.socket.error as exc: if self.stopped: self.retryLimit = 0 # Force Thread end - elif exc.args[0] != 48: # 48 = Port In Use + elif exc.args[0] != 48: # 48 = Port In Use + PLUGIN.serverLogger.debug(u"Socket Error {} for {} accessing Port: {} ".format(exc.args[0], indigo.devices[self.ahbDevId].name, PLUGIN.globals['alexaHueBridge'][self.ahbDevId]['port'])) raise else: + PLUGIN.serverLogger.debug(u"Socket Error {} for {} accessing Port: {} ".format(exc.args[0], indigo.devices[self.ahbDevId].name, PLUGIN.globals['alexaHueBridge'][self.ahbDevId]['port'])) self.retry = True self.retryCount += 1 PLUGIN.serverLogger.error("'{}' unable to access HTTP port {} as already in use - waiting 15 seconds to try again (will retry {} more times)".format(indigo.devices[self.ahbDevId].name, PLUGIN.globals['alexaHueBridge'][self.ahbDevId]['port'], self.retryLimit)) @@ -143,9 +147,29 @@ class HttpdRequestHandler(SocketServer.BaseRequestHandler): def handle(self): try: + client_ip = self.client_address[0] + client_port = self.client_address[1] + self.client_name_address = u"{}:{}".format(self.client_address[0],self.client_address[1]) + for aeDevId in PLUGIN.globals['amazonEchoDevices']: + if PLUGIN.globals['amazonEchoDevices'][aeDevId] == self.client_address[0]: + self.client_name_address = u"'{}':{}".format(indigo.devices[aeDevId].name, self.client_address[1]) + datetimeNowUi = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + keyValueList = [ + {'key': 'activityDetected', 'value': True, 'uiValue': 'Active'}, + {'key': 'lastActiveDateTime', 'value': datetimeNowUi} + ] + indigo.devices[aeDevId].updateStatesOnServer(keyValueList) + + indigo.devices[aeDevId].updateStateImageOnServer(indigo.kStateImageSel.SensorOn) + + PLUGIN.globals['queues']['amazonEchoDeviceTimer'].put(aeDevId) # Queue a message to set a timer to set device inactive after a short period of time + + break + ahbDev = indigo.devices[self.server.alexaHueBridgeId] data = self.request.recv(1024) - PLUGIN.serverLogger.debug(str("HttpdRequestHandler.handle invoked for '{}' by {} with data:\n{}\n\n".format(ahbDev.name, self.client_address, data))) + PLUGIN.serverLogger.debug(str("HttpdRequestHandler.handle invoked for '{}' by {} with data:\n{}\n\n".format(ahbDev.name, self.client_name_address, data))) get_match = re.search(r'GET (.*?(/[^\s^/]*?))\s', data) if get_match: @@ -162,6 +186,10 @@ def handle(self): if put_reponse_data is not None: self.send_headers("file.json") self.request.sendall(put_reponse_data) + + a = 1 + b = 2 + c = a + b except StandardError, e: PLUGIN.serverLogger.error(u"StandardError detected in HttpdRequestHandler for '{}'. Line '{}' has error='{}'".format(ahbDev.name, sys.exc_traceback.tb_lineno, e)) @@ -180,7 +208,7 @@ def send_headers(self, file): def get_response(self, ahbDevId, request_string): try: ahbDev = indigo.devices[ahbDevId] - PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, request string: {}".format(ahbDev.name, self.client_address, request_string)) + PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, request string: {}".format(ahbDev.name, self.client_name_address, request_string)) # get device list get_match = re.search(r'(/[^\s^/]+)$',request_string) @@ -192,14 +220,14 @@ def get_response(self, ahbDevId, request_string): request_file = "/index.html" PLUGIN.serverLogger.debug("hue_listener.get_response request file: " + request_file) if re.search(r'/lights$',request_string): - PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, discovery request, returning the full list of devices in Hue JSON format".format(ahbDev.name, self.client_address)) + PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, discovery request, returning the full list of devices in Hue JSON format".format(ahbDev.name, self.client_name_address)) return self.getHueDeviceJSON(ahbDevId) # Get individual device status - I'm actually not sure what the last two cases are for, but this is taken directly # from the source script so I just left it in. get_device_match = re.search(r'/lights/([a-fA-F0-9]+)$',request_string) if get_device_match: - PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, found /lights/ in request string: {}".format(ahbDev.name, self.client_address, request_string)) + PLUGIN.serverLogger.debug("hue_listener.get_response for '{}' from {}, found /lights/ in request string: {}".format(ahbDev.name, self.client_name_address, request_string)) return self.getHueDeviceJSON(ahbDevId, get_device_match.group(1)) # Hash key elif request_file in FILE_LIST: PLUGIN.serverLogger.debug("hue_listener.get_response for '{}', serving from a local file: {}".format(ahbDev.name, request_file)) @@ -271,9 +299,9 @@ def put_response(self, ahbDevId, request_string,request_data): list.append({"success":{key:request[key]}}) if key.lower() == "on": # ON == TRUE, OFF == FALSE - PLUGIN.turnOnOffDevice(ahbDevId, alexaDeviceNameKey, request[key]) + PLUGIN.turnOnOffDevice(self.client_name_address, ahbDevId, alexaDeviceNameKey, request[key]) if key.lower() == "bri": - PLUGIN.setDeviceBrightness(ahbDevId, alexaDeviceNameKey, int(round((float(request[key]) * 100.0 ) / 255.0))) # 2017-AUG-15 + PLUGIN.setDeviceBrightness(self.client_name_address, ahbDevId, alexaDeviceNameKey, int(round((float(request[key]) * 100.0 ) / 255.0))) # 2017-AUG-15 return json.dumps(list) except StandardError, e: PLUGIN.serverLogger.error(u"StandardError detected in put_response for device '{}'. Line '{}' has error='{}'".format(ahbDev.name, sys.exc_traceback.tb_lineno, e)) @@ -317,7 +345,7 @@ def getHueDeviceJSON(self, ahbDevId, alexaDeviceHashedKey=None): PLUGIN.serverLogger.debug('deviceListDict: {}'.format(str(deviceListDict))) PLUGIN.serverLogger.debug(u"'{}' json data: \n{}".format(self.ahbDevName, json.dumps(deviceListDict, indent=4))) if PLUGIN.globals['showDiscoveryInEventLog']: - PLUGIN.globals['queues']['discoveryLogging'].put([self.discoveryId, self.client_address, self.ahbDevName, PLUGIN.globals['discoveryLists'][self.discoveryId]]) + PLUGIN.globals['queues']['discoveryLogging'].put([self.discoveryId, self.client_name_address, self.ahbDevName, PLUGIN.globals['discoveryLists'][self.discoveryId]]) PLUGIN.serverLogger.debug(u"getHueDeviceJSON invocation completed for '{}'".format(self.ahbDevName)) return json.dumps(deviceListDict) except Exception, e: diff --git a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/plugin.py b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/plugin.py index f20d1dc..9fe6462 100755 --- a/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/plugin.py @@ -22,6 +22,7 @@ import traceback import uuid +from amazon_echo_device_timer import ThreadAmazonEchoDeviceTimer from constants import * from ghpu import GitHubPluginUpdater from discovery import Broadcaster, Responder @@ -89,6 +90,9 @@ def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): # Initialising Message self.generalLogger.info(u"Alexa-Hue Bridge initialising . . .") + self.globals['amazonEchoDevices'] = {} + self.globals['amazonEchoDeviceTimers'] = {} + self.globals['alexaHueBridge'] = {} self.globals['alexaHueBridge']['publishedOtherAlexaDevices'] = {} self.globals['alexaHueBridge']['publishedHashKeys'] = {} @@ -145,11 +149,14 @@ def startup(self): # Create process queue self.globals['queues'] = {} self.globals['queues']['discoveryLogging'] = Queue.PriorityQueue() # Used to queue commands to be sent to discovery logging + self.globals['queues']['amazonEchoDeviceTimer'] = Queue.Queue() # Used to queue commands to be sent to Amazon Echo Timer Thread (for setting device state 'No Activity') - # define and start threads that will send messages to & receive messages from the lifx devices + # define and start threads for Discovery Logging and Amazon Echo Device Timers self.globals['threads'] = {} self.globals['threads']['discoveryLogging'] = ThreadDiscoveryLogging(self) self.globals['threads']['discoveryLogging'].start() + self.globals['threads']['AmazonEchoDeviceTimer'] = ThreadAmazonEchoDeviceTimer(self) + self.globals['threads']['AmazonEchoDeviceTimer'].start() def shutdown(self): self.methodTracer.threaddebug(u"CLASS: Plugin") @@ -356,10 +363,38 @@ def is_connected(): except self.StopThread: self.generalLogger.info(u"Alexa-Hue Bridge shutdown requested") + + #################################################### + # start an Alexa-Hue Bridge or an Amazon Echo device + #################################################### + def deviceStartComm(self, dev): + if dev.deviceTypeId == EMULATED_HUE_BRIDGE_TYPEID: + self.deviceStartCommAhb(dev) + elif dev.deviceTypeId == ECHO_DEVICE_TYPEID: + self.deviceStartCommAe(dev) + + ################################################ + # start the Amazon Echo device (aka aeDev) + ################################################ + def deviceStartCommAe(self, aeDev): + try: + keyValueList = [ + {'key': 'activityDetected', 'value': False, 'uiValue': 'No Activity'}, + {'key': 'lastActiveDateTime', 'value': "---"} + ] + aeDev.updateStatesOnServer(keyValueList) + + aeDev.updateStateImageOnServer(indigo.kStateImageSel.SensorOff) + + self.globals['amazonEchoDevices'][aeDev.id] = aeDev.address + + except StandardError, e: + self.generalLogger.error(u"StandardError detected in deviceStartComm [AE] for '{}'. Line '{}' has error='{}'".format(indigo.devices[aeDev.id].name, sys.exc_traceback.tb_lineno, e)) + ################################################ # start the Alexa-Hue Bridge device (aka ahbDev) ################################################ - def deviceStartComm(self, ahbDev): + def deviceStartCommAhb(self, ahbDev): try: self.methodTracer.threaddebug(u"CLASS: Plugin") self.generalLogger.debug(u"DEVICE START: {}".format(ahbDev.name)) @@ -514,15 +549,18 @@ def deviceStartComm(self, ahbDev): self.generalLogger.info(u"Alexa-Hue Bridge '{}' started: Host: {} Port: {}".format(self.globals['alexaHueBridge'][ahbDev.id]['hubName'], self.globals['alexaHueBridge'][ahbDev.id]['host'], self.globals['alexaHueBridge'][ahbDev.id]['port'])) except StandardError, e: - self.generalLogger.error(u"StandardError detected in deviceStartComm for '{}'. Line '{}' has error='{}'".format(indigo.devices[ahbDev.id].name, sys.exc_traceback.tb_lineno, e)) + self.generalLogger.error(u"StandardError detected in deviceStartComm [AHB] for '{}'. Line '{}' has error='{}'".format(indigo.devices[ahbDev.id].name, sys.exc_traceback.tb_lineno, e)) def deviceStopComm(self, ahbDev): self.methodTracer.threaddebug(u"CLASS: Plugin") - stoppedId = ahbDev.id - stoppedName = ahbDev.name - try: + if ahbDev.deviceTypeId != EMULATED_HUE_BRIDGE_TYPEID: + return + + stoppedId = ahbDev.id + stoppedName = ahbDev.name + if 'webServer' in self.globals['alexaHueBridge'][stoppedId]: if self.globals['alexaHueBridge'][stoppedId]['webServer']: self.globals['alexaHueBridge'][stoppedId]['webServer'].stop() @@ -793,7 +831,11 @@ def retrieveOtherPublishedDevices(self, ahbDevId): def validateDeviceConfigUi(self, valuesDict, typeId, ahbDevId): self.methodTracer.threaddebug(u"CLASS: Plugin") - if typeId == EMULATED_HUE_BRIDGE_TYPEID: + if typeId == ECHO_DEVICE_TYPEID: + valuesDict['address'] = valuesDict.get("address", '- none -') + + elif typeId == EMULATED_HUE_BRIDGE_TYPEID: + self.generalLogger.debug(u"Validating Device config for type: " + typeId) self.generalLogger.debug(u"validateDeviceConfigUi VALUESDICT = {}".format(valuesDict)) @@ -835,17 +877,36 @@ def validateDeviceConfigUi(self, valuesDict, typeId, ahbDevId): return True, valuesDict - def closedDeviceConfigUi(self, valuesDict, userCancelled, typeId, ahbDevId): + def closedDeviceConfigUi(self, valuesDict, userCancelled, typeId, devId): self.methodTracer.threaddebug(u"CLASS: Plugin") try: - self.generalLogger.debug(u"'closePrefsConfigUi' called with userCancelled = {}".format(str(userCancelled))) + self.generalLogger.debug(u"'closePrefsConfigUi' called with userCancelled = {}".format(str(userCancelled))) if userCancelled: return - if typeId != EMULATED_HUE_BRIDGE_TYPEID: - return + if typeId == ECHO_DEVICE_TYPEID: + valuesDict = self.closedAeDeviceConfigUi(valuesDict, userCancelled, typeId, devId) + if typeId == EMULATED_HUE_BRIDGE_TYPEID: + valuesDict = self.closedAhbDeviceConfigUi(valuesDict, userCancelled, typeId, devId) + + return valuesDict + + except StandardError, e: + self.generalLogger.error(u"StandardError detected in closedDeviceConfigUi for '{}'. Line '{}' has error='{}'".format(indigo.devices[devId].name, sys.exc_traceback.tb_lineno, e)) + + def closedAeDeviceConfigUi(self, valuesDict, userCancelled, typeId, aeDevId): + self.methodTracer.threaddebug(u"CLASS: Plugin") + try: + self.globals['amazonEchoDevices'][aeDevId] = valuesDict.get("address", '- none -') + return valuesDict + except StandardError, e: + self.generalLogger.error(u"StandardError detected in closedDeviceConfigUi [AE] for '{}'. Line '{}' has error='{}'".format(indigo.devices[aeDevId].name, sys.exc_traceback.tb_lineno, e)) + + def closedAhbDeviceConfigUi(self, valuesDict, userCancelled, typeId, ahbDevId): + self.methodTracer.threaddebug(u"CLASS: Plugin") + try: port = valuesDict.get("port", 'auto') port_changed = False try: @@ -853,8 +914,8 @@ def closedDeviceConfigUi(self, valuesDict, userCancelled, typeId, ahbDevId): port_changed = True except: port_changed = True - if port_changed: - self.globals['alexaHueBridge'][ahbDevId]['forceDeviceStopStart'] = True + if port_changed: + self.globals['alexaHueBridge'][ahbDevId]['forceDeviceStopStart'] = True self.globals['alexaHueBridge'][ahbDevId]['autoStartDiscovery'] = valuesDict.get("autoStartDiscovery", True) @@ -884,23 +945,25 @@ def closedDeviceConfigUi(self, valuesDict, userCancelled, typeId, ahbDevId): return valuesDict except StandardError, e: - self.generalLogger.error(u"StandardError detected in closedDeviceConfigUi for '{}'. Line '{}' has error='{}'".format(indigo.devices[ahbDevId].name, sys.exc_traceback.tb_lineno, e)) + self.generalLogger.error(u"StandardError detected in closedDeviceConfigUi [AHB] for '{}'. Line '{}' has error='{}'".format(indigo.devices[ahbDevId].name, sys.exc_traceback.tb_lineno, e)) - - ######################################## - # The next two methods should catch when a device name changes in Indigo and when a device we have published + # The next method should catch when a device we have published # gets deleted - we'll just rebuild the device list cache in those situations. + # + Handle deletion of this plugin's Amazon Echo Devices ######################################## def deviceDeleted(self, dev): self.methodTracer.threaddebug(u"CLASS: Plugin") - if dev.deviceTypeId != EMULATED_HUE_BRIDGE_TYPEID: + if dev.deviceTypeId != EMULATED_HUE_BRIDGE_TYPEID and dev.deviceTypeId != ECHO_DEVICE_TYPEID: for ahbDevId in self.globals['alexaHueBridge']: if 'publishedAlexaDevices' in self.globals['alexaHueBridge'][ahbDevId]: if dev.id in self.globals['alexaHueBridge'][ahbDevId]['publishedAlexaDevices']: self.generalLogger.info(u"A device ({}) that was published has been deleted - you'll probably want use the Alexa app to forget that device.".format(dev.name)) self.refreshDeviceList(ahbDevId) + elif dev.deviceTypeId == ECHO_DEVICE_TYPEID: + if dev.id in self.globals['amazonEchoDevices']: + del self.globals['amazonEchoDevices'][dev.id] super(Plugin, self).deviceDeleted(dev) @@ -956,7 +1019,7 @@ def actionsToListIncludingNone(self, filter="", valuesDict=None, typeId="", targ # Set a default with id 0 # Iterates through the action list - actionList = [(0, 'NO ACTION')] + actionList = [(0, 'NO ACTION'),(1, 'HANDLE DIM AS ON/OFF')] for action in indigo.actionGroups: actionList.append((action.id, action.name)) return actionList @@ -1572,7 +1635,7 @@ def validateActionConfigUi(self, valuesDict, typeId, devId): # alexaDeviceName is the name of the device known to Alexa # turnOn is a boolean to indicate on/off ######################################## - def turnOnOffDevice(self, ahbDevId, alexaDeviceNameKey, turnOn): + def turnOnOffDevice(self, client_name_address, ahbDevId, alexaDeviceNameKey, turnOn): ahbDev = indigo.devices[ahbDevId] publishedAlexaDevices = self.jsonLoadsProcess(ahbDev.pluginProps['alexaDevices']) @@ -1585,13 +1648,13 @@ def turnOnOffDevice(self, ahbDevId, alexaDeviceNameKey, turnOn): devId = int(alexaDeviceData['devId']) name = indigo.devices[devId].name onOff = 'ON' if turnOn else 'OFF' - self.generalLogger.info(u"Set on state of Alexa device \"{}\" [\"{}\"] to {}".format(alexaDeviceName, name, onOff)) + self.generalLogger.info(u"Request received from {}: Setting on state of Alexa device \"{}\" [\"{}\"] to {}".format(client_name_address, alexaDeviceName, name, onOff)) if turnOn: indigo.device.turnOn(devId) else: indigo.device.turnOff(devId) except: - self.generalLogger.error(u"Indigo Device with id {} doesn't exist for Alexa Device \"{}\" - Edit Alexa Hue Bridge \"{}\" and correct error.".format(devId, alexaDeviceName, ahbDev.name)) + self.generalLogger.error(u"Request received from {}: Indigo Device with id {} doesn't exist for Alexa Device \"{}\" - Edit Alexa Hue Bridge \"{}\" and correct error.".format(client_name_address, devId, alexaDeviceName, ahbDev.name)) elif alexaDeviceData['mode'] == 'A': # Action onOffVarId = int(alexaDeviceData['variableOnOffId']) actionOnId = int(alexaDeviceData['actionOnId']) @@ -1606,9 +1669,9 @@ def turnOnOffDevice(self, ahbDevId, alexaDeviceNameKey, turnOn): indigo.actionGroup.execute(actionOnId) else: indigo.actionGroup.execute(actionOffId) - self.generalLogger.info(u"Set on state of Alexa device \"{}\" to {}".format(alexaDeviceName, onOff)) + self.generalLogger.info(u"Request received from {}: Setting on state of Alexa device \"{}\" to {}".format(client_name_address, alexaDeviceName, onOff)) except: - self.generalLogger.error(u"Alexa Device \"{}\" doesn't have supporting Indigo Actions.".format(alexaDeviceName)) + self.generalLogger.error(u"Request received from {}: Alexa Device \"{}\" doesn't have supporting Indigo Actions.".format(client_name_address, alexaDeviceName)) ######################################## @@ -1618,7 +1681,7 @@ def turnOnOffDevice(self, ahbDevId, alexaDeviceNameKey, turnOn): # alexaDeviceName is the name of the device known to Alexa # brightness is the brightness in the range 0-100 ######################################## - def setDeviceBrightness(self, ahbDevId, alexaDeviceNameKey, brightness): + def setDeviceBrightness(self, client_name_address, ahbDevId, alexaDeviceNameKey, brightness): ahbDev = indigo.devices[ahbDevId] publishedAlexaDevices = self.jsonLoadsProcess(ahbDev.pluginProps['alexaDevices']) @@ -1632,20 +1695,29 @@ def setDeviceBrightness(self, ahbDevId, alexaDeviceNameKey, brightness): dev = indigo.devices[devId] name = dev.name except: - self.generalLogger.error(u"Indigo Device with id {} doesn't exist for Alexa Device \"{}\" - Edit Alexa Hue Bridge \"{}\" and correct error.".format(devId, alexaDeviceName, ahbDev.name)) + self.generalLogger.error(u"Request received from {}: Indigo Device with id {} doesn't exist for Alexa Device \"{}\" - Edit Alexa Hue Bridge \"{}\" and correct error.".format(client_name_address, devId, alexaDeviceName, ahbDev.name)) return if isinstance(dev, indigo.DimmerDevice): - self.generalLogger.info(u"Set brightness of Alexa device \"{}\" [\"{}\"] to {}".format(alexaDeviceName, name, brightness)) + self.generalLogger.info(u"Request received from {}: Setting brightness of Alexa device \"{}\" [\"{}\"] to {}".format(client_name_address, alexaDeviceName, name, brightness)) indigo.dimmer.setBrightness(dev, value=brightness) else: - self.generalLogger.error(u"Alexa Device \"{}\" [\"{}\"] doesn't support dimming.".format(alexaDeviceName, name)) + self.generalLogger.error(u"Request received from {}: Alexa Device \"{}\" [\"{}\"] doesn't support dimming.".format(client_name_address, alexaDeviceName, name)) elif alexaDevice['mode'] == 'A': # Action dimVarId = int(alexaDevice['variableDimId']) actionDimId = int(alexaDevice['actionDimId']) - if dimVarId == 0 and actionDimId == 0: - self.generalLogger.error(u"Alexa Device \"{}\" doesn't support dimming.".format(alexaDeviceName)) + if actionDimId == 1: # HANDLE DIM AS ON/OFF + if dimVarId != 0: + brightnessVar = str(brightness) + indigo.variable.updateValue(dimVarId, value=brightnessVar) + if brightness > 0: + turnOn = True + else: + turnOn = False + self.turnOnOffDevice(client_name_address, ahbDevId, alexaDeviceNameKey, turnOn) + elif dimVarId == 0 and actionDimId == 0: + self.generalLogger.error(u"Request received from {}: Alexa Device \"{}\" doesn't support dimming.".format(client_name_address, alexaDeviceName)) else: - self.generalLogger.info(u"Set brightness of Alexa device \"{}\" to {}".format(alexaDeviceName, brightness)) + self.generalLogger.info(u"Request received from {}: Setting brightness of Alexa device \"{}\" to {}".format(client_name_address, alexaDeviceName, brightness)) if dimVarId != 0: brightness = str(brightness) indigo.variable.updateValue(dimVarId, value=brightness) diff --git a/README.md b/README.md index 9c94dff..614bf6b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Overview -The Alexa-Hue Bridge is a plugin for version 7+ of the [Indigo Home Automation system][1]. Version 3.x of this plugin emulates multiple Philips Hue bridges to publish Indigo actions and devices (on/off and dimmer types only) to most Amazon Alexa devices (Echo [Gen 1], Dot [Gen 1 & 2], FireTV, etc.). +The Alexa-Hue Bridge is a plugin for version 7+ of the [Indigo Home Automation system][1]. Version 3.0.23+ of this plugin emulates multiple Philips Hue bridges to publish Indigo actions and devices (on/off and dimmer types only) to these Amazon Alexa devices: Echo [Gen 1], Echo Dot [Gen 1 & 2]. Use the latest 1.x release for Indigo 6.