Skip to content

Commit

Permalink
Define Echo Devices + Minor Enhancements
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Jon authored and Jon committed Jan 23, 2018
1 parent 9c1fffd commit 6872da2
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 44 deletions.
2 changes: 1 addition & 1 deletion Alexa-Hue Bridge.indigoPlugin/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>PluginVersion</key>
<string>3.0.20</string>
<string>3.0.23</string>
<key>ServerApiVersion</key>
<string>2.0</string>
<key>IwsApiVersion</key>
Expand Down
25 changes: 25 additions & 0 deletions Alexa-Hue Bridge.indigoPlugin/Contents/Server Plugin/Devices.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
<Devices>
<Device type="custom" id="echoDevice">
<Name>Amazon Echo Device</Name>
<ConfigUI>
<Field type="textfield" id="address" defaultValue="- none -" hidden="false">
<Label>IP Address:</Label>
</Field>
</ConfigUI>
<States>
<State id="activityDetected">
<ValueType>Boolean</ValueType>
<TriggerLabel>Activity Detected</TriggerLabel>
<ControlPageLabel>Activity Detected</ControlPageLabel>
</State>
<State id="lastActiveDateTime">
<ValueType>String</ValueType>
<TriggerLabel>Last Activity Detected</TriggerLabel>
<ControlPageLabel>Last Activity Detected</ControlPageLabel>
</State>
</States>

<UiDisplayStateId>activityDetected</UiDisplayStateId>
</Device>



<Device type="relay" id="emulatedHueBridge">
<Name>Emulated Hue Bridge [Devices]</Name>
<ConfigUI>
Expand Down
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
except ImportError, e:
pass

import datetime
import Queue
import sys
import threading
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import indigo
except ImportError, e:
pass

import datetime
import json
import mimetypes
import re
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand All @@ -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))

Expand All @@ -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)
Expand All @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit 6872da2

Please sign in to comment.