class CocktailSkill(MycroftSkill):
    @intent_file_handler('Recipie.intent')
    def get_recipie(self, message):
        cocktail = search_dish(message.data['dish'])
        if cocktail:
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(cocktail[:-1]),
                    'final_ingredient': cocktail[-1]
                })
            time.sleep(1)
            self.speak(cocktail['strInstructions'])
            self.set_context('IngredientContext', str(cocktail))
        else:
            self.speak_dialog('NotFound')

    def repeat_ingredients(self, ingredients):
        self.speak(ingredients)

    @intent_handler(AdaptIntent().require('Ingredients').require(
        'What').require('IngredientContext'))
    def what_were_ingredients(self, message):
        return self.repeat_ingredients(message.data['IngredientContext'])

    @intent_handler(AdaptIntent().require('Ingredients').require(
        'TellMe').require('Again').require('IngredientContext'))
    def tell_ingredients_again(self, message):
        return self.repeat_ingredients(message.data['IngredientContext'])
Пример #2
0
class CocktailSkill(MycroftSkill):
    @intent_handler('Random.intent')
    def get_random(self, message):
        cocktail = random_cocktail()

        self.speak_dialog("RandomDrink", {"drink": cocktail['strDrink']})
        time.sleep(1)
        self.speak_dialog(
            'YouWillNeed', {
                'ingredients': ', '.join(ingredients(cocktail)[:-1]),
                'final_ingredient': ingredients(cocktail)[-1]
            })
        time.sleep(1)
        self.speak(cocktail['strInstructions'])
        self.set_context('IngredientContext', str(ingredients(cocktail)))

    @intent_handler('Recipe.intent')
    def get_recipe(self, message):
        cocktail = search_cocktail(message.data['drink'])
        if cocktail:
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(ingredients(cocktail)[:-1]),
                    'final_ingredient': ingredients(cocktail)[-1]
                })
            time.sleep(1)
            self.speak(cocktail['strInstructions'])
            self.set_context('IngredientContext', str(ingredients(cocktail)))
        else:
            self.speak_dialog('NotFound')

    def repeat_ingredients(self, ingredients):
        self.speak(ingredients)

    @intent_handler('Needed.intent')
    def get_ingredients(self, message):
        cocktail = search_cocktail(message.data['drink'])
        if cocktail:
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(ingredients(cocktail)[:-1]),
                    'final_ingredient': ingredients(cocktail)[-1]
                })
            self.set_context('IngredientContext', str(ingredients(cocktail)))
        else:
            self.speak_dialog('NotFound')

    @intent_handler(
        AdaptIntent("").require('Ingredients').require('What').require(
            'IngredientContext'))
    def what_were_ingredients(self, message):
        """Context aware handler if the user asks for a repeat."""
        return self.repeat_ingredients(message.data['IngredientContext'])

    @intent_handler(
        AdaptIntent("").require('Ingredients').require('TellMe').require(
            'Again').require('IngredientContext'))
    def tell_ingredients_again(self, message):
        return self.repeat_ingredients(message.data['IngredientContext'])
Пример #3
0
class CocktailSkill(MycroftSkill):

    instructionMapper = {
        "de": "strInstructionsDE",
        "en": "strInstructions",
        "fr": "strInstructionsFR",
        "es": "strInstructionsES"
    }

    def initialize(self):
        self.language = self.config_core.get('lang').split('-')[0]
        self.instructionKey = self.instructionMapper.get(
            self.language, "strInstructions")

    @intent_handler('Random.intent')
    def get_random(self, message):
        cocktail = random_cocktail()
        self.speak_dialog("RandomDrink", {"drink": cocktail['strDrink']})
        time.sleep(1)
        self.handle_cocktail(cocktail)

    @intent_handler('Recipe.intent')
    def get_recipe(self, message):
        cocktail = search_cocktail(message.data['drink'])
        self.handle_cocktail(cocktail)

    def handle_cocktail(self, cocktail):
        if cocktail:
            ingreds = ingredients(cocktail, self.language)
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(ingreds[:-1]),
                    'final_ingredient': ingreds[-1]
                })
            time.sleep(1)
            instructions = cocktail.get(self.instructionKey, None)
            if instructions is None:
                instructions = cocktail["strInstructions"]
            self.speak(instructions)
            self.set_context('IngredientContext', str(', '.join(ingreds)))
        else:
            self.speak_dialog('NotFound')

    def repeat_ingredients(self, ingredients):
        self.speak(ingredients)

    @intent_handler('Needed.intent')
    def get_ingredients(self, message):
        cocktail = search_cocktail(message.data['drink'])
        if cocktail:
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(ingredients(cocktail)[:-1]),
                    'final_ingredient': ingredients(cocktail)[-1]
                })
            self.set_context('IngredientContext', str(ingredients(cocktail)))
        else:
            self.speak_dialog('NotFound')

    @intent_handler(
        AdaptIntent("").require('Ingredients').require('What').require(
            'IngredientContext'))
    def what_were_ingredients(self, message):
        """Context aware handler if the user asks for a repeat."""
        return self.repeat_ingredients(message.data['IngredientContext'])

    @intent_handler(
        AdaptIntent("").require('Ingredients').require('TellMe').require(
            'Again').require('IngredientContext'))
    def tell_ingredients_again(self, message):
        return self.repeat_ingredients(message.data['IngredientContext'])
Пример #4
0
class RecipeSkill(MycroftSkill):
    @intent_file_handler('recipe.intent')
    def get_recipe(self, message):
        recipe = search_dish(message.data['dish'])
        if recipe:
            ingredients = recipe['ingredientLines']
            calories = "the total calories is " + str(
                round(recipe['calories'], 2))
            totalNutr = recipe['totalNutrients']
            totalNutrlist = ["the total nutrients are"]
            for key, value in totalNutr.items():
                totalNutrlist.append(' '.join(
                    (value['label'], str(round(value['quantity'],
                                               2)), value['unit'])))
            self.speak_dialog(
                'YouWillNeed', {
                    'ingredients': ', '.join(ingredients[:-1]),
                    'final_ingredient': ingredients[-1]
                })
            time.sleep(1)
            self.speak(calories)

            self.set_context('IngredientContext', str(ingredients))
            self.set_context('caloriesContext', str(calories))
            self.set_context('totalNutrlistContext', str(totalNutrlist))
        else:
            self.speak_dialog('NotFound')

    @intent_file_handler('rec.intent')
    def get_recommendation(self, message):
        duration = self.get_response('askfood')
        if duration is None:
            return  # user cancelled
        print(duration)
        dish_infor = dish_recommendation(duration)
        if dish_infor:

            ingredients = dish_infor['ingredientLines']
            label = dish_infor['label']
            calories = "the total calories is " + str(
                round(dish_infor['calories'], 2))
            totalNutr = dish_infor['totalNutrients']
            totalNutrlist = ["the total nutrients are"]
            for key, value in totalNutr.items():
                totalNutrlist.append(' '.join(
                    (value['label'], str(round(value['quantity'],
                                               2)), value['unit'])))
            self.speak_dialog('YouCanCook', {'label': label})
            self.speak(calories)

            self.set_context('IngredientContext', str(ingredients))
            self.set_context('caloriesContext', str(calories))
            self.set_context('totalNutrlistContext', str(totalNutrlist))
        else:
            self.speak_dialog('NotFound')

    @intent_file_handler('food.intent')
    def get_nutrition(self, message):
        nutrients = search_nutrients(message.data['food'])
        if nutrients:

            speakNu = nutrients
            self.speak_dialog(
                'okay', {
                    'nutrition': ', '.join(speakNu[:-1]),
                    'final_nutrition': speakNu[-1]
                })
            time.sleep(1)
            self.set_context('NutrientsContext', str(nutrients))
        else:
            self.speak_dialog('NotFound')

    @intent_handler(AdaptIntent().require('Nutrients').require(
        'TellMe').require('forthismeal').require('totalNutrlistContext'))
    def tell_ingredients_again(self, message):
        return self.repeat_context(message.data['totalNutrlistContext'])

    def repeat_context(self, context):
        self.speak(context)

    @intent_handler(AdaptIntent().require('Ingredients').require(
        'What').require('IngredientContext'))
    def what_were_ingredients(self, message):
        return self.repeat_context(message.data['IngredientContext'])

    @intent_handler(AdaptIntent().require('Ingredients').require(
        'TellMe').require('Again').require('IngredientContext'))
    def tell_ingredients_again(self, message):
        return self.repeat_context(message.data['IngredientContext'])

    @intent_handler(AdaptIntent().require('calories').require('What').require(
        'caloriesContext'))
    def what_were_calories(self, message):
        return self.repeat_context(message.data['caloriesContext'])

    @intent_handler(AdaptIntent().require('calories').require(
        'TellMe').require('Again').require('caloriesContext'))
    def tell_calories_again(self, message):
        return self.repeat_context(message.data['caloriesContext'])

    @intent_handler(AdaptIntent().require('nutritionword').require(
        'What').require('totalNutrlistContext'))
    def what_were_nutrition(self, message):
        return self.repeat_context(message.data['totalNutrlistContext'])

    @intent_handler(AdaptIntent().require('nutritionword').require(
        'TellMe').require('Again').require('totalNutrlistContext'))
    def tell_nutrition_again(self, message):
        return self.repeat_context(message.data['totalNutrlistContext'])
