OHB3 Retrieve user feedback through Alexa devices

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:

  1. 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.

  1. 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.
  1. 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 :slight_smile:

4 Likes

Thank your for sharing this! I was looking for a feedback solution.
Unfortunately I can‘t get it to work.

After the “ask smart home dialog about rule 420”, Alexa asks me „Yes or No?“.
But no matter what I answer, I always get the “Sorry, I had trouble doing what you asked. Please try again.” response from her.

Is it possible you share the skill without your login so that I just need to use my own myopehab credentials?

Thank you!

Did you supply your username and password you use to login to myopenhab on line 24 in lambda_function.py?

It is not the username & password you use locally!

Yes, I changed the username and password to my myopenhab login credentials.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.