Thanks for all the help with this guys - I used bits and pieces from all of your posts to get something working pretty good for my home.
I ended up reworking the CSV script that someone here posted as a starting point, and used a MySQL database instead. I have some tables in here to define the devices in my system, and a separate table with speech phrases for each "device" - and then with some parsing and some more sophisticated queries (Natural language full text search which then falls back to a LIKE query) - it seems to get the proper device almost every time, but to be fair, I did spend quite a bit of time on my "speechphrase" table, making sure I put in most every combination I could think of for phrases that people would use to control a specific device. I doubt that the LIKE query is actually all the helpful, unless the phrase is really short to begin with. Using the MySQL database was also nice because it allows me to store custom responses for the different scenes or devices as well to make it a little bit more fun.
There was some brute forcing as far as the parsing goes... I was surprised to see Alexa return numbers in full english - like "twenty three" instead of "23" - so that took a bit of messing around for when I say something like - "set office light to level 23". Parsing the integer out would have been WAY easier. I'm a bit of a python hack though... so I'm sure somebody that knows what they are doing could probably have found a much easier way. -So after some research on this, it seems like if you were able to make use of slot variables, then Alexa could return numbers to you instead of language - but that's ok - I like it this way because it gives the most flexibility, if I understand how this works.
Overall, seems to work very well. The only issue that I have, is that it seems about 1 out of every 5 times or so - The response back to Amazon doesn't work. Seems like maybe it's a timing thing or something, but I'm not sure. If anyone has any ideas on that, please let me know.
Next up - tying in Google Assistant / Siri...
If any of you Alexa gurus out there know how to rework the code that Brandon posted to use the smart home groups instead of the lambda invocation word - I'd be interested in that. It seems much more natural to not have to say the invocation phrase. I named my invocation phrase to "the house" - so that you say "Alexa, ask the house to turn off...whatever..." - and it's not too bad that way, but not as nice as just saying "Alexa, turn off ... whatever"
Code: Select all
import MySQLdb
MySQLdb_IP = "xx"
MySQLdb_User = "xx"
MySQLdb_Password = "xx"
MySQLdb_Database = "xx"
#Initialize the response in case this script does not set it to something valid
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "EventGhost Macro Error", False, False)
#Use this line to test without Alexa
#voice = "change the television input to tivo"
voice = eg.event.payload[0]
print "Incoming Alexa Speech = " + voice
#Initialize Zone, SubID, DeviceName, ClassType, ResponseOverride, and Level variables
zone=-1
subid=-1
devicename=""
classtype=""
responseoverride=""
level=-1
levelsuffix=""
#Filter unneeded words from speech for better matching
voice=voice.replace("the"," ")
voice=voice.replace("to"," " )
voice=voice.replace("turn"," " )
voice=voice.replace("set"," " )
voice=voice.replace("please"," " )
voice=voice.replace("change"," " )
voice=voice.replace("level"," " )
voice=voice.replace("percent"," " )
voice=voice.replace("activate"," " )
voice=voice.replace("scene"," " )
voice=voice.replace("'","" )
#Remove whitespace & split into words
voice=voice.strip()
voicewords=voice.split()
#Parse sublevels out if there is one present, before handling levels
if "dash one" in voice:
levelsuffix="-1"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash two" in voice:
levelsuffix="-2"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash three" in voice:
levelsuffix="-3"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash four" in voice:
levelsuffix="-4"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash five" in voice:
levelsuffix="-5"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash six" in voice:
levelsuffix="-6"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash seven" in voice:
levelsuffix="-7"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash eight" in voice:
levelsuffix="-8"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
elif "dash nine" in voice:
levelsuffix="-9"
voicewords[voicewords.index("dash")+1] = ""
voicewords[voicewords.index("dash")] = ""
if "high" in voicewords:
level=100
voicewords[voicewords.index("high")] = ""
elif "medium" in voicewords and "high" in voicewords:
level=75
voicewords[voicewords.index("medium")] = ""
voicewords[voicewords.index("high")] = ""
elif "medium" in voicewords:
level=50
voicewords[voicewords.index("medium")] = ""
elif "low" in voicewords:
level=25
voicewords[voicewords.index("low")] = ""
elif "on" in voicewords:
level=100
voicewords[voicewords.index("on")] = ""
elif "off" in voicewords:
level=0
voicewords[voicewords.index("off")] = ""
elif "ten" in voicewords:
level=10
voicewords[voicewords.index("ten")] = ""
elif "eleven" in voicewords:
level=11
voicewords[voicewords.index("eleven")] = ""
elif "twelve" in voicewords:
level=12
voicewords[voicewords.index("twelve")] = ""
elif "thirteen" in voicewords:
level=13
voicewords[voicewords.index("thirteen")] = ""
elif "fourteen" in voicewords:
level=14
voicewords[voicewords.index("fourteen")] = ""
elif "fifteen" in voicewords:
level=15
voicewords[voicewords.index("fifteen")] = ""
elif "sixteen" in voicewords:
level=16
voicewords[voicewords.index("sixteen")] = ""
elif "seventeen" in voicewords:
level=17
voicewords[voicewords.index("seventeen")] = ""
elif "eighteen" in voicewords:
level=18
voicewords[voicewords.index("eighteen")] = ""
elif "nineteen" in voicewords:
level=19
voicewords[voicewords.index("nineteen")] = ""
elif "twenty" in voicewords:
level=20
voicewords[voicewords.index("twenty")] = ""
elif "thirty" in voicewords:
level=30
voicewords[voicewords.index("thirty")] = ""
elif "forty" in voicewords:
level=40
voicewords[voicewords.index("fourty")] = ""
elif "fifty" in voicewords:
level=50
voicewords[voicewords.index("fifty")] = ""
elif "sixty" in voicewords:
level=60
voicewords[voicewords.index("sixty")] = ""
elif "seventy" in voicewords:
level=70
voicewords[voicewords.index("seventy")] = ""
elif "eighty" in voicewords:
level=80
voicewords[voicewords.index("eighty")] = ""
elif "ninety" in voicewords:
level=90
voicewords[voicewords.index("ninety")] = ""
elif "hundred" in voicewords:
level=100
voicewords[voicewords.index("hundred")] = ""
voicewords[voicewords.index("one")] = ""
if level>=20 or level==-1:
if "zero" in voicewords:
if level==-1:
level=0
else:
level=level+0
voicewords[voicewords.index("zero")] = ""
elif "one" in voicewords:
if level==-1:
level=1
else:
level=level+1
voicewords[voicewords.index("one")] = ""
elif "two" in voicewords:
if level==-1:
level=2
else:
level=level+2
voicewords[voicewords.index("two")] = ""
elif "three" in voicewords:
if level==-1:
level=3
else:
level=level+3
voicewords[voicewords.index("three")] = ""
elif "four" in voicewords:
if level==-1:
level=4
else:
level=level+4
voicewords[voicewords.index("four")] = ""
elif "five" in voicewords:
if level==-1:
level=5
else:
level=level+5
voicewords[voicewords.index("five")] = ""
elif "six" in voicewords:
if level==-1:
level=6
else:
level=level+6
voicewords[voicewords.index("six")] = ""
elif "seven" in voicewords:
if level==-1:
level=7
else:
level=level+7
voicewords[voicewords.index("seven")] = ""
elif "eight" in voicewords:
if level==-1:
level=8
else:
level=level+8
voicewords[voicewords.index("eight")] = ""
elif "nine" in voicewords:
if level==-1:
level=9
else:
level=level+9
voicewords[voicewords.index("nine")] = ""
#Remove empty words removed by level extraction
voicewords=filter(None, voicewords)
#Use MySQL Database to search speech phrases
searchstring=""
for word in voicewords:
searchstring+=" +%s" % (word.strip())
db = MySQLdb.connect(MySQLdb_IP,MySQLdb_User,MySQLdb_Password,MySQLdb_Database)
cursor = db.cursor()
strQuery="SELECT D.DeviceID AS Zone, S.SubID, D.Device, C.Class, S.ResponseOverride FROM Speech AS S JOIN Devices AS D ON S.DeviceID=D.DeviceID JOIN DeviceClasses AS C ON D.ClassID=C.ClassID WHERE MATCH (SpeechPhrase) AGAINST ('" + searchstring + "' IN NATURAL LANGUAGE MODE) LIMIT 1"
cursor.execute(strQuery)
try:
variablefetch = cursor.fetchone ()
zone=variablefetch[0]
subid=variablefetch[1]
devicename=variablefetch[2]
classtype=variablefetch[3]
responseoverride=variablefetch[4]
print "NaturalLanguage_Search=" + searchstring + ", ZoneID=" + str(zone) + ", SubID=" + str(subid) + ", ClassType=" + classtype + ", Level=" + str(level)
except Exception:
try:
print "Natural Speech Search failed - trying LIKE query"
searchstring=""
for word in voicewords:
searchstring+="%%%s%%" % (word.strip())
strQuery="SELECT D.DeviceID AS Zone, S.SubID, D.Device, C.Class, S.ResponseOverride FROM Speech AS S JOIN Devices AS D ON S.DeviceID=D.DeviceID JOIN DeviceClasses AS C ON D.ClassID=C.ClassID WHERE SpeechPhrase LIKE '" + searchstring + "' LIMIT 1"
cursor.execute(strQuery)
variablefetch=cursor.fetchone ()
zone=variablefetch[0]
subid=variablefetch[1]
devicename=variablefetch[2]
classtype=variablefetch[3]
responseoverride=variablefetch[4]
print "LIKE_Search=" + searchstring + ", ZoneID=" + str(zone) + ", SubID=" + str(subid) + ", ClassType=" + classtype + ", Level=" + str(level)
except Exception:
print "No records found using Natural Speech or LIKE queries for: " + str(voicewords)
zone=-1
cursor.close()
db.close()
#Return unknown command if no zone is found in CSV, otherwise process command
if zone==-1:
eg.plugins.Webserver.SetValue(u"bb_response", "\ncmd is unknown: ", False, False)
else:
if classtype=="TV": #Television Command
if zone==1000: #Family Room Television Command
if level != -1: #This must be a power on/off command
if level==100:
state="on"
else:
state="off"
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["power", state ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "turning television " + str(state), False, False)
else: #This must be a volume event
if "up" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["volume", "up"])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
elif "down" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["volume", "down" ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
elif "mute" in voicewords or "unmute" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["volume", "mute" ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
elif "tivo" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["input", "tivo" ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
elif "fire" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["input", "fire tv" ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
elif "chromecast" in voicewords:
eg.TriggerEvent("LGTV", prefix="Alexa", payload=["input", "chromecast" ])
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "ok", False, False)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\ncmd is unknown: ", False, False)
elif classtype=="Tivo Mini": #Tivo Mino Command
if zone==1001: #Family Room Tivo Command
if "channel" in voicewords:
eg.TriggerEvent("TivoMini", prefix="Alexa", payload=["channel", str(level), levelsuffix])
elif "pause" in voicewords:
eg.TriggerEvent("TivoMini", prefix="Alexa", payload=["pause"])
elif "play" in voicewords or "resume" in voicewords:
eg.TriggerEvent("TivoMini", prefix="Alexa", payload=["play"])
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\ncmd is unknown: ", False, False)
elif classtype=="Security System": #Security System Command
if "away" in voicewords:
eg.plugins.Envisalink.ArmAway()
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Security System Armed Away", False, False)
elif "stay" in voicewords:
eg.plugins.Envisalink.ArmStay()
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Security System Armed Stay", False, False)
elif level==0 or "disarm" in voicewords:
eg.plugins.Envisalink.Off()
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Security System Disarmed", False, False)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\ncmd is unknown: ", False, False)
else: #RadioRA2 Command
if any(classtype in ct for ct in ["Maestro","SivoiaQS","Fan","MaestroDiscrete"]):
if responseoverride is not None:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + responseoverride, False, False)
else:
if classtype=="MaestroDiscrete":
if level==0:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Setting " + devicename + " off", False, False)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Setting " + devicename + " on", False, False)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Setting " + devicename + " to level " + str(level), False, False)
command = "#OUTPUT," + str(zone) + ",1," + str(level)
eg.plugins.LutronRadioRA2.MyCommand(command)
elif classtype=="Keypad" and subid is not None:
if responseoverride is not None:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + responseoverride, False, False)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\nReturn Msg: " + "Activating scene " + devicename, False, False)
command = "#DEVICE," + str(zone) + "," + str(subid) + ",3"
eg.plugins.LutronRadioRA2.MyCommand(command)
else:
eg.plugins.Webserver.SetValue(u"bb_response", "\ncmd is unknown: ", False, False)
#Done