Пример #5
0
class FhemSkill(FallbackSkill):
    def __init__(self):
        super(FhemSkill, self).__init__(name="FhemSkill")
        LOG.info("__init__")
        self.fhem = None
        self.enable_fallback = False

    def _setup(self, force=False):
        if self.settings is not None and (force or self.fhem is None):
            LOG.debug("_setup")
            portnumber = self.settings.get('portnum')
            try:
                portnumber = int(portnumber)
            except TypeError:
                portnumber = 8083
            except ValueError:
                # String might be some rubbish (like '')
                portnumber = 0

            self.fhem = FhemClient(self.settings.get('host'),
                                   self.settings.get('username'),
                                   self.settings.get('password'), portnumber,
                                   self.settings.get('room'),
                                   self.settings.get('ignore_rooms'),
                                   self.settings.get('ssl') == True,
                                   self.settings.get('verify') == True)
            if self.fhem:
                # Check if natural language control is loaded at fhem-server
                # and activate fallback accordingly
                LOG.debug("fallback_device_name %s" %
                          self.settings.get('fallback_device_name'))
                LOG.debug("enable_fallback %s" %
                          self.settings.get('enable_fallback'))
                if self.settings.get('enable_fallback') == True and \
                    self.settings.get('fallback_device_name') is not None:
                    fallback_device = self.fhem.get_device(
                        "NAME", self.settings.get('fallback_device_name'))
                    if fallback_device:
                        self.fallback_device_name = fallback_device['Name']
                        self.fallback_device_type = \
                                fallback_device['Internals']['TYPE']
                        LOG.debug("fallback_device_type is %s" %
                                  self.fallback_device_type)
                        if self.fallback_device_type in [
                                "Talk2Fhem", "TEERKO", "Babble"
                        ]:
                            self.enable_fallback = True
                        else:
                            self.enable_fallback = False
                else:
                    self.enable_fallback = False
                LOG.debug('fhem-fallback enabled: %s' % self.enable_fallback)

    def _force_setup(self):
        LOG.debug('Creating a new Fhem-Client')
        self._setup(True)

    def initialize(self):
        # Needs higher priority than general fallback skills
        self.register_fallback(self.handle_fallback, 2)
        # Check and then monitor for credential changes
        self.settings.set_changed_callback(self.on_websettings_changed)

    def on_websettings_changed(self):
        # Only attempt to load if the host is set
        LOG.debug("websettings changed")
        if self.settings.get('host', None):
            try:
                self._setup(True)
            except Exception:
                pass

    @intent_handler(AdaptIntent().require("SwitchActionKeyword").require(
        "Action").require("Entity"))
    def handle_switch_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        LOG.debug("Starting Switch Intent")
        entity = message.data["Entity"]
        action = message.data["Action"]
        allowed_types = ['light', 'switch', 'outlet']
        LOG.debug("Entity: %s" % entity)
        LOG.debug("Action: %s" % action)
        # TODO if entity is 'all', 'any' or 'every' turn on
        # every single entity not the whole group
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return
        LOG.debug("Entity State: %s" % fhem_entity['state'])
        # fhem_data = {'entity_id': fhem_entity['id']}

        # IDEA: set context for 'turn it off' again or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        # keep original actioname for speak_dialog
        # when device already is in desiredstate
        original_action = action
        if self.lang.lower().startswith("de"):
            if (action == 'ein') or (action == 'an'):
                action = 'on'
            elif action == 'aus':
                action = 'off'
        LOG.debug("- action: %s" % action)
        LOG.debug("- state: %s" % fhem_entity['state']['Value'])
        if fhem_entity['state']['Value'] == action:
            LOG.debug("Entity in requested state")
            self.speak_dialog('fhem.device.already',
                              data={
                                  'dev_name': fhem_entity['dev_name'],
                                  'action': original_action
                              })
        elif action == "toggle":
            if (fhem_entity['state']['Value'] == 'off'):
                action = 'on'
            else:
                action = 'off'
            LOG.debug("toggled action: %s" % action)
            self.fhem.execute_service("set", fhem_entity['id'], action)
            self.speak_dialog('fhem.device.%s' % action, data=fhem_entity)
        elif action in ["on", "off"]:
            LOG.debug("action: on/off")
            self.speak_dialog('fhem.device.%s' % action, data=fhem_entity)
            self.fhem.execute_service("set", fhem_entity['id'], action)
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(AdaptIntent().optionally("LightsKeyword").require(
        "SetVerb").require("Entity").require("BrightnessValue"))
    def handle_light_set_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if (self.fhem is None):
            self.speak_dialog('fhem.error.setup')
            return
        entity = message.data["Entity"]
        allowed_types = ['light']  # TODO
        try:
            brightness_req = float(message.data["BrightnessValue"])
            if brightness_req > 100 or brightness_req < 0:
                self.speak_dialog('fhem.brightness.badreq')
        except KeyError:
            brightness_req = 10.0
        brightness_value = int(brightness_req / 100 * 255)
        brightness_percentage = int(brightness_req)
        LOG.debug("Entity: %s" % entity)
        LOG.debug("Brightness Value: %s" % brightness_value)
        LOG.debug("Brightness Percent: %s" % brightness_percentage)
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return
        fhem_data = {'entity_id': fhem_entity['id']}

        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        # TODO - Allow value set
        if "SetVerb" in message.data:
            fhem_data['brightness'] = brightness_value
            fhem_data['dev_name'] = fhem_entity['dev_name']
            self.fhem.execute_service("fhem", "turn_on", fhem_data)
            self.speak_dialog('fhem.brightness.dimmed', data=fhem_data)
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(AdaptIntent().optionally("LightsKeyword").one_of(
        "IncreaseVerb", "DecreaseVerb", "LightBrightenVerb",
        "LightDimVerb").require("Entity").optionally("BrightnessValue"))
    def handle_light_adjust_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        allowed_types = ['light']
        entity = message.data["Entity"]

        try:
            brightness_req = float(message.data["BrightnessValue"])
            if brightness_req > 100 or brightness_req < 0:
                self.speak_dialog('fhem.brightness.badreq')
        except KeyError:
            brightness_req = 10.0
        brightness_value = int(brightness_req / 100 * 255)
        # brightness_percentage = int(brightness_req) # debating use
        LOG.debug("Entity: %s" % entity)
        LOG.debug("Brightness Value: %s" % brightness_value)
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return
        fhem_data = {'entity_id': fhem_entity['id']}
        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        # if self.lang == 'de':
        #    if action == 'runter' or action == 'dunkler':
        #        action = 'dim'
        #    elif action == 'heller' or action == 'hell':
        #        action = 'brighten'
        if "DecreaseVerb" in message.data or \
                "LightDimVerb" in message.data:
            if fhem_entity['state'] == "off":
                self.speak_dialog('fhem.brightness.cantdim.off',
                                  data=fhem_entity)
            else:
                light_attrs = self.fhem.find_entity_attr(fhem_entity['id'])
                if light_attrs['unit_measure'] is None:
                    self.speak_dialog('fhem.brightness.cantdim.dimmable',
                                      data=fhem_entity)
                else:
                    fhem_data['brightness'] = light_attrs['unit_measure']
                    if fhem_data['brightness'] < brightness_value:
                        fhem_data['brightness'] = 10
                    else:
                        fhem_data['brightness'] -= brightness_value
                    self.fhem.execute_service("fhem", "turn_on", fhem_data)
                    fhem_data['dev_name'] = fhem_entity['dev_name']
                    self.speak_dialog('fhem.brightness.decreased',
                                      data=fhem_data)
        elif "IncreaseVerb" in message.data or \
                "LightBrightenVerb" in message.data:
            if fhem_entity['state'] == "off":
                self.speak_dialog('fhem.brightness.cantdim.off',
                                  data=fhem_entity)
            else:
                light_attrs = self.fhem.find_entity_attr(fhem_entity['id'])
                if light_attrs['unit_measure'] is None:
                    self.speak_dialog('fhem.brightness.cantdim.dimmable',
                                      data=fhem_entity)
                else:
                    fhem_data['brightness'] = light_attrs['unit_measure']
                    if fhem_data['brightness'] > brightness_value:
                        fhem_data['brightness'] = 255
                    else:
                        fhem_data['brightness'] += brightness_value
                    self.fhem.execute_service("fhem", "turn_on", fhem_data)
                    fhem_data['dev_name'] = fhem_entity['dev_name']
                    self.speak_dialog('fhem.brightness.increased',
                                      data=fhem_data)
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(
        AdaptIntent().require("AutomationActionKeyword").require("Entity"))
    def handle_automation_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        entity = message.data["Entity"]
        allowed_types = ['automation', 'scene', 'script']  # TODO
        LOG.debug("Entity: %s" % entity)
        # also handle scene and script requests
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        fhem_data = {'entity_id': fhem_entity['id']}
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return

        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        LOG.debug("Triggered automation/scene/script: {}".format(fhem_data))
        if "automation" in fhem_entity['id']:
            self.fhem.execute_service('automation', 'trigger', fhem_data)
            self.speak_dialog('fhem.automation.trigger',
                              data={"dev_name": fhem_entity['dev_name']})
        elif "script" in fhem_entity['id']:
            self.speak_dialog('fhem.automation.trigger',
                              data={"dev_name": fhem_entity['dev_name']})
            self.fhem.execute_service("fhem", "turn_on", data=fhem_data)
        elif "scene" in fhem_entity['id']:
            self.speak_dialog('fhem.device.on', data=fhem_entity)
            self.fhem.execute_service("fhem", "turn_on", data=fhem_data)

    @intent_handler(
        AdaptIntent().require("SensorStatusKeyword").require("Entity"))
    def handle_sensor_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return

        entity = message.data["Entity"]
        allowed_types = ['sensor', 'thermometer']  # TODO
        LOG.debug("Entity: %s" % entity)
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return

        entity = fhem_entity['id']
        sensor_name = fhem_entity['dev_name']
        sensor_state = ""
        sensor_unit = ""

        tokens = fhem_entity['state']['Value'].split(" ")
        for t in range(0, len(tokens)):
            tok = tokens[t].lower().replace(":", "")
            # LOG.debug("tok = %s" % tok)
            if tok in ['t', 'temp', 'temperatur', 'temperature']:
                sensor_state += self.__translate("sensor.temperature")
            elif tok in ['h', 'hum', 'humidity']:
                sensor_state += self.__translate("sensor.humidity")
            elif tok in ['p', 'pamb', 'press', 'pressure']:
                sensor_state += self.__translate("sensor.pressure")
            else:
                sensor_state += tokens[t]
            sensor_state += " "

        LOG.debug("fhem_entity['state']['Value']: %s" %
                  fhem_entity['state']['Value'])
        LOG.debug("sensor_state: %s" % sensor_state)
        self.speak_dialog('fhem.sensor',
                          data={
                              "dev_name": sensor_name,
                              "value": sensor_state,
                              "unit": sensor_unit
                          })

        # # IDEA: set context for 'read it out again' or similar
        # # self.set_context('Entity', fhem_entity['dev_name'])
        #
        # unit_measurement = self.fhem.find_entity_attr(entity)
        # if unit_measurement[0] is not None:
        #     sensor_unit = unit_measurement[0]
        # else:
        #     sensor_unit = ''
        #
        # sensor_name = unit_measurement[1]
        # sensor_state = unit_measurement[2]
        # # extract unit for correct pronounciation
        # # this is fully optional
        # try:
        #     from quantulum import parser
        #     quantulumImport = True
        # except ImportError:
        #     quantulumImport = False
        #
        # if quantulumImport and unit_measurement != '':
        #     quantity = parser.parse((u'{} is {} {}'.format(
        #         sensor_name, sensor_state, sensor_unit)))
        #     if len(quantity) > 0:
        #         quantity = quantity[0]
        #         if (quantity.unit.name != "dimensionless" and
        #                 quantity.uncertainty <= 0.5):
        #             sensor_unit = quantity.unit.name
        #             sensor_state = quantity.value
        #
        # self.speak_dialog('fhem.sensor', data={
        #     "dev_name": sensor_name,
        #     "value": sensor_state,
        #     "unit": sensor_unit})
        # # IDEA: Add some context if the person wants to look the unit up
        # # Maybe also change to name
        # # if one wants to look up "outside temperature"
        # # self.set_context("SubjectOfInterest", sensor_unit)

    #@intent_handler(AdaptIntent().require("PresenceTrackerKeyword").require("Entity"))
    @intent_file_handler('presence.intent')
    def handle_presence_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        wanted = message.data["entity"]
        LOG.debug("wanted: %s" % wanted)

        try:
            r = self.fhem.execute_service("jsonlist2", "TYPE=ROOMMATE&XHR=1")
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return

        result = r.json()
        if result['totalResultsReturned'] < 1:
            self.speak_dialog('fhem.presence.error')
            return

        roommates = result['Results']
        presence = None
        bestRatio = 66

        for rm in roommates:
            if 'rr_realname' in rm['Attributes'] and \
                rm['Attributes']['rr_realname'] in rm['Attributes']:
                realname = rm['Attributes'][rm['Attributes']['rr_realname']]
                LOG.debug("realname: %s" % realname)
                ratio = fuzz.ratio(wanted.lower(), realname.lower())
                LOG.debug("ratio: %s" % ratio)
                if ratio > bestRatio:
                    presence = rm['Readings']['presence']['Value']
                    bestName = realname
                    bestRatio = ratio

        if presence:
            location = self.__translate('presence.%s' % presence)
            self.speak_dialog('fhem.presence.found',
                              data={
                                  'wanted': bestName,
                                  'location': location
                              })
        else:
            self.speak_dialog('fhem.presence.error')

    @intent_file_handler('set.climate.intent')
    def handle_set_thermostat_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        LOG.debug("Starting Thermostat Intent")

        entity = message.data["entity"]
        LOG.debug("Entity: %s" % entity)
        LOG.debug("This is the message data: %s" % message.data)
        temperature = message.data["temp"]
        LOG.debug("desired temperature from message: %s" % temperature)

        allowed_types = ['thermostat']
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": entity})
            return
        LOG.debug("Entity State: %s" % fhem_entity['state'])

        entity_id = fhem_entity['id']
        target_entity = entity_id

        # defaults for min/max temp and step
        minValue = 5.0
        maxValue = 35.0
        minStep = 0.5
        unit = ""
        cmd = ""

        # check thermostat type, derive command and min/max values
        LOG.debug("fhem_entity: %s" % fhem_entity)
        # for that get thermostat device
        td = self.fhem.get_device("NAME", fhem_entity['id'])
        LOG.debug("td: %s" % td)
        if 'desired-temp' in td['Readings']:
            cmd = "desired-temp"
            if 'FBTYPE' in td['Readings'] and \
                td['Readings']['FBTYPE']=='Comet DECT':
                #LOG.debug("Comet DECT")
                minValue = 8.0
                maxValue = 28.0
            elif td['Internals']['TYPE'] == 'FHT':
                #LOG.debug("FHT")
                minValue = 6.0
                maxValue = 30.0
            elif td['Internals']['TYPE'] == 'CUL_HM':
                #LOG.debug("HM")
                # test for Clima-Subdevice
                if 'channel_04' in td['Internals']:
                    target_entity = td['Internals']['channel_04']
        elif 'desiredTemperature' in td['Readings']:
            #LOG.debug("MAX")
            cmd = "desiredTemperature"
            minValue = 4.5
            maxValue = 30.5
        elif 'desired' in td['Readings']:
            #LOG.debug("PID20")
            cmd = "desired"
        elif 'homebridgeMapping' in td['Attributes']:
            LOG.debug("homebridgeMapping")
            hbm = td['Attributes']['homebridgeMapping'].split(" ")
            for h in hbm:
                #TargetTemperature=desired-temp::desired-temp,
                #minValue=5,maxValue=35,minStep=0.5,nocache=1
                if h.startswith("TargetTemperature"):
                    targettemp = (h.split("=", 1)[1]).split(",")
                    LOG.debug("targettemp = %s" % targettemp)
                    for t in targettemp:
                        LOG.debug("t = %s" % t)
                        if t.startswith("desired-temp"):
                            t2 = t.split(":")
                            cmd = t2[0]
                            if t2[1] != '':
                                target_entity = t2[1]
                        elif t.startswith("minValue"):
                            minValue = float(t.split("=")[1])
                        elif t.startswith("maxValue"):
                            maxValue = float(t.split("=")[1])
                        elif t.startswith("minStep"):
                            minStep = float(t.split("=")[1])

        if cmd == "":
            LOG.info("FHEM device %s has unknown thermostat type" % entity_id)
            self.speak_dialog('fhem.error.notsupported')
            return

        LOG.debug("target_entity: %s cmd: %s" % (target_entity, cmd))
        LOG.debug("minValue: %s maxValue: %s minStep: %s" %
                  (minValue, maxValue, minStep))

        # check if desired temperature is out of bounds
        if (float(temperature)<minValue) or (float(temperature)>maxValue) or \
                (float(temperature) % minStep != 0.0):
            self.speak_dialog('fhem.thermostat.badreq',
                              data={
                                  "minValue": minValue,
                                  "maxValue": maxValue,
                                  "minStep": minStep
                              })
            return

        action = "%s %s" % (cmd, temperature)
        LOG.debug("set %s %s" % (target_entity, action))
        self.fhem.execute_service("set", target_entity, action)
        self.speak_dialog('fhem.set.thermostat',
                          data={
                              "dev_name": entity,
                              "value": temperature,
                              "unit": unit
                          })

    def handle_fallback(self, message):
        LOG.debug("entering handle_fallback with utterance '%s'" %
                  message.data.get('utterance'))
        self._setup()
        if self.fhem is None:
            LOG.debug("FHEM setup error")
            self.speak_dialog('fhem.error.setup')
            return False
        if not self.enable_fallback:
            LOG.debug("fallback not enabled!")
            return False

        # pass message to FHEM-server
        try:
            if self.fallback_device_type == "TEERKO":
                #LOG.debug("fallback device type TEERKO")
                response = self.fhem.execute_service(
                    "set", self.fallback_device_name,
                    "TextCommand {}".format(message.data.get('utterance')))
            elif self.fallback_device_type == "Talk2Fhem":
                #LOG.debug("fallback device type Talk2Fhem")
                response = self.fhem.execute_service(
                    "set", self.fallback_device_name,
                    message.data.get('utterance'))
            elif self.fallback_device_type == "Babble":
                #LOG.debug("fallback device type Babble")
                cmd = '{Babble_DoIt("%s","%s","testit","1")}' % (
                    self.fallback_device_name, message.data.get('utterance'))
                response = self.fhem.execute_service(cmd)
                # Babble gives feedback through response
                # existence of string '[Babble_Normalize]' means success!
                if response.text.find('[Babble_Normalize]') > 0:
                    return True
                else:
                    return False
            else:
                LOG.debug("fallback device type UNKNOWN")
                return False
        except ConnectionError:
            LOG.debug("connection error")
            self.speak_dialog('fhem.error.offline')
            return False

        result = self.fhem.get_device("NAME", self.fallback_device_name)
        #LOG.debug("result: %s" % result)

        if not result:  # or result['Readings']['status']['Value'] == 'err':
            #LOG.debug("no result")
            return False

        answer = ""
        if self.fallback_device_type == "Talk2Fhem":
            if result['Readings']['status']['Value'] == 'answers':
                #LOG.debug("answering with Talk2Fhem result")
                answer = result['Readings']['answers']['Value']
            else:
                return False
        elif self.fallback_device_type == "TEERKO":
            if result['Readings']['Answer']['Value'] is not None:
                #LOG.debug("answering with TEERKO result")
                answer = result['Readings']['Answer']['Value']
            else:
                return False
        # Babble gives feedback through response, so nothing to do here
        else:
            #LOG.debug("status undefined")
            return False

        if answer == "":
            #LOG.debug("empty answer")
            return False

        asked_question = False
        # TODO: maybe enable conversation here if server asks sth like
        # "In which room?" => answer should be directly passed to this skill
        if answer.endswith("?"):
            LOG.debug("answer endswith question mark")
            asked_question = True
        self.speak(answer, expect_response=asked_question)
        return True

    def shutdown(self):
        self.remove_fallback(self.handle_fallback)
        super(FhemSkill, self).shutdown()

    def stop(self):
        pass

    def __translate(self, term, data=None):
        try:
            return self.dialog_renderer.render(term, data)
        except BaseException:
            # no dialog at all (e.g. unsupported language or unknown term
            return term
