very strange about what happened to the post – here it is again.
Here is the item file. For each device you want to control you will need to create a Generic_Item and Timer Item. The format of the items are used in the rules files to control the real item in the correct room. Also, your Alexa will need to be configured to turn on/off this Generic_Item whenever you want to turn on the appropriate item. I do this through ha-bridge which is HUE emulation app but you can certainly use openHab for this configuration.
In the future, if you ever need to add another device you want to control, you simply create these two items and get Alexa to discover the generic item and everything will simply work. No other changes are required.
group_items.items
Group:DateTime:LATEST gLastCommandTime
// Group:DateTime:MAX gMaxCommandTime
Group gLastPhrase
Group gGenericDevices
Group:Switch:OR(ON, OFF) gGenericTimers
Switch Generic_Fan (gGenericDevices)
Switch TimerGeneric_Fan (gGenericTimers) {expire="5S, command=OFF"}
Switch Generic_Lamp (gGenericDevices)
Switch TimerGeneric_Lamp (gGenericTimers) {expire="5S, command=OFF"}
Switch Generic_LedLight (gGenericDevices)
Switch TimerGeneric_LedLight (gGenericTimers) {expire="5S, command=OFF"}
Switch Generic_PlayShanana (gGenericDevices)
Switch TimerGeneric_PlayShanana (gGenericTimers) {expire="5S, command=OFF"}
Dimmer Generic_Heat (gGenericDevices)
Switch TimerGeneric_Heat (gGenericTimers) {expire="5S, command=OFF"}
Dimmer Generic_Air (gGenericDevices)
Switch TimerGeneric_Air (gGenericTimers) {expire="5S, command=OFF"}
Each Alexa that you own has to have the lastvoice command be a member of the gLastPhrase group. And a DateTime item has to be created to store the last time this alexa received a command. The name of the item is used in the rules file so stick to the format (or update the rules). I think I missed this the last time I posted. Here is an example from my Kitchen Alexa:
String Echo_Kitchen_LastVoiceCommand "Last voice command" (Alexa_Kitchen, gLastPhrase) {channel="amazonechocontrol:echo:account1:echoshow1:lastVoiceCommand"}
// Time of Last voice command
DateTime Echo_Kitchen_LastVoiceCommandTime (gLastCommandTime)
Because I copied the demo config and the configs from this binding when I first created my items the room names are not entirely consistent. Therefore, I created a map file to find the correct room the Alexa is in and get to the correct device. Here is the map file I use. You will most likely need something similar to find the right room and the correct device to activate.
echo-to-room.map
Echo_Family_Room = FF_FamilyRoom
Echo_Living_Room = FF_LivingRoom
Echo_Dinning_Room = FF_Dining
Echo_Office = FF_Office
Echo_Guest_Room = FF_GuestRoom
Echo_EFL_Room = FF_EmiliaSRoom
Echo_ACL_Room = F2_AmandaSRoom
Echo_Kitchen = FF_Kitchen
Echo_SEL_Room = F2_SophieSRoom
Echo_MasterBed_Room = F2_MasterBedroom
=UNDEF
So if I say turn on the fan in the Kitchen, Alexa will turn on the Generic_Fan item and the rules will find that voice command came from the Kitchen Alexa. Once the rules know it was the Kitchen, they will know that Echo_Kitchen is mapped to FF_Kitchen and will ultimately turn on the FF_Kitchen_Fan
The part after the underscore in the Generic_Item identifies the device that will be turned on/off. I parse out the name of the item and look for it in the phrase. For some of my items the item name doesnt match the phrase that I say to Alexa. For those items I have another map file that maps the item name to the phrase I use with Alexa. For instance, I have an item called PlayShanana but I say “sophies bedtime music” to Alexa. If you have those types of situations, you can use the following map file to ensure the correct item maps to the phrase you use with Alexa. If you dont have this situation, you can ignore this file and the code in the rules that reference it.
item-utterance.map
LedLight = l. e. d.
PlayShanana = sophie's bedtime music
= UNDEF
Once you have the items and maps setup, all you need are the rules that make this work. There shouldnt be any changes needed here.
echo.rules
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "Get Last Echo Command TimeStamp"
when
Member of gLastPhrase changed
then
val String itemName = triggeringItem.name + "Time"
val currentTime = new DateTimeType()
postUpdate(itemName, currentTime.toString )
// if the timer is on, we're waiting for the utterance to catch up to the switch
gGenericTimers.members.filter( item | item.state == ON ).forEach[ item |
// var GenericItem deviceItem = null
item.postUpdate(OFF) // cancel the timer
val deviceItem = ScriptServiceUtil.getItemRegistry.getItem("Generic_" + item.name.split('_').get(1)) as GenericItem //by convention the item is xxxTimer _device
logInfo("Amazon Echo","Sending a Command to {} with value {} to trigger the rule again", deviceItem.name, deviceItem.state )
deviceItem.sendCommand(deviceItem.state.toString) //retrigger my device rule since the phrase just caught up
]
end
rule "find alexa that changed device state"
when Member of gGenericDevices received command
then
val String device = triggeringItem.name.split('_').get(1)
val String tmpMapping = transform("MAP","item-utterance.map", device)
val String deviceUtterance = if (tmpMapping == "UNDEF") device else tmpMapping
// find the room that spoke most recently, but check only the ones where the word 'turn', 'set' and the triggering device is in the phrase
val turnList = gLastPhrase.members.filter( i | i.state.toString.contains(deviceUtterance.toLowerCase) && (i.state.toString.contains("turn") || i.state.toString.contains("set")))
val mostRecentItem = turnList.sortBy[ i | (ScriptServiceUtil.getItemRegistry.getItem(i.name + "Time").state as DateTimeType).zonedDateTime.toInstant.toEpochMilli ].last
val Number maxEpoch = if (mostRecentItem !== null) (ScriptServiceUtil.getItemRegistry.getItem(mostRecentItem.name + "Time").state as DateTimeType).zonedDateTime.toInstant.toEpochMilli else 0
val Number timeNow = now.millis
// logInfo ("Amazon Echo", "The most recent echo utterance happened in {} at {}", mostRecentItem.name, maxEpoch)
// logInfo ("Amazon Echo", "The time now is {} and the time difference is {}", timeNow, (maxEpoch + 5000 - timeNow))
if ((maxEpoch + 5000) > timeNow) { //the oldest voice command is less than 5s old
val String maxItem = mostRecentItem.name.replace("_LastVoiceCommand","")
var String commandDevice = transform("MAP","echo-to-room.map",maxItem) + "_" + device
// a device item doesnt exist
if (ScriptServiceUtil.getItemRegistry.getItems(commandDevice).isEmpty) {
logInfo("Amazon Echo", "No such device: {}; Checking thermostat", commandDevice)
// check to see if a tstat item was changed
val String tstatDevice = transform("MAP","echo-to-thermostat.map", maxItem) + "_" + device
if (ScriptServiceUtil.getItemRegistry.getItems(tstatDevice).isEmpty) {
logInfo("Amazon Echo", "No such thermostat: {}", tstatDevice)
ScriptServiceUtil.getItemRegistry.getItem(maxItem +"_TTS").sendCommand("This room does not have a controllable " + deviceUtterance.toLowerCase)
return;
}
// found a thermostat
commandDevice = tstatDevice
}
// logInfo("Amazon Echo", "Device: {} , ReceivedCommand: {}, FinalCommand: {}", commandDevice, receivedCommand, finalCommand)
sendCommand(commandDevice, receivedCommand.toString)
}
else {
// state change happened first or last voice command happened too long ago or someone used controller to change status
// start a timer to let the voice utterance happen, if timer finishes before utterance the command was too far in the past
sendCommand("TimerGeneric_" + device, "ON")
// logInfo("Amazon Echo", "Starting Timer on {} to see if the utterance catches up", device)
}
end
After getting this working for fans, lamps, leds, I decided I wanted to be able to say turn on the heat or turn off the air from anywhere in the house and the correct thermostat would be set. In order to do that I needed to add a few items and configurations. This shows you what I added, but feel free to not use and comment out the parts that have to do with tstats in the rules file.
echo-to-thermostat.map
Echo_Family_Room = FF_FR_Tstat
Echo_Living_Room = FF_FR_Tstat
Echo_Dinning_Room = FF_FR_Tstat
Echo_Office = FF_Hall_Tstat
Echo_Guest_Room = FF_Hall_Tstat
Echo_EFL_Room = FF_Hall_Tstat
Echo_ACL_Room = F2_Hall_Tstat
Echo_Kitchen = FF_FR_Tstat
Echo_SEL_Room = F2_Hall_Tstat
Echo_MasterBed_Room = F2_Hall_Tstat
=UNDEF
This says that when I’m in a some room, I want to control the specific thermostat.
I also created an Air and Heat Dimmer item for each thermostat as such:
Dimmer FF_Hall_Tstat_Air (gTstatSwitch)
Dimmer FF_Hall_Tstat_Heat (gTstatSwitch)
Number FF_Hall_Tstat_HeatSetPoint "Heat [%d °F]" <heating> (FF_Hall_Tstat) {channel="omnilink:thermostat:2c8fef01:3:heat_setpoint"}
Number FF_Hall_Tstat_CoolSetPoint "Cool [%d °F]" <snow> (FF_Hall_Tstat) {channel="omnilink:thermostat:2c8fef01:3:cool_setpoint"}
Number FF_Hall_Tstat_SystemMode "Mode [%s]" <airconditioner> (FF_Hall_Tstat) {channel="omnilink:thermostat:2c8fef01:3:system_mode"}
Do the same for each thermostat and add these items to the group item gTstatSwitch. I use a Dimmer so that I can set the temperature to a specific value.
These items get turned on/off or set to a specific value from the Generic_Air or Generic_Heat item after you tell Alexa to do something like set the heat to 70. Or turn the heat off.
I have Omni Thermostats, but the code should be similar to any other thermostat. The rules determine if we’re calling for air or heat, then set the cool set point or heat set point appropriately based on how my thermostats work. This is a rule file you will most likely need to modify based on your thermostats.
import org.eclipse.smarthome.model.script.ScriptServiceUtil
rule "air or heat turned on or off"
when
Member of gTstatSwitch received command
then
var String device = triggeringItem.name.split('_').get(3) // contains "Air" or "Heat"
var String itemPrefixName = triggeringItem.name.replace("_" + device,"") // tstat prefix
var Number multiplier = 1
var Number newTemp = triggeringItem.state as DecimalType
var String itemSuffix = ""
if (receivedCommand.toString == "ON" || receivedCommand.toString == "100") multiplier = 1
else multiplier = -1
var Number currentTemp = (ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName +"_Temperature").state as DecimalType).intValue
var Number heatSPTemp = (ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName +"_HeatSetPoint").state as DecimalType).intValue
var Number coolSPTemp = (ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName +"_CoolSetPoint").state as DecimalType).intValue
var Number currentState = ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName +"_SystemMode").state as DecimalType
if (device == "Heat") {
itemSuffix = "_HeatSetPoint"
if (newTemp == 0 || newTemp == 100) {
if (heatSPTemp > currentTemp) currentTemp = heatSPTemp
newTemp = (currentTemp + (2.75 * multiplier)).intValue
}
}
else {
itemSuffix = "_CoolSetPoint"
if (newTemp == 0 || newTemp == 100 ) {
if (coolSPTemp > currentTemp) currentTemp = coolSPTemp
newTemp = (currentTemp - (2.75 * multiplier)).intValue
}
}
if (currentState != 3)
ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName +"_SystemMode").sendCommand(3)
logInfo("TStat Rules", "currentTemp: {} , newTemp: {}, multiplier: {}", currentTemp, newTemp, multiplier)
ScriptServiceUtil.getItemRegistry.getItem(itemPrefixName + itemSuffix).sendCommand(newTemp)
end
Like I said in my last post, I’m happy to help explain any of the code or logic so that you can get your house working like this. This has really been a huge improvement to what Alexa can do on its own.
Now that this is all working in OH 2.5.x, it looks like I need to start thinking about porting it to OH3.
Best wishes,
Jerry