Hi *,
I wanted to be able to run some rules only after confirmation through Alexa and I could not find a existing solution, therefore I created my own.
Prerequisites:
- Amazon Echo Control Skill
- Alexa device / Amazon account (obviously…)
- OpenHAB Cloud Setup
- Prepare OpenHAB
- Create an item called Alexa_Feedback with type string that will receive the users feedback.
- Next copy and or modify the below script to invoke the question to the user:
events.sendCommand("Echo1_Speak", "Good Morning. Should I read you the latest news?")
events.sendCommand("Echo1_TextCommand", "ask smart home dialogue about rule 1")
This script will play the defined text after Echo1_Speak and start the custom skill that will be implemented in the next step.
- Custom Skill
In order to receive the feedback the Alexa devices need a custom skill (AFAIK) which however can be hosted on Amazons servers for free and setup quite easy.
- Login at the Alexa Skill Developer Console here
- Create a first Skill and choose a name (doesn’t really matter what) your input language and for model select custom and for “method to host your skill’s backend resources” select Amazon-hosted (Python)
- Before you click create make sure that in the top left corner the appropriate datacenter location
- Let Amazon do its thing (this takes several minutes)
- On the Build tab select Invocations → Skill invocation name from the left menu and choose the skills activation phrase. In the example code above it’s called smart home dialogue.
- On the Build tab select Interaction model → Intents and delete all Intents that are not marked as required
- On the Build tab select Slots types and create a new slot type. Name the slot type feedback and add the values yes and no, each with synonyms you’d like to be able to use.
- On the Build tab select Interaction model → Intents and create a custom intent (name is irrelevant).
– As utterance type: about rule {rule_id}
– Add two intent slots:
— rule_id with type AMAZON.number
— feedback with type feedback
Click on “Edit dialogue” on the right for intent slot feedback:
-
Enable: “Is this slot required to fulfill the intent?”
-
For “Speech prompts” use “Yes or No?”
-
For user utterances type {feedback}
-
Click save model and build model
-
Click the Code tab on the top menu
-
Paste the following code to lamda_function.py and change username and password in the function ohbRequest:
# -*- coding: utf-8 -*-
# This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK for Python.
# Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
# session persistence, api calls, and more.
# This sample is built using the handler classes approach in skill builder.
import logging
import requests
from requests.auth import HTTPBasicAuth
import ask_sdk_core.utils as ask_utils
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.dispatch_components import AbstractExceptionHandler
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
def ohbRequest(string):
dest = "https://myopenhab.org/rest/items/Alexa_Feedback"
requests.post(url=dest, data=string.encode('utf-8'), headers={'Content-Type': 'text/plain'}, auth=HTTPBasicAuth('username', 'password'))
class LaunchRequestHandler(AbstractRequestHandler):
"""Handler for Skill Launch."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_request_type("LaunchRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "Yes or No?"
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
class RuleFeedbackIntentHandler(AbstractRequestHandler):
"""Handler for RuleFeedbackIntent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_intent_name("RuleFeedbackIntent")(handler_input)
def handle(self, handler_input):
rule_id = (ask_utils.request_util.get_slot(handler_input, "rule_id")).value
feedback = (ask_utils.request_util.get_slot(handler_input, "feedback")).value
toSet = str(rule_id) + ":" + str(feedback)
ohbRequest(toSet)
# type: (HandlerInput) -> Response
speak_output = "Okay"
return (
handler_input.response_builder
.speak(speak_output)
.set_should_end_session(True)
.response
)
class HelpIntentHandler(AbstractRequestHandler):
"""Handler for Help Intent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_intent_name("AMAZON.HelpIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "i can process yes or no requests for openHab"
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
class CancelOrStopIntentHandler(AbstractRequestHandler):
"""Single handler for Cancel and Stop Intent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return (ask_utils.is_intent_name("AMAZON.CancelIntent")(handler_input) or
ask_utils.is_intent_name("AMAZON.StopIntent")(handler_input))
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "Okay!"
return (
handler_input.response_builder
.speak(speak_output)
.response
)
class FallbackIntentHandler(AbstractRequestHandler):
"""Single handler for Fallback Intent."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_intent_name("AMAZON.FallbackIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speech = "hmm, i didn't get that. Could you please repeat your answer?"
return handler_input.response_builder.speak(speech).ask(speech).response
class SessionEndedRequestHandler(AbstractRequestHandler):
"""Handler for Session End."""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return ask_utils.is_request_type("SessionEndedRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
# Any cleanup logic goes here.
return handler_input.response_builder.response
class CatchAllExceptionHandler(AbstractExceptionHandler):
"""Generic error handling to capture any syntax or routing errors. If you receive an error
stating the request handler chain is not found, you have not implemented a handler for
the intent being invoked or included it in the skill builder below.
"""
def can_handle(self, handler_input, exception):
# type: (HandlerInput, Exception) -> bool
return True
def handle(self, handler_input, exception):
# type: (HandlerInput, Exception) -> Response
logger.error(exception, exc_info=True)
speak_output = "Sorry, I had trouble doing what you asked. Please try again."
return (
handler_input.response_builder
.speak(speak_output)
.ask(speak_output)
.response
)
# The SkillBuilder object acts as the entry point for your skill, routing all request and response
# payloads to the handlers above. Make sure any new handlers or interceptors you've
# defined are included below. The order matters - they're processed top to bottom.
sb = SkillBuilder()
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(RuleFeedbackIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelOrStopIntentHandler())
sb.add_request_handler(FallbackIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_exception_handler(CatchAllExceptionHandler())
lambda_handler = sb.lambda_handler()
- Delete the utils.py / helper.py
- Save and deploy
- On the Test tab press and hold the mic and say: “ask smart home dialog about rule 420” and answer with Yes or No.
- On openHAB the item Alexa_Feedback should now read: 420:yes or 420:no depending on your answer.
- Create a second openHAB rule:
configuration: {}
triggers:
- id: "1"
configuration:
itemName: Alexa_Feedback
type: core.ItemStateUpdateTrigger
conditions: []
actions:
- inputs: {}
id: "2"
configuration:
type: application/javascript
script: >-
//var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);
var feedback = itemRegistry.getItem("Alexa_Feedback").state
var rule = feedback.toString().split(':')[0]
var answer = feedback.toString().split(':')[1]
switch(rule) {
case "1":
if(answer == "yes"){
events.sendCommand("Echo1_TextCommand", "read me the news")
} else if (answer == "no") {
events.sendCommand("Echo1_Speak", "okay a quiet morning then.")
}
}
type: script.ScriptAction
You can extend and reuse this skill for multiple user prompts by changing the rule number that is asked in the first rule. e.g.: events.sendCommand(“Echo1_TextCommand”, “ask smart home dialogue about rule 2”) will yield a change in Alexa_Feedback with the format 2:yes or “2:no”. The second openHAB rule can then be extended for an additional case statement.
Hope this helps you