Пример #6
0
class FhemSkill(FallbackSkill):

    def __init__(self):
        super(FhemSkill, self).__init__(name="FhemSkill")
        LOG.info("__init__")
        self.fhem = None
        self.enable_fallback = False
        self.device_location = ""

    def _setup(self, force=False):
        # when description of home.mycroft.ai > Devices > [this mycroft device]
        # is filled, use this the name of the room where mycroft is located
        if self.settings.get('device_location', False):
            dev = DeviceApi()
            info = dev.get()
            if 'description' in info:
                self.device_location = info['description']
        LOG.debug("mycroft device location: {}".format(self.device_location))

        if self.settings and (force or self.fhem is None):
            LOG.debug("_setup")
            portnumber = self.settings.get('portnum')
            try:
                portnumber = int(portnumber)
            except TypeError:
                portnumber = 8083
            except ValueError:
                # String might be some rubbish (like '')
                portnumber = 0

            self.fhem = \
                python_fhem.Fhem(self.settings.get('host'),
                                 port=portnumber,  csrf=True,
                                 protocol=self.settings.get('protocol',
                                                            'http').lower(),
                                 use_ssl=self.settings.get('ssl', False),
                                 username=self.settings.get('username'),
                                 password=self.settings.get('password')
                                 )
            self.fhem.connect()
            LOG.debug("connect: {}".format(self.fhem.connected()))
            if self.fhem.connected():
                self.allowed_devices_room = self.settings.get('room', 'Homebridge')
                self.ignore_rooms = self.settings.get('ignore_rooms', '')

                # Check if natural language control is loaded at fhem-server
                # and activate fallback accordingly
                LOG.debug("fallback_device_name %s" %
                          self.settings.get('fallback_device_name'))
                LOG.debug("enable_fallback %s" %
                          self.settings.get('enable_fallback'))
                if self.settings.get('enable_fallback') and \
                   self.settings.get('fallback_device_name', ""):
                    fallback_device = \
                        self.fhem.get_device(self.settings.get(
                            'fallback_device_name', ""))
                    if fallback_device:
                        # LOG.debug("fallback device {}".format(fallback_device))
                        self.fallback_device_name = self.settings.get(
                            'fallback_device_name', "")
                        self.fallback_device_type = self.fhem.get_internals(
                            "TYPE", name=self.fallback_device_name)[
                                self.fallback_device_name]
                        LOG.debug("fallback_device_type is %s" %
                                  self.fallback_device_type)
                        if self.fallback_device_type in ["Talk2Fhem",
                                                         "TEERKO",
                                                         "Babble"]:
                            self.enable_fallback = True
                        else:
                            self.enable_fallback = False
                else:
                    self.enable_fallback = False
                LOG.debug('fhem-fallback enabled: %s' % self.enable_fallback)

    def initialize(self):
        self._setup(True)
        # Needs higher priority than general fallback skills
        self.register_fallback(self.handle_fallback, 2)
        # Check and then monitor for credential changes
        self.settings_change_callback = self.on_websettings_changed

        # registering entities, is that actually necessary?
        self.register_entity_file('action.entity')
        self.register_entity_file('room.entity')
        self.register_entity_file('open.entity')
        self.register_entity_file('close.entity')
        #self.register_entity_file('blind.entity')

    def on_websettings_changed(self):
        # Only attempt to load if the host is set
        LOG.debug("websettings changed")
        if self.settings.get('host', None):
            try:
                self._setup(force=True)
            except Exception:
                pass

    #@intent_file_handler('blind.intent')
    def handle_blind_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        LOG.info("Starting Blind Intent")
        LOG.info("message.data {}".format(message.data))

        device = message.data.get("device")
        action = ""
        percent = None
        if message.data.get("open"):
            action = "open"
            speak_action = message.data.get("open")
            target_pct = 0
        elif message.data.get("close"):
            action = "closed" # sic!
            speak_action = message.data.get("close")
            target_pct = 100
        elif message.data.get("percent"):
            percent = message.data.get("percent")
            if percent.isdigit():
                action = "pct"
                target_pct = int(percent)

        if not action:
            LOG.info("no action for blind intent found!")
            return False
        if message.data.get("room"):
            room = message.data.get("room")
        else:
            # if no room is given use device location
            room = self.device_location
        allowed_types = 'blind'
        LOG.info("Device: %s" % device)
        LOG.info("Action: %s" % action)
        LOG.info("Room: %s" % room)
        if percent:
            LOG.info("Percent: %s" % percent)
        try:
            fhem_device = self._find_device(device, allowed_types, room)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": device})
            return
        LOG.info("Entity State: %s" % fhem_device['state'])

        open_pct = 0
        closed_pct = 100

        blind = self.fhem.get_device(fhem_device['id'])[0]
        if blind['Internals']['TYPE'] == 'ROLLO':
            if action == "pct":
                self.fhem.send_cmd("set {} pct {}".format(fhem_device['id'],
                                                          target_pct))
                self.speak_dialog('fhem.blind.set',
                                  data={"device": device,
                                        "percent": target_pct})
            else:
                self.fhem.send_cmd("set {} {}".format(fhem_device['id'],
                                                      action))
                self.speak_dialog('fhem.blind', data={"device": device,
                                                      "action": speak_action,
                                                      "room": room})
        else:
            self.speak_dialog('fhem.error.notsupported')
            return

    @intent_file_handler('switch.intent')
    def handle_switch_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        LOG.debug("Starting Switch Intent")
        LOG.debug("message.data {}".format(message.data))

        device = message.data.get("device")
        if message.data.get("action"):
            action = message.data.get("action")
            self.log.info("action: {}".format(action))
        else:
            # when no action is given toggle the device
            action = "toggle"
        if message.data.get("room"):
            room = message.data.get("room")
        else:
            # if no room is given use device location
            room = self.device_location
        allowed_types = '(light|switch|outlet)'
        LOG.debug("Device: %s" % device)
        LOG.debug("Action: %s" % action)
        LOG.debug("Room: %s" % room)

        # TODO if entity is 'all', 'any' or 'every' turn on
        # every single entity not the whole group
        try:
            fhem_device = self._find_device(device, allowed_types, room)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": device})
            return
        LOG.debug("Entity State: %s" % fhem_device['state'])
        # fhem_data = {'entity_id': fhem_entity['id']}

        # keep original actioname for speak_dialog
        # when device already is in desiredstate
        if action != 'toggle':
            original_action = action
        else:
            original_action = self.translate('toggle_keyword')
        action_values = self.translate_namedvalues('actions.value')
        if action in action_values.keys():
            action = action_values[action]
        LOG.debug("- action: %s" % action)
        LOG.debug("- state: %s" % fhem_device['state']['Value'])
        if fhem_device['state']['Value'] == action:
            LOG.debug("Entity in requested state")
            self.speak_dialog('fhem.device.already', data={
                'dev_name': fhem_device['dev_name'],
                'action': original_action})
        elif action == 'toggle':
            if(fhem_device['state']['Value'] == 'off'):
                action = 'on'
            else:
                action = 'off'
            LOG.debug("toggled action: %s" % action)
            self.fhem.send_cmd("set {} {}".format(fhem_device['id'], action))
            self.speak_dialog('fhem.switch',
                              data={'dev_name': fhem_device['dev_name'],
                                    'action': original_action})
        elif action in ["on", "off"]:
            LOG.debug("action: on/off")
            self.speak_dialog('fhem.switch',
                              data={'dev_name': fhem_device['dev_name'],
                                    'action': original_action})
            self.fhem.send_cmd("set {} {}".format(fhem_device['id'], action))
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(AdaptIntent().optionally("LightsKeyword")
                    .require("SetVerb").require("Device")
                    .require("BrightnessValue"))
    def handle_light_set_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if(self.fhem is None):
            self.speak_dialog('fhem.error.setup')
            return
        device = message.data["Device"]
        allowed_types = ['light']  # TODO
        try:
            brightness_req = float(message.data["BrightnessValue"])
            if brightness_req > 100 or brightness_req < 0:
                self.speak_dialog('fhem.brightness.badreq')
        except KeyError:
            brightness_req = 10.0
        brightness_value = int(brightness_req / 100 * 255)
        brightness_percentage = int(brightness_req)
        LOG.debug("device: %s" % device)
        LOG.debug("Brightness Value: %s" % brightness_value)
        LOG.debug("Brightness Percent: %s" % brightness_percentage)
        try:
            fhem_device = self.fhem.find_device(device, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={
                              "dev_name": device})
            return
        fhem_data = {'device_id': fhem_device['id']}

        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        # TODO - Allow value set
        if "SetVerb" in message.data:
            fhem_data['brightness'] = brightness_value
            fhem_data['dev_name'] = fhem_device['dev_name']
            self.fhem.execute_service("fhem", "turn_on", fhem_data)
            self.speak_dialog('fhem.brightness.dimmed',
                              data=fhem_data)
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(AdaptIntent().optionally("LightsKeyword")
                    .one_of("IncreaseVerb", "DecreaseVerb",
                            "LightBrightenVerb", "LightDimVerb")
                    .require("Device").optionally("BrightnessValue"))
    def handle_light_adjust_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        allowed_types = ['light']
        device = message.data["Device"]

        try:
            brightness_req = float(message.data["BrightnessValue"])
            if brightness_req > 100 or brightness_req < 0:
                self.speak_dialog('fhem.brightness.badreq')
        except KeyError:
            brightness_req = 10.0
        brightness_value = int(brightness_req / 100 * 255)
        # brightness_percentage = int(brightness_req) # debating use
        LOG.debug("device: %s" % device)
        LOG.debug("Brightness Value: %s" % brightness_value)
        try:
            fhem_device = self.fhem.find_device(device, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={
                              "dev_name": device})
            return
        fhem_data = {'device_id': fhem_device['id']}
        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        # if self.lang == 'de':
        #    if action == 'runter' or action == 'dunkler':
        #        action = 'dim'
        #    elif action == 'heller' or action == 'hell':
        #        action = 'brighten'
        if "DecreaseVerb" in message.data or \
                "LightDimVerb" in message.data:
            if fhem_device['state'] == "off":
                self.speak_dialog('fhem.brightness.cantdim.off',
                                  data=fhem_device)
            else:
                light_attrs = self.fhem.find_entity_attr(fhem_device['id'])
                if light_attrs['unit_measure'] is None:
                    self.speak_dialog(
                        'fhem.brightness.cantdim.dimmable',
                        data=fhem_device)
                else:
                    fhem_data['brightness'] = light_attrs['unit_measure']
                    if fhem_data['brightness'] < brightness_value:
                        fhem_data['brightness'] = 10
                    else:
                        fhem_data['brightness'] -= brightness_value
                    self.fhem.execute_service("fhem", "turn_on", fhem_data)
                    fhem_data['dev_name'] = fhem_device['dev_name']
                    self.speak_dialog('fhem.brightness.decreased',
                                      data=fhem_data)
        elif "IncreaseVerb" in message.data or \
                "LightBrightenVerb" in message.data:
            if fhem_device['state'] == "off":
                self.speak_dialog(
                    'fhem.brightness.cantdim.off',
                    data=fhem_device)
            else:
                light_attrs = self.fhem.find_entity_attr(fhem_device['id'])
                if light_attrs['unit_measure'] is None:
                    self.speak_dialog(
                        'fhem.brightness.cantdim.dimmable',
                        data=fhem_device)
                else:
                    fhem_data['brightness'] = light_attrs['unit_measure']
                    if fhem_data['brightness'] > brightness_value:
                        fhem_data['brightness'] = 255
                    else:
                        fhem_data['brightness'] += brightness_value
                    self.fhem.execute_service("fhem", "turn_on", fhem_data)
                    fhem_data['dev_name'] = fhem_device['dev_name']
                    self.speak_dialog('fhem.brightness.increased',
                                      data=fhem_data)
        else:
            self.speak_dialog('fhem.error.sorry')
            return

    @intent_handler(AdaptIntent().require("AutomationActionKeyword")
                    .require("Entity"))
    def handle_automation_intent(self, message):
        # TODO not supported yet
        self.speak_dialog('fhem.error.notsupported')
        return
        #
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        entity = message.data["Entity"]
        allowed_types = ['automation', 'scene', 'script']  # TODO
        LOG.debug("Entity: %s" % entity)
        # also handle scene and script requests
        try:
            fhem_entity = self.fhem.find_device(entity, allowed_types)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        fhem_data = {'entity_id': fhem_entity['id']}
        if fhem_entity is None:
            self.speak_dialog('fhem.device.unknown', data={
                              "dev_name": entity})
            return

        # IDEA: set context for 'turn it off again' or similar
        # self.set_context('Entity', fhem_entity['dev_name'])

        LOG.debug("Triggered automation/scene/script: {}".format(fhem_data))
        if "automation" in fhem_entity['id']:
            self.fhem.execute_service('automation', 'trigger', fhem_data)
            self.speak_dialog('fhem.automation.trigger',
                              data={"dev_name": fhem_entity['dev_name']})
        elif "script" in fhem_entity['id']:
            self.speak_dialog('fhem.automation.trigger',
                              data={"dev_name": fhem_entity['dev_name']})
            self.fhem.execute_service("fhem", "turn_on", data=fhem_data)
        elif "scene" in fhem_entity['id']:
            self.speak_dialog('fhem.device.on',
                              data=fhem_entity)
            self.fhem.execute_service("fhem", "turn_on", data=fhem_data)

    @intent_file_handler('sensor.intent')
    def handle_sensor_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return

        device = message.data.get("device")
        if message.data.get("room"):
            room = message.data.get("room")
        else:
            # if no room is given use device location
            room = self.device_location
        allowed_types = '(sensor|thermometer)'
        LOG.debug("device: %s" % device)
        try:
            fhem_device = self._find_device(device, allowed_types, room)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={
                              "dev_name": device})
            return

        device = fhem_device['id']
        sensor_name = fhem_device['dev_name']
        sensor_state = ""
        sensor_unit = ""

        sensor_values = self.translate_namedvalues('sensor.value')
        tokens = fhem_device['state']['Value'].split(" ")
        for t in range(0, len(tokens)):
            tok = tokens[t].lower().replace(":", "")
            # LOG.debug("tok = %s" % tok)
            if tok in ['t', 'temp', 'temperatur', 'temperature']:
                sensor_state += sensor_values["temperature"]
            elif tok in ['h', 'hum', 'humidity']:
                sensor_state += sensor_values["humidity"]
            elif tok in ['p', 'pamb', 'press', 'pressure']:
                sensor_state += sensor_values["pressure"]
            else:
                sensor_state += tokens[t]
            sensor_state += " "

        LOG.debug("fhem_device['state']['Value']: %s" %
                  fhem_device['state']['Value'])
        LOG.debug("sensor_state: %s" % sensor_state)
        self.speak_dialog('fhem.sensor', data={
             "dev_name": sensor_name,
             "value": sensor_state,
             "unit": sensor_unit})

        # # IDEA: set context for 'read it out again' or similar
        # # self.set_context('Entity', fhem_entity['dev_name'])
        #
        # unit_measurement = self.fhem.find_entity_attr(entity)
        # if unit_measurement[0] is not None:
        #     sensor_unit = unit_measurement[0]
        # else:
        #     sensor_unit = ''
        #
        # sensor_name = unit_measurement[1]
        # sensor_state = unit_measurement[2]
        # # extract unit for correct pronounciation
        # # this is fully optional
        # try:
        #     from quantulum import parser
        #     quantulumImport = True
        # except ImportError:
        #     quantulumImport = False
        #
        # if quantulumImport and unit_measurement != '':
        #     quantity = parser.parse((u'{} is {} {}'.format(
        #         sensor_name, sensor_state, sensor_unit)))
        #     if len(quantity) > 0:
        #         quantity = quantity[0]
        #         if (quantity.unit.name != "dimensionless" and
        #                 quantity.uncertainty <= 0.5):
        #             sensor_unit = quantity.unit.name
        #             sensor_state = quantity.value
        #
        # self.speak_dialog('fhem.sensor', data={
        #     "dev_name": sensor_name,
        #     "value": sensor_state,
        #     "unit": sensor_unit})
        # # IDEA: Add some context if the person wants to look the unit up
        # # Maybe also change to name
        # # if one wants to look up "outside temperature"
        # # self.set_context("SubjectOfInterest", sensor_unit)

    @intent_file_handler('presence.intent')
    def handle_presence_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        wanted = message.data["entity"]
        LOG.debug("wanted: %s" % wanted)

        try:
            roommates = self.fhem.get(room=self.allowed_devices_room,
                                      device_type='ROOMMATE')
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return

        if len(roommates) < 1:
            self.speak_dialog('fhem.presence.error')
            return

        presence = None
        bestRatio = 66

        for rm in roommates:
            if 'rr_realname' in rm['Attributes'].keys():
                realname = rm['Attributes'][rm['Attributes']['rr_realname']]
                LOG.debug("realname: %s" % realname)
                ratio = fuzz.ratio(wanted.lower(), realname.lower())
                LOG.debug("ratio: %s" % ratio)
                if ratio > bestRatio:
                    presence = rm['Readings']['presence']['Value']
                    bestName = realname
                    bestRatio = ratio

        presence_values = self.translate_namedvalues('presence.value')
        if presence:
            location = presence_values[presence]
            self.speak_dialog('fhem.presence.found',
                              data={'wanted': bestName,
                                    'location': location})
        else:
            self.speak_dialog('fhem.presence.error')

    @intent_file_handler('set.climate.intent')
    def handle_set_thermostat_intent(self, message):
        self._setup()
        if self.fhem is None:
            self.speak_dialog('fhem.error.setup')
            return
        LOG.debug("Starting Thermostat Intent")

        if message.data.get("device"):
            device = message.data.get("device")
        else:
            device = "thermostat"
        if message.data.get("room"):
            room = message.data.get("room")
        else:
            room = self.device_location

        LOG.debug("Device: %s" % device)
        LOG.debug("This is the message data: %s" % message.data)
        temperature = message.data["temp"]
        LOG.debug("desired temperature from message: %s" % temperature)

        allowed_types = 'thermostat'
        try:
            fhem_device = self._find_device(device, allowed_types, room)
        except ConnectionError:
            self.speak_dialog('fhem.error.offline')
            return
        if fhem_device is None:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": device})
            return
        LOG.debug("Entity State: %s" % fhem_device['state'])

        device_id = fhem_device['id']
        target_device = device_id

        # defaults for min/max temp and step
        minValue = 5.0
        maxValue = 35.0
        minStep = 0.5
        unit = ""
        cmd = ""

        # check thermostat type, derive command and min/max values
        LOG.debug("fhem_device: %s" % fhem_device)
        # for that get thermostat device
        td = self.fhem.get_device(fhem_device['id'])
        if len(td) != 1:
            self.speak_dialog('fhem.device.unknown', data={"dev_name": device})
            return
        td = td[0]
        LOG.debug("td: %s" % td)
        if 'desired-temp' in td['Readings']:
            cmd = "desired-temp"
            if ('FBTYPE' in td['Readings']) and \
               (td['Readings']['FBTYPE'] == 'Comet DECT'):
                # LOG.debug("Comet DECT")
                minValue = 8.0
                maxValue = 28.0
            elif td['Internals']['TYPE'] == 'FHT':
                # LOG.debug("FHT")
                minValue = 6.0
                maxValue = 30.0
            elif td['Internals']['TYPE'] == 'CUL_HM':
                LOG.debug("HM")
                # test for Clima-Subdevice
                if 'channel_04' in td['Internals']:
                    target_device = td['Internals']['channel_04']
        elif 'desiredTemperature' in td['Readings']:
            # LOG.debug("MAX")
            cmd = "desiredTemperature"
            minValue = 4.5
            maxValue = 30.5
        elif 'desired' in td['Readings']:
            # LOG.debug("PID20")
            cmd = "desired"
        elif 'homebridgeMapping' in td['Attributes']:
            LOG.debug("homebridgeMapping")
            hbm = td['Attributes']['homebridgeMapping'].split(" ")
            for h in hbm:
                # TargetTemperature=desired-temp::desired-temp,
                # minValue=5,maxValue=35,minStep=0.5,nocache=1
                if h.startswith("TargetTemperature"):
                    targettemp = (h.split("=", 1)[1]).split(",")
                    LOG.debug("targettemp = %s" % targettemp)
                    for t in targettemp:
                        LOG.debug("t = %s" % t)
                        if t.startswith("desired-temp"):
                            t2 = t.split(":")
                            cmd = t2[0]
                            if t2[1] != '':
                                target_device = t2[1]
                        elif t.startswith("minValue"):
                            minValue = float(t.split("=")[1])
                        elif t.startswith("maxValue"):
                            maxValue = float(t.split("=")[1])
                        elif t.startswith("minStep"):
                            minStep = float(t.split("=")[1])

        if not cmd:
            LOG.info("FHEM device %s has unknown thermostat type" % device_id)
            self.speak_dialog('fhem.error.notsupported')
            return

        LOG.debug("target_device: %s cmd: %s" % (target_device, cmd))
        LOG.debug("minValue: %s maxValue: %s minStep: %s" % (minValue, maxValue,
                                                             minStep))

        # check if desired temperature is out of bounds
        if (float(temperature) < minValue) or (float(temperature) > maxValue) \
           or (float(temperature) % minStep != 0.0):
            self.speak_dialog('fhem.thermostat.badreq',
                              data={"minValue": minValue,
                                    "maxValue": maxValue,
                                    "minStep": minStep})
            return

        action = "%s %s" % (cmd, temperature)
        LOG.debug("set %s %s" % (target_device, action))
        self.fhem.send_cmd("set {} {}".format(target_device, action))
        self.speak_dialog('fhem.set.thermostat',
                          data={
                              "dev_name": device,
                              "value": temperature,
                              "unit": unit})

    def handle_fallback(self, message):
        LOG.debug("entering handle_fallback with utterance '%s'" %
                  message.data.get('utterance'))
        self._setup()
        if self.fhem is None:
            LOG.debug("FHEM setup error")
            self.speak_dialog('fhem.error.setup')
            return False
        if not self.enable_fallback:
            LOG.debug("fallback not enabled!")
            return False

        # pass message to FHEM-server
        try:
            # TODO check response after switch to python-fhem lib
            if self.fallback_device_type == "TEERKO":
                # LOG.debug("fallback device type TEERKO")
                response = self.fhem.send_cmd(
                    "set {} TextCommand {}".format(self.fallback_device_name,
                                                   message.data.get('utterance')))
            elif self.fallback_device_type == "Talk2Fhem":
                # LOG.debug("fallback device type Talk2Fhem")
                response = self.fhem.send_cmd(
                    "set {} {}".format(self.fallback_device_name,
                                       message.data.get('utterance')))
            elif self.fallback_device_type == "Babble":
                # LOG.debug("fallback device type Babble")
                cmd = '{Babble_DoIt("%s","%s","testit","1")}' % \
                    (self.fallback_device_name, message.data.get('utterance'))
                response = self.fhem.send_cmd(cmd)
                # Babble gives feedback through response
                # existence of string '[Babble_Normalize]' means success!
                if response.text.find('[Babble_Normalize]') > 0:
                    return True
                else:
                    return False
            else:
                LOG.debug("fallback device type UNKNOWN")
                return False
        except ConnectionError:
            LOG.debug("connection error")
            self.speak_dialog('fhem.error.offline')
            return False

        fdn = self.fallback_device_name
        result = self.fhem.get_readings(name=fdn)
        LOG.debug("result: %s" % result)

        if not result:
            # LOG.debug("no result")
            return False

        answer = ""
        if self.fallback_device_type == "Talk2Fhem":
            if result[fdn]['status']['Value'] == 'answers':
                # LOG.debug("answering with Talk2Fhem result")
                answer = result[fdn]['answers']['Value']
            else:
                return False
        elif self.fallback_device_type == "TEERKO":
            if result[fdn]['Answer']['Value'] is not None:
                # LOG.debug("answering with TEERKO result")
                answer = result[fdn]['Answer']['Value']
            else:
                return False
        # Babble gives feedback through response, so nothing to do here
        else:
            # LOG.debug("status undefined")
            return False

        if answer == "":
            # LOG.debug("empty answer")
            return False

        asked_question = False
        # TODO: maybe enable conversation here if server asks sth like
        # "In which room?" => answer should be directly passed to this skill
        if answer.endswith("?"):
            LOG.debug("answer endswith question mark")
            asked_question = True
        self.speak(answer, expect_response=asked_question)
        return True

    def _find_device(self, device, allowed_types, room=""):
        LOG.debug("device: {} allowed_types: {} room: {}".format(device,
                                                                 allowed_types,
                                                                 room))
        filter_dict = {'genericDeviceType': allowed_types}

        # new search strategy: first check if there is a fit in specified room
        if room:
            room = self._normalize(self._clean_common_words(room))
            # LOG.debug("normalized room: {}".format(room))
            filter_dict['room'] = room
        device_candidates = self.fhem.get(room=self.allowed_devices_room,
                                          filters=filter_dict)

        if len(device_candidates) == 1:
            # TODO can we do anything if len(...) > 1 ?
            LOG.debug("perfect match")
            # we have a perfect match:
            # there is only one device of the allowed type in the room
            dc = device_candidates[0]
            best_device = {"id": dc['Name'],
                           "dev_name": self._get_aliasname(dc),
                           "state": dc['Readings']['state'],
                           "best_score": 999}
            return best_device

        # try again without filter on room
        if 'room' in filter_dict.keys():
            LOG.debug("try again without filter on room")
            del filter_dict['room']
        device_candidates = self.fhem.get(room=self.allowed_devices_room,
                                          filters=filter_dict)
        # LOG.debug(device_candidates)

        # require a score above 50%
        best_score = 50
        best_device = None

        if device_candidates:
            for dc in device_candidates:
                # LOG.debug("==================================================")
                norm_name = self._normalize(dc['Name'])
                norm_name_list = norm_name.split(" ")
                # LOG.debug("norm_name_list = %s" % norm_name_list)

                dev_room = self._get_normalized_room_list(dc)
                for r in dev_room:
                    if (r not in norm_name_list):
                        norm_name += (" " + self._normalize(r))

                # LOG.debug("dev_room: {}".format(dev_room))
                # LOG.debug("norm_name = %s" % norm_name)

                alias = self._get_aliasname(dc)
                norm_alias = self._normalize(alias)

                try:
                    if (norm_name != norm_alias) and ('alias' in
                                                      dc['Attributes']):
                        score = fuzz.token_sort_ratio(
                            device,
                            norm_alias)
                        # add bonus if room name match
                        if room and dev_room:
                            score += self._get_bonus_for_room(room,
                                                              dev_room[0])
                        if score > best_score:
                            best_score = score
                            best_device = {
                                "id": dc['Name'],
                                "dev_name": alias,
                                "state": dc['Readings']['state'],
                                "best_score": best_score}

                    score = fuzz.token_sort_ratio(device, norm_name)
                    # add bonus if room name match
                    if room and dev_room:
                        score += self._get_bonus_for_room(room, dev_room[0])
                    # LOG.debug("%s %s" % (norm_name, score))
                    if score > best_score:
                        best_score = score
                        best_device = {
                            "id": dc['Name'],
                            "dev_name": alias,
                            "state": dc['Readings']['state'],
                            "best_score": best_score}

                except KeyError:
                    pass  # print("KeyError")
            LOG.debug("best device = %s" % best_device)
            return best_device

    def _get_bonus_for_room(self, room, dev_room):
        if fuzz.ratio(room, dev_room) > REQUIRED_RATIO_FOR_BONUS:
            LOG.debug("bonus! {} {}".format(room, dev_room))
            return BONUS
        else:
            return 0

    def _get_aliasname(self, device):
        if 'alias' in device['Attributes']:
            alias = device['Attributes']['alias']
        else:
            alias = device['Name']
        return alias

    def _get_normalized_room_list(self, dev):
        # add device room to name
        dev_room = []
        # LOG.debug(self.ignore_rooms)
        ignore = [x.lower() for x in self.ignore_rooms.split(",")]
        # LOG.debug("ignore = %s" % ignore)
        if 'room' in dev['Attributes']:
            rooms = [x.lower() for x in dev['Attributes']['room'].split(",")]
            rooms.remove(self.allowed_devices_room.lower())
            # LOG.debug("rooms = %s" % rooms)
            for r in rooms:
                # LOG.debug("r = %s" % r)
                if (r not in ignore):
                    # LOG.debug("adding r = %s" % r)
                    dev_room.append(r)
        return dev_room

    def _normalize(self, name):
        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
        s2 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
        return s2.replace("_", " ").replace("-", " ").replace(".", " ")

    def _clean_common_words(self, text):
        txt = text.split(" ")
        common_words = self.translate_list("common.words")
        # LOG.debug("common_words {}".format(common_words))
        for i in range(0, len(txt)):
            if txt[i] in common_words:
                txt[i] = ""
        res = ""
        for t in txt:
            res += "{} ".format(t)
        prev_len = len(res) + 1
        while prev_len > len(res):
            res.replace("  ", " ")
            prev_len = len(res)
        # LOG.debug("out {}".format(res))
        return res.strip()

    def shutdown(self):
        self.remove_fallback(self.handle_fallback)
        super(FhemSkill, self).shutdown()

    def stop(self):
        pass

    def __translate(self, term, data=None):
        try:
            return self.dialog_renderer.render(term, data)
        except BaseException:
            # no dialog at all (e.g. unsupported language or unknown term
            return term