Ejemplo n.º 1
0
async def test_get_automation_trace(hass, hass_ws_client):
    """Test tracing an automation."""
    id = 1

    def next_id():
        nonlocal id
        id += 1
        return id

    sun_config = {
        "id": "sun",
        "trigger": {"platform": "event", "event_type": "test_event"},
        "action": {"service": "test.automation"},
    }
    moon_config = {
        "id": "moon",
        "trigger": [
            {"platform": "event", "event_type": "test_event2"},
            {"platform": "event", "event_type": "test_event3"},
        ],
        "condition": {
            "condition": "template",
            "value_template": "{{ trigger.event.event_type=='test_event2' }}",
        },
        "action": {"event": "another_event"},
    }

    assert await async_setup_component(
        hass,
        "automation",
        {
            "automation": [
                sun_config,
                moon_config,
            ]
        },
    )

    with patch.object(config, "SECTIONS", ["automation"]):
        await async_setup_component(hass, "config", {})

    client = await hass_ws_client()
    contexts = {}

    # Trigger "sun" automation
    context = Context()
    hass.bus.async_fire("test_event", context=context)
    await hass.async_block_till_done()

    # List traces
    await client.send_json({"id": next_id(), "type": "automation/trace/list"})
    response = await client.receive_json()
    assert response["success"]
    run_id = _find_run_id(response["result"], "sun")

    # Get trace
    await client.send_json(
        {
            "id": next_id(),
            "type": "automation/trace/get",
            "automation_id": "sun",
            "run_id": run_id,
        }
    )
    response = await client.receive_json()
    assert response["success"]
    trace = response["result"]
    assert trace["context"]["parent_id"] == context.id
    assert len(trace["action_trace"]) == 1
    assert len(trace["action_trace"]["action/0"]) == 1
    assert trace["action_trace"]["action/0"][0]["error"]
    assert "result" not in trace["action_trace"]["action/0"][0]
    assert trace["condition_trace"] == {}
    assert trace["config"] == sun_config
    assert trace["context"]
    assert trace["error"] == "Unable to find service test.automation"
    assert trace["state"] == "stopped"
    assert trace["trigger"] == "event 'test_event'"
    assert trace["unique_id"] == "sun"
    assert trace["variables"]
    contexts[trace["context"]["id"]] = {
        "run_id": trace["run_id"],
        "automation_id": trace["automation_id"],
    }

    # Trigger "moon" automation, with passing condition
    hass.bus.async_fire("test_event2")
    await hass.async_block_till_done()

    # List traces
    await client.send_json({"id": next_id(), "type": "automation/trace/list"})
    response = await client.receive_json()
    assert response["success"]
    run_id = _find_run_id(response["result"], "moon")

    # Get trace
    await client.send_json(
        {
            "id": next_id(),
            "type": "automation/trace/get",
            "automation_id": "moon",
            "run_id": run_id,
        }
    )
    response = await client.receive_json()
    assert response["success"]
    trace = response["result"]
    assert len(trace["action_trace"]) == 1
    assert len(trace["action_trace"]["action/0"]) == 1
    assert "error" not in trace["action_trace"]["action/0"][0]
    assert "result" not in trace["action_trace"]["action/0"][0]
    assert len(trace["condition_trace"]) == 1
    assert len(trace["condition_trace"]["condition/0"]) == 1
    assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True}
    assert trace["config"] == moon_config
    assert trace["context"]
    assert "error" not in trace
    assert trace["state"] == "stopped"
    assert trace["trigger"] == "event 'test_event2'"
    assert trace["unique_id"] == "moon"
    assert trace["variables"]
    contexts[trace["context"]["id"]] = {
        "run_id": trace["run_id"],
        "automation_id": trace["automation_id"],
    }

    # Trigger "moon" automation, with failing condition
    hass.bus.async_fire("test_event3")
    await hass.async_block_till_done()

    # List traces
    await client.send_json({"id": next_id(), "type": "automation/trace/list"})
    response = await client.receive_json()
    assert response["success"]
    run_id = _find_run_id(response["result"], "moon")

    # Get trace
    await client.send_json(
        {
            "id": next_id(),
            "type": "automation/trace/get",
            "automation_id": "moon",
            "run_id": run_id,
        }
    )
    response = await client.receive_json()
    assert response["success"]
    trace = response["result"]
    assert len(trace["action_trace"]) == 0
    assert len(trace["condition_trace"]) == 1
    assert len(trace["condition_trace"]["condition/0"]) == 1
    assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": False}
    assert trace["config"] == moon_config
    assert trace["context"]
    assert "error" not in trace
    assert trace["state"] == "stopped"
    assert trace["trigger"] == "event 'test_event3'"
    assert trace["unique_id"] == "moon"
    assert trace["variables"]
    contexts[trace["context"]["id"]] = {
        "run_id": trace["run_id"],
        "automation_id": trace["automation_id"],
    }

    # Trigger "moon" automation, with passing condition
    hass.bus.async_fire("test_event2")
    await hass.async_block_till_done()

    # List traces
    await client.send_json({"id": next_id(), "type": "automation/trace/list"})
    response = await client.receive_json()
    assert response["success"]
    run_id = _find_run_id(response["result"], "moon")

    # Get trace
    await client.send_json(
        {
            "id": next_id(),
            "type": "automation/trace/get",
            "automation_id": "moon",
            "run_id": run_id,
        }
    )
    response = await client.receive_json()
    assert response["success"]
    trace = response["result"]
    assert len(trace["action_trace"]) == 1
    assert len(trace["action_trace"]["action/0"]) == 1
    assert "error" not in trace["action_trace"]["action/0"][0]
    assert "result" not in trace["action_trace"]["action/0"][0]
    assert len(trace["condition_trace"]) == 1
    assert len(trace["condition_trace"]["condition/0"]) == 1
    assert trace["condition_trace"]["condition/0"][0]["result"] == {"result": True}
    assert trace["config"] == moon_config
    assert trace["context"]
    assert "error" not in trace
    assert trace["state"] == "stopped"
    assert trace["trigger"] == "event 'test_event2'"
    assert trace["unique_id"] == "moon"
    assert trace["variables"]
    contexts[trace["context"]["id"]] = {
        "run_id": trace["run_id"],
        "automation_id": trace["automation_id"],
    }

    # Check contexts
    await client.send_json({"id": next_id(), "type": "automation/trace/contexts"})
    response = await client.receive_json()
    assert response["success"]
    assert response["result"] == contexts
Ejemplo n.º 2
0
async def test_reload_config_service(hass, calls, hass_admin_user, hass_read_only_user):
    """Test the reload config service."""
    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: {
                "alias": "hello",
                "trigger": {"platform": "event", "event_type": "test_event"},
                "action": {
                    "service": "test.automation",
                    "data_template": {"event": "{{ trigger.event.event_type }}"},
                },
            }
        },
    )
    assert hass.states.get("automation.hello") is not None
    assert hass.states.get("automation.bye") is None
    listeners = hass.bus.async_listeners()
    assert listeners.get("test_event") == 1
    assert listeners.get("test_event2") is None

    hass.bus.async_fire("test_event")
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].data.get("event") == "test_event"

    test_reload_event = async_capture_events(hass, EVENT_AUTOMATION_RELOADED)

    with patch(
        "homeassistant.config.load_yaml_config_file",
        autospec=True,
        return_value={
            automation.DOMAIN: {
                "alias": "bye",
                "trigger": {"platform": "event", "event_type": "test_event2"},
                "action": {
                    "service": "test.automation",
                    "data_template": {"event": "{{ trigger.event.event_type }}"},
                },
            }
        },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                automation.DOMAIN,
                SERVICE_RELOAD,
                context=Context(user_id=hass_read_only_user.id),
                blocking=True,
            )
        await hass.services.async_call(
            automation.DOMAIN,
            SERVICE_RELOAD,
            context=Context(user_id=hass_admin_user.id),
            blocking=True,
        )
        # De-flake ?!
        await hass.async_block_till_done()

    assert len(test_reload_event) == 1

    assert hass.states.get("automation.hello") is None
    assert hass.states.get("automation.bye") is not None
    listeners = hass.bus.async_listeners()
    assert listeners.get("test_event") is None
    assert listeners.get("test_event2") == 1

    hass.bus.async_fire("test_event")
    await hass.async_block_till_done()
    assert len(calls) == 1

    hass.bus.async_fire("test_event2")
    await hass.async_block_till_done()
    assert len(calls) == 2
    assert calls[1].data.get("event") == "test_event2"
Ejemplo n.º 3
0
async def test_if_fires_on_zone_enter(hass, calls):
    """Test for firing on zone enter."""
    context = Context()
    hass.states.async_set(
        "geo_location.entity",
        "hello",
        {
            "latitude": 32.881011,
            "longitude": -117.234758,
            "source": "test_source"
        },
    )
    await hass.async_block_till_done()

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: {
                "trigger": {
                    "platform": "geo_location",
                    "source": "test_source",
                    "zone": "zone.test",
                    "event": "enter",
                },
                "action": {
                    "service": "test.automation",
                    "data_template": {
                        "some":
                        "{{ trigger.%s }}" % "}} - {{ trigger.".join((
                            "platform",
                            "entity_id",
                            "from_state.state",
                            "to_state.state",
                            "zone.name",
                        ))
                    },
                },
            }
        },
    )

    hass.states.async_set(
        "geo_location.entity",
        "hello",
        {
            "latitude": 32.880586,
            "longitude": -117.237564
        },
        context=context,
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].context.parent_id == context.id
    assert (calls[0].data["some"] ==
            "geo_location - geo_location.entity - hello - hello - test")

    # Set out of zone again so we can trigger call
    hass.states.async_set(
        "geo_location.entity",
        "hello",
        {
            "latitude": 32.881011,
            "longitude": -117.234758
        },
    )
    await hass.async_block_till_done()

    await hass.services.async_call(
        automation.DOMAIN,
        SERVICE_TURN_OFF,
        {ATTR_ENTITY_ID: ENTITY_MATCH_ALL},
        blocking=True,
    )

    hass.states.async_set(
        "geo_location.entity",
        "hello",
        {
            "latitude": 32.880586,
            "longitude": -117.237564
        },
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
Ejemplo n.º 4
0
def _state_empty_context(hass, entity_id):
    # We don't restore context unless we need it by joining the
    # events table on the event_id for state_changed events
    state = hass.states.get(entity_id)
    state.context = Context(id=None)
    return state
Ejemplo n.º 5
0
 def context(self, msg: dict[str, Any]) -> Context:
     """Return a context."""
     return Context(user_id=self.user.id)
Ejemplo n.º 6
0
async def test_reload(hass, hass_admin_user, hass_read_only_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = er.async_get(hass)

    assert await async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: {
                "test_1": {"initial": 50, "min": 0, "max": 51},
                "test_3": {"initial": 10, "min": 0, "max": 15},
            }
        },
    )

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_number.test_1")
    state_2 = hass.states.get("input_number.test_2")
    state_3 = hass.states.get("input_number.test_3")

    assert state_1 is not None
    assert state_2 is None
    assert state_3 is not None
    assert float(state_1.state) == 50
    assert float(state_3.state) == 10
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None

    with patch(
        "homeassistant.config.load_yaml_config_file",
        autospec=True,
        return_value={
            DOMAIN: {
                "test_1": {"initial": 40, "min": 0, "max": 51},
                "test_2": {"initial": 20, "min": 10, "max": 30},
            }
        },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                DOMAIN,
                SERVICE_RELOAD,
                blocking=True,
                context=Context(user_id=hass_read_only_user.id),
            )
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )
        await hass.async_block_till_done()

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_number.test_1")
    state_2 = hass.states.get("input_number.test_2")
    state_3 = hass.states.get("input_number.test_3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is None
    assert float(state_1.state) == 50
    assert float(state_2.state) == 20
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None
Ejemplo n.º 7
0
    def test_if_fires_on_zone_enter(self):
        """Test for firing on zone enter."""
        context = Context()
        self.hass.states.set(
            'geo_location.entity', 'hello', {
                'latitude': 32.881011,
                'longitude': -117.234758,
                'source': 'test_source'
            })
        self.hass.block_till_done()

        assert setup_component(
            self.hass, automation.DOMAIN, {
                automation.DOMAIN: {
                    'trigger': {
                        'platform': 'geo_location',
                        'source': 'test_source',
                        'zone': 'zone.test',
                        'event': 'enter',
                    },
                    'action': {
                        'service': 'test.automation',
                        'data_template': {
                            'some':
                            '{{ trigger.%s }}' % '}} - {{ trigger.'.join(
                                ('platform', 'entity_id', 'from_state.state',
                                 'to_state.state', 'zone.name'))
                        },
                    }
                }
            })

        self.hass.states.set('geo_location.entity',
                             'hello', {
                                 'latitude': 32.880586,
                                 'longitude': -117.237564
                             },
                             context=context)
        self.hass.block_till_done()

        self.assertEqual(1, len(self.calls))
        assert self.calls[0].context is context
        self.assertEqual(
            'geo_location - geo_location.entity - hello - hello - test',
            self.calls[0].data['some'])

        # Set out of zone again so we can trigger call
        self.hass.states.set('geo_location.entity', 'hello', {
            'latitude': 32.881011,
            'longitude': -117.234758
        })
        self.hass.block_till_done()

        common.turn_off(self.hass)
        self.hass.block_till_done()

        self.hass.states.set('geo_location.entity', 'hello', {
            'latitude': 32.880586,
            'longitude': -117.237564
        })
        self.hass.block_till_done()

        self.assertEqual(1, len(self.calls))
Ejemplo n.º 8
0
 def __init__(self, config, user_id, request_id):
     """Initialize the request data."""
     self.config = config
     self.request_id = request_id
     self.context = Context(user_id=user_id)
Ejemplo n.º 9
0
async def test_reload(hass, hass_admin_user):
    """Test reloading the YAML config."""
    assert await async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: [
                {
                    "name": "Person 1",
                    "id": "id-1"
                },
                {
                    "name": "Person 2",
                    "id": "id-2"
                },
            ]
        },
    )

    assert len(hass.states.async_entity_ids()) == 2

    state_1 = hass.states.get("person.person_1")
    state_2 = hass.states.get("person.person_2")
    state_3 = hass.states.get("person.person_3")

    assert state_1 is not None
    assert state_1.name == "Person 1"
    assert state_2 is not None
    assert state_2.name == "Person 2"
    assert state_3 is None

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: [
                    {
                        "name": "Person 1-updated",
                        "id": "id-1"
                    },
                    {
                        "name": "Person 3",
                        "id": "id-3"
                    },
                ]
            },
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )
        await hass.async_block_till_done()

    assert len(hass.states.async_entity_ids()) == 2

    state_1 = hass.states.get("person.person_1")
    state_2 = hass.states.get("person.person_2")
    state_3 = hass.states.get("person.person_3")

    assert state_1 is not None
    assert state_1.name == "Person 1-updated"
    assert state_2 is None
    assert state_3 is not None
    assert state_3.name == "Person 3"
Ejemplo n.º 10
0
async def test_config_reload(hass, hass_admin_user, hass_read_only_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = await entity_registry.async_get_registry(hass)

    _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids())

    config = {
        DOMAIN: {
            "test_1": {},
            "test_2": {
                CONF_NAME: "Hello World",
                CONF_ICON: "mdi:work",
                CONF_DURATION: 10,
            },
        }
    }

    assert await async_setup_component(hass, "timer", config)
    await hass.async_block_till_done()

    assert count_start + 2 == len(hass.states.async_entity_ids())
    await hass.async_block_till_done()

    state_1 = hass.states.get("timer.test_1")
    state_2 = hass.states.get("timer.test_2")
    state_3 = hass.states.get("timer.test_3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None

    assert STATUS_IDLE == state_1.state
    assert ATTR_ICON not in state_1.attributes
    assert ATTR_FRIENDLY_NAME not in state_1.attributes

    assert STATUS_IDLE == state_2.state
    assert "Hello World" == state_2.attributes.get(ATTR_FRIENDLY_NAME)
    assert "mdi:work" == state_2.attributes.get(ATTR_ICON)
    assert "0:00:10" == state_2.attributes.get(ATTR_DURATION)

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: {
                    "test_2": {
                        CONF_NAME: "Hello World reloaded",
                        CONF_ICON: "mdi:work-reloaded",
                        CONF_DURATION: 20,
                    },
                    "test_3": {},
                }
            },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                DOMAIN,
                SERVICE_RELOAD,
                blocking=True,
                context=Context(user_id=hass_read_only_user.id),
            )
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )
        await hass.async_block_till_done()

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("timer.test_1")
    state_2 = hass.states.get("timer.test_2")
    state_3 = hass.states.get("timer.test_3")

    assert state_1 is None
    assert state_2 is not None
    assert state_3 is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None

    assert STATUS_IDLE == state_2.state
    assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME)
    assert "mdi:work-reloaded" == state_2.attributes.get(ATTR_ICON)
    assert "0:00:20" == state_2.attributes.get(ATTR_DURATION)

    assert STATUS_IDLE == state_3.state
    assert ATTR_ICON not in state_3.attributes
    assert ATTR_FRIENDLY_NAME not in state_3.attributes
Ejemplo n.º 11
0
    async def async_run(
        self,
        run_variables: Optional[_VarsType] = None,
        context: Optional[Context] = None,
        started_action: Optional[Callable[..., Any]] = None,
    ) -> None:
        """Run script."""
        if context is None:
            self._log("Running script requires passing in a context",
                      level=logging.WARNING)
            context = Context()

        if self.is_running:
            if self.script_mode == SCRIPT_MODE_SINGLE:
                if self._max_exceeded != "SILENT":
                    self._log("Already running",
                              level=LOGSEVERITY[self._max_exceeded])
                return
            if self.script_mode == SCRIPT_MODE_RESTART:
                self._log("Restarting")
                await self.async_stop(update_state=False)
            elif len(self._runs) == self.max_runs:
                if self._max_exceeded != "SILENT":
                    self._log(
                        "Maximum number of runs exceeded",
                        level=LOGSEVERITY[self._max_exceeded],
                    )
                return

        # If this is a top level Script then make a copy of the variables in case they
        # are read-only, but more importantly, so as not to leak any variables created
        # during the run back to the caller.
        if self._top_level:
            if self.variables:
                try:
                    variables = self.variables.async_render(
                        self._hass,
                        run_variables,
                    )
                except template.TemplateError as err:
                    self._log("Error rendering variables: %s",
                              err,
                              level=logging.ERROR)
                    raise
            elif run_variables:
                variables = dict(run_variables)
            else:
                variables = {}

            variables["context"] = context
        else:
            variables = cast(dict, run_variables)

        if self.script_mode != SCRIPT_MODE_QUEUED:
            cls = _ScriptRun
        else:
            cls = _QueuedScriptRun
        run = cls(self._hass, self, cast(dict, variables), context,
                  self._log_exceptions)
        self._runs.append(run)
        if started_action:
            self._hass.async_run_job(started_action)
        self.last_triggered = utcnow()
        self._changed()

        try:
            await asyncio.shield(run.async_run())
        except asyncio.CancelledError:
            await run.async_stop()
            self._changed()
            raise
Ejemplo n.º 12
0
async def test_reload(hass, hass_admin_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = await entity_registry.async_get_registry(hass)

    _LOGGER.debug("ENTITIES @ start: %s", hass.states.async_entity_ids())

    assert await async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: {
                "test_1": None,
                "test_2": {
                    "name": "Hello World",
                    "icon": "mdi:work",
                    "initial": True
                },
            }
        },
    )

    _LOGGER.debug("ENTITIES: %s", hass.states.async_entity_ids())

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_boolean.test_1")
    state_2 = hass.states.get("input_boolean.test_2")
    state_3 = hass.states.get("input_boolean.test_3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is None
    assert STATE_ON == state_2.state

    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: {
                    "test_2": {
                        "name": "Hello World reloaded",
                        "icon": "mdi:work_reloaded",
                        "initial": False,
                    },
                    "test_3": None,
                }
            },
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_boolean.test_1")
    state_2 = hass.states.get("input_boolean.test_2")
    state_3 = hass.states.get("input_boolean.test_3")

    assert state_1 is None
    assert state_2 is not None
    assert state_3 is not None

    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None

    assert STATE_ON == state_2.state  # reload is not supposed to change entity state
    assert "Hello World reloaded" == state_2.attributes.get(ATTR_FRIENDLY_NAME)
    assert "mdi:work_reloaded" == state_2.attributes.get(ATTR_ICON)
async def test_service_run(hass, caplog):
    """Test running a service with keyword arguments."""
    notify_q = asyncio.Queue(0)
    await setup_script(
        hass,
        notify_q,
        dt(2020, 7, 1, 11, 59, 59, 999999),
        """

@service
def func1(arg1=1, arg2=2, context=None):
    x = 1
    x = 2 * x + 3
    log.info(f"this is func1 x = {x}, arg1 = {arg1}, arg2 = {arg2}")
    pyscript.done = [x, arg1, arg2, str(context)]

# registering twice will cause it to be called twice
@service("other.func2")
@service("other.func2")
@service
def func2(**kwargs):
    x = 1
    x = 2 * x + 3
    log.info(f"this is func1 x = {x}, kwargs = {kwargs}")
    has2 = service.has_service("pyscript", "func2")
    has3 = service.has_service("pyscript", "func3")
    del kwargs["context"]
    pyscript.done = [x, kwargs, has2, has3]

@service
def call_service(domain=None, name=None, **kwargs):
    if domain == "pyscript" and name == "func1":
        task.sleep(0)
        pyscript.func1(**kwargs)
    else:
        service.call(domain, name, **kwargs)

""",
    )
    context = Context(user_id="1234", parent_id="5678", id="8901")
    await hass.services.async_call("pyscript", "func1", {}, context=context)
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [5, 1, 2, str(context)]
    assert "this is func1 x = 5" in caplog.text

    await hass.services.async_call("pyscript",
                                   "func1", {"arg1": 10},
                                   context=context)
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [5, 10, 2, str(context)]

    await hass.services.async_call(
        "pyscript",
        "call_service",
        {
            "domain": "pyscript",
            "name": "func1",
            "arg1": "string1"
        },
        context=context,
    )
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [5, "string1", 2, str(context)]

    await hass.services.async_call("pyscript",
                                   "func1", {
                                       "arg1": "string1",
                                       "arg2": 123
                                   },
                                   context=context)
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [5, "string1", 123, str(context)]

    await hass.services.async_call("pyscript", "call_service", {
        "domain": "pyscript",
        "name": "func2"
    })
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [5, {"trigger_type": "service"}, 1, 0]

    await hass.services.async_call(
        "pyscript",
        "call_service",
        {
            "domain": "pyscript",
            "name": "func2",
            "arg1": "string1"
        },
    )
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [
        5, {
            "trigger_type": "service",
            "arg1": "string1"
        }, 1, 0
    ]

    await hass.services.async_call("pyscript", "func2", {
        "arg1": "string1",
        "arg2": 456
    })
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [
        5, {
            "trigger_type": "service",
            "arg1": "string1",
            "arg2": 456
        }, 1, 0
    ]

    await hass.services.async_call("other", "func2", {
        "arg1": "string1",
        "arg2": 123
    })
    ret = await wait_until_done(notify_q)
    assert literal_eval(ret) == [
        5, {
            "trigger_type": "service",
            "arg1": "string1",
            "arg2": 123
        }, 1, 0
    ]
    assert literal_eval(ret) == [
        5, {
            "trigger_type": "service",
            "arg1": "string1",
            "arg2": 123
        }, 1, 0
    ]
Ejemplo n.º 14
0
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:

    conf = config.get(DOMAIN, {})  # type: ConfigType

    if conf is None:
        # If we have a config entry, setup is done by that config entry.
        # If there is no config entry, this should fail.
        return bool(hass.config_entries.async_entries(DOMAIN))

    hass.data[DATA_AIHOME_CONFIG] = conf

    # Only import if we haven't before.
    if not hass.config_entries.async_entries(DOMAIN):
        hass.async_create_task(
            hass.config_entries.flow.async_init(
                DOMAIN,
                context={'source': config_entries.SOURCE_IMPORT},
                data={}))

    if CONF_HTTP in conf:
        if conf.get(CONF_HTTP) is None:
            conf[CONF_HTTP] = HTTP_SCHEMA({})
        hass.http.register_view(AihomeGateView(hass))
        hass.http.register_view(
            AihomeAuthView(
                hass,
                conf.get(CONF_HTTP, {}).get(CONF_HA_URL,
                                            hass.config.api.base_url)))
        global EXPIRATION
        EXPIRATION = timedelta(hours=conf.get(CONF_HTTP).get(
            CONF_EXPIRE_IN_HOURS, DEFAULT_EXPIRE_IN_HOURS))
        _LOGGER.info('[init] aihome run with "http mode"(mode 1)')
        MODE.append('http')
    if CONF_HTTP_PROXY in conf:
        if conf.get(CONF_HTTP_PROXY) is None:
            conf[CONF_HTTP_PROXY] = HTTP_PROXY({})
        _LOGGER.info('[init] aihome run with "http_proxy mode"(mode 2)')
        if CONF_SETTING not in conf:
            _LOGGER.error(
                '[init] fail to start aihome: http_proxy mode require mqtt congfiguration'
            )
            return False
        MODE.append('http_proxy')
    if CONF_SKILL in conf:
        if conf.get(CONF_SKILL) is None:
            conf[CONF_SKILL] = SKILL_SCHEMA({})
        _LOGGER.info('[init] aihome run with "skill mode"(mode 3)')
        if CONF_SETTING not in conf:
            _LOGGER.error(
                '[init] fail to start aihome: skil mode require mqtt congfiguration'
            )
            return False
        MODE.append('skill')

    aihome_util.ENTITY_KEY = conf.get(CONF_SETTING, {}).get(CONF_ENTITY_KEY)
    aihome_util.CONTEXT_AIHOME = Context(
        conf.get(CONF_SETTING, {}).get(CONF_USER_ID))

    platform = conf[CONF_PLATFORM]
    manager = hass.data[DATA_AIHOME_BIND_MANAGER] = aihome_util.BindManager(
        hass, platform)
    await manager.async_load()

    for p in platform:
        try:
            module = importlib.import_module('custom_components.{}.{}'.format(
                DOMAIN, p))
            _LOGGER.error('[init] import %s.%s', DOMAIN, p)
            HANDLER[p] = module.createHandler(hass)
        except ImportError:
            _LOGGER.error('[init] Unable to import %s.%s', DOMAIN, p)
            return False

    return True
Ejemplo n.º 15
0
async def test_reload(hass, hass_admin_user, hass_read_only_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = er.async_get(hass)

    assert await async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: {
                "dt1": {
                    "has_time": False,
                    "has_date": True,
                    "initial": "2019-1-1"
                },
                "dt3": {
                    CONF_HAS_TIME: True,
                    CONF_HAS_DATE: True
                },
            }
        },
    )

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_datetime.dt1")
    state_2 = hass.states.get("input_datetime.dt2")
    state_3 = hass.states.get("input_datetime.dt3")

    dt_obj = datetime.datetime(2019, 1, 1, 0, 0)
    assert state_1 is not None
    assert state_2 is None
    assert state_3 is not None
    assert dt_obj.strftime(FMT_DATE) == state_1.state
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       "dt1") == f"{DOMAIN}.dt1"
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt2") is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       "dt3") == f"{DOMAIN}.dt3"

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: {
                    "dt1": {
                        "has_time": True,
                        "has_date": False,
                        "initial": "23:32"
                    },
                    "dt2": {
                        "has_time": True,
                        "has_date": True
                    },
                }
            },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                DOMAIN,
                SERVICE_RELOAD,
                blocking=True,
                context=Context(user_id=hass_read_only_user.id),
            )
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_datetime.dt1")
    state_2 = hass.states.get("input_datetime.dt2")
    state_3 = hass.states.get("input_datetime.dt3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is None
    assert state_1.state == DEFAULT_TIME.strftime(FMT_TIME)
    assert state_2.state == datetime.datetime.combine(
        datetime.date.today(), DEFAULT_TIME).strftime(FMT_DATETIME)

    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       "dt1") == f"{DOMAIN}.dt1"
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN,
                                       "dt2") == f"{DOMAIN}.dt2"
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "dt3") is None
Ejemplo n.º 16
0
async def test_confirmable_notification(hass: HomeAssistant) -> None:
    """Test confirmable notification blueprint."""
    with patch_blueprint(
            "confirmable_notification.yaml",
            BUILTIN_BLUEPRINT_FOLDER / "confirmable_notification.yaml",
    ):
        assert await async_setup_component(
            hass,
            script.DOMAIN,
            {
                "script": {
                    "confirm": {
                        "use_blueprint": {
                            "path": "confirmable_notification.yaml",
                            "input": {
                                "notify_device":
                                "frodo",
                                "title":
                                "Lord of the things",
                                "message":
                                "Throw ring in mountain?",
                                "confirm_action": [{
                                    "service": "homeassistant.turn_on",
                                    "target": {
                                        "entity_id": "mount.doom"
                                    },
                                }],
                            },
                        }
                    }
                }
            },
        )

    turn_on_calls = async_mock_service(hass, "homeassistant", "turn_on")
    context = Context()

    with patch(
            "homeassistant.components.mobile_app.device_action.async_call_action_from_config"
    ) as mock_call_action:

        # Trigger script
        await hass.services.async_call(script.DOMAIN,
                                       "confirm",
                                       context=context)

        # Give script the time to attach the trigger.
        await asyncio.sleep(0.1)

    hass.bus.async_fire("mobile_app_notification_action",
                        {"action": "ANYTHING_ELSE"})
    hass.bus.async_fire("mobile_app_notification_action",
                        {"action": "CONFIRM_" + Context().id})
    hass.bus.async_fire("mobile_app_notification_action",
                        {"action": "CONFIRM_" + context.id})
    await hass.async_block_till_done()

    assert len(mock_call_action.mock_calls) == 1
    _hass, config, variables, _context = mock_call_action.mock_calls[0][1]

    template.attach(hass, config)
    rendered_config = template.render_complex(config, variables)

    assert rendered_config == {
        "title": "Lord of the things",
        "message": "Throw ring in mountain?",
        "alias": "Send notification",
        "domain": "mobile_app",
        "type": "notify",
        "device_id": "frodo",
        "data": {
            "actions": [
                {
                    "action": "CONFIRM_" + _context.id,
                    "title": "Confirm"
                },
                {
                    "action": "DISMISS_" + _context.id,
                    "title": "Dismiss"
                },
            ]
        },
    }

    assert len(turn_on_calls) == 1
    assert turn_on_calls[0].data == {
        "entity_id": ["mount.doom"],
    }
Ejemplo n.º 17
0
    async def async_trigger(self, run_variables, context=None, skip_condition=False):
        """Trigger automation.

        This method is a coroutine.
        """
        reason = ""
        if "trigger" in run_variables and "description" in run_variables["trigger"]:
            reason = f' by {run_variables["trigger"]["description"]}'
        self._logger.debug("Automation triggered%s", reason)

        # Create a new context referring to the old context.
        parent_id = None if context is None else context.id
        trigger_context = Context(parent_id=parent_id)

        with trace_automation(
            self.hass,
            self.unique_id,
            self._raw_config,
            self._blueprint_inputs,
            trigger_context,
            self._trace_config,
        ) as automation_trace:
            if self._variables:
                try:
                    variables = self._variables.async_render(self.hass, run_variables)
                except template.TemplateError as err:
                    self._logger.error("Error rendering variables: %s", err)
                    automation_trace.set_error(err)
                    return
            else:
                variables = run_variables
            # Prepare tracing the automation
            automation_trace.set_trace(trace_get())

            # Set trigger reason
            trigger_description = variables.get("trigger", {}).get("description")
            automation_trace.set_trigger_description(trigger_description)

            # Add initial variables as the trigger step
            if "trigger" in variables and "idx" in variables["trigger"]:
                trigger_path = f"trigger/{variables['trigger']['idx']}"
            else:
                trigger_path = "trigger"
            trace_element = TraceElement(variables, trigger_path)
            trace_append_element(trace_element)

            if (
                not skip_condition
                and self._cond_func is not None
                and not self._cond_func(variables)
            ):
                self._logger.debug(
                    "Conditions not met, aborting automation. Condition summary: %s",
                    trace_get(clear=False),
                )
                script_execution_set("failed_conditions")
                return

            self.async_set_context(trigger_context)
            event_data = {
                ATTR_NAME: self._name,
                ATTR_ENTITY_ID: self.entity_id,
            }
            if "trigger" in variables and "description" in variables["trigger"]:
                event_data[ATTR_SOURCE] = variables["trigger"]["description"]

            @callback
            def started_action():
                self.hass.bus.async_fire(
                    EVENT_AUTOMATION_TRIGGERED, event_data, context=trigger_context
                )

            try:
                with trace_path("action"):
                    await self.action_script.async_run(
                        variables, trigger_context, started_action
                    )
            except (vol.Invalid, HomeAssistantError) as err:
                self._logger.error(
                    "Error while executing automation %s: %s",
                    self.entity_id,
                    err,
                )
                automation_trace.set_error(err)
            except Exception as err:  # pylint: disable=broad-except
                self._logger.exception("While executing automation %s", self.entity_id)
                automation_trace.set_error(err)
Ejemplo n.º 18
0
async def async_setup_entry(hass, config_entry):
    """Load a config entry."""
    
    hass.data.setdefault(DOMAIN, {})
    hass.data[DOMAIN].setdefault(DATA_HAVCS_HANDLER, {})
    hass.data[DOMAIN].setdefault(DATA_HAVCS_CONFIG, {})
    hass.data[DOMAIN].setdefault(DATA_HAVCS_ITEMS, {})
    hass.data[DOMAIN].setdefault(DATA_HAVCS_SETTINGS, {})
    conf = hass.data[DOMAIN].get(DATA_HAVCS_CONFIG)

    # Config entry was created because user had configuration.yaml entry
    # They removed that, so remove entry.
    if config_entry.source == config_entries.SOURCE_IMPORT:
        if not conf:
            hass.async_create_task(
                hass.config_entries.async_remove(config_entry.entry_id))
            _LOGGER.info("[init] there is no config in yaml and havcs is managered by yaml, remove config entry. ")
            return False

    elif config_entry.source == SOURCE_PLATFORM:
        if not conf:
            if [entry for entry in hass.config_entries.async_entries(DOMAIN) if entry.source == config_entries.SOURCE_USER]:
                return True
            else:
                hass.async_create_task(
                    hass.config_entries.async_remove(config_entry.entry_id))
                return False
        else:
                return True

    # If user didn't have configuration.yaml config, generate defaults
    elif config_entry.source == config_entries.SOURCE_USER:
        if not conf:
            conf = CONFIG_SCHEMA({DOMAIN: dict(config_entry.data)})[DOMAIN]
        elif any(key in conf for key in config_entry.data):
            _LOGGER.warning(
                "[init] Data in your config entry is going to override your "
                "configuration.yaml: %s", config_entry.data)
            for key in config_entry.data:
                if key in conf:
                    if isinstance(conf[key], dict):
                        conf[key].update(config_entry.data[key])
                    else:
                        conf[key] = config_entry.data[key]
                else:
                    conf[key] = config_entry.data[key]
            if CONF_HTTP not in config_entry.data and CONF_HTTP in conf:
                conf.pop(CONF_HTTP)
            if CONF_HTTP_PROXY not in config_entry.data and CONF_HTTP_PROXY in conf:
                conf.pop(CONF_HTTP_PROXY)
            if CONF_SKILL not in config_entry.data and CONF_SKILL in conf:
                conf.pop(CONF_SKILL)
            conf = CONFIG_SCHEMA({DOMAIN: conf})[DOMAIN]

    http_manager = hass.data[DOMAIN][DATA_HAVCS_HTTP_MANAGER] = HavcsHttpManager(hass, conf.get(CONF_HTTP, {}).get(CONF_HA_URL, get_url(hass)), DEVICE_CONFIG_SCHEMA, SETTINGS_CONFIG_SCHEMA)
    if CONF_HTTP in conf:
        if conf.get(CONF_HTTP) is None:
            conf[CONF_HTTP] = HTTP_SCHEMA({})
        http_manager.set_expiration(timedelta(hours=conf.get(CONF_HTTP).get(CONF_EXPIRE_IN_HOURS, DEFAULT_EXPIRE_IN_HOURS)))
        http_manager.register_auth_authorize()
        http_manager.register_auth_token()
        http_manager.register_service()
        _LOGGER.info("[init] havcs enable \"http mode\"")

        MODE.append('http')
    if CONF_HTTP_PROXY in conf:
        if conf.get(CONF_HTTP_PROXY) is None:
            conf[CONF_HTTP_PROXY] = HTTP_PROXY({})
        _LOGGER.info("[init] havcs enable \"http_proxy mode\"")
        if CONF_SETTING not in conf:
            _LOGGER.error("[init] fail to start havcs: http_proxy mode require mqtt congfiguration")
            return False
        MODE.append('http_proxy')
    if CONF_SKILL in conf:
        if conf.get(CONF_SKILL) is None:
            conf[CONF_SKILL] = SKILL_SCHEMA({})
        _LOGGER.info("[init] havcs enable \"skill mode\"")
        if CONF_SETTING not in conf:
            _LOGGER.error("[init] fail to start havcs: skill mode require mqtt congfiguration")
            return False
        MODE.append('skill')
    
    havcs_util.ENTITY_KEY = conf.get(CONF_SETTING, {}).get(CONF_ENTITY_KEY)
    havcs_util.CONTEXT_HAVCS = Context(conf.get(CONF_SETTING, {}).get(CONF_USER_ID))

    platforms = conf.get(CONF_PLATFORM)

    device_config = conf.get(CONF_DEVICE_CONFIG)
    if device_config == 'text':
        havc_device_config_path = os.path.join(hass.config.config_dir, 'havcs.yaml')
        if not os.path.isfile(havc_device_config_path):
            with open(havc_device_config_path, "wt") as havc_device_config_file:
                havc_device_config_file.write('')
        hass.components.frontend.async_remove_panel(DOMAIN)
    else:
        havc_device_config_path = os.path.join(hass.config.config_dir, 'havcs-ui.yaml')
        if not os.path.isfile(havc_device_config_path):
            if os.path.isfile(os.path.join(hass.config.config_dir, 'havcs.yaml')):
                shutil.copyfile(os.path.join(hass.config.config_dir, 'havcs.yaml'), havc_device_config_path)
            else:
                with open(havc_device_config_path, "wt") as havc_device_config_file:
                    havc_device_config_file.write('')
        http_manager.register_deivce_manager()
    hass.data[DOMAIN][CONF_DEVICE_CONFIG_PATH] = havc_device_config_path

    havc_settings_config_path = os.path.join(hass.config.config_dir, 'havcs-settings.yaml')
    if not os.path.isfile(havc_settings_config_path):
        with open(havc_settings_config_path, "wt") as havc_settings_config_file:
            havc_settings_config_file.write('')
    http_manager.register_settings_manager()
    hass.data[DOMAIN][CONF_SETTINGS_CONFIG_PATH] = havc_settings_config_path

    sync_device = conf.get(CONF_SKILL, {}).get(CONF_SYNC_DEVICE)
    bind_device = conf.get(CONF_SKILL, {}).get(CONF_BIND_DEVICE)

    if CONF_HTTP_PROXY not in conf and CONF_SKILL not in conf:
        _LOGGER.debug("[init] havcs only run in http mode, skip mqtt initialization")
        ha_url = conf.get(CONF_HTTP, {}).get(CONF_HA_URL, get_url(hass))
        _LOGGER.debug("[init] ha_url = %s, base_url = %s", ha_url, get_url(hass))
    else:
        setting_conf = conf.get(CONF_SETTING)
        app_key = setting_conf.get(CONF_APP_KEY)
        app_secret = setting_conf.get(CONF_APP_SECRET)
        decrypt_key =bytes().fromhex(sha1(app_secret.encode("utf-8")).hexdigest())[0:16]

        if platforms:
            bind_manager = hass.data[DOMAIN][DATA_HAVCS_BIND_MANAGER] = HavcsBindManager(hass, platforms, bind_device, sync_device, app_key, decrypt_key)
            await bind_manager.async_init()

        allowed_uri = conf.get(CONF_HTTP_PROXY, {}).get(CONF_ALLOWED_URI)
        ha_url = conf.get(CONF_HTTP_PROXY, {}).get(CONF_HA_URL, get_url(hass))

        # 组装mqtt配置
        mqtt_conf = {}
        for (k,v) in  setting_conf.items():
            if(k == CONF_APP_KEY):
                mqtt_conf.setdefault('username', v)
            elif(k == CONF_APP_SECRET):
                mqtt_conf.setdefault('password', v)
            elif(k == CONF_ENTITY_KEY):
                continue
            else:
                mqtt_conf.setdefault(k, v)
        certificate = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ca.crt')
        if os.path.exists(certificate):
            mqtt_conf[CONF_CERTIFICATE] = certificate
            _LOGGER.debug("[init] sucess to autoload ca.crt from %s", certificate)
        
        validate_mqtt_conf = mqtt.CONFIG_SCHEMA({'mqtt': mqtt_conf})['mqtt']
        hass.data[DOMAIN][DATA_HAVCS_MQTT] = mqtt.MQTT(
            hass,
            config_entry,
            mqtt_conf
        )
        _LOGGER.debug("[init] connecting to mqtt server")
        
        await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_connect()  # 没有返回值

        retry = 5
        _LOGGER.debug("[init] wait for mqtt client connection status")
        while retry > 0 :
            await asyncio.sleep(5)
            if hass.data[DOMAIN][DATA_HAVCS_MQTT].connected:
                _LOGGER.debug("[init] mqtt client connected")
                break
            retry -= 1
            _LOGGER.debug("[init] mqtt client not connected yet, check after 5s")
  
        if hass.data[DOMAIN][DATA_HAVCS_MQTT].connected:
            pass
        else:
            import hashlib
            md5_l = hashlib.md5()
            with open(certificate,mode="rb") as f:
                by = f.read()
            md5_l.update(by)
            local_ca_md5 = md5_l.hexdigest()
            _LOGGER.debug("[init] local ca.crt md5 %s", local_ca_md5)
            
            try:
                ca_url = 'https://raw.githubusercontent.com/cnk700i/havcs/master/custom_components/havcs/ca.crt'
                session = async_get_clientsession(hass, verify_ssl=False)
                with async_timeout.timeout(5, loop=hass.loop):
                    response = await session.get(ca_url)
                ca_bytes = await response.read()
                md5_l = hashlib.md5()
                md5_l.update(ca_bytes)
                latest_ca_md5 = md5_l.hexdigest()
                _LOGGER.debug("[init] remote ca.crt md5 %s", latest_ca_md5)
                if local_ca_md5 != latest_ca_md5:
                    _LOGGER.error("[init] can not connect to mqtt server(host = %s, port = %s), try update ca.crt file ",setting_conf[CONF_BROKER], setting_conf[CONF_PORT])
                else:
                    _LOGGER.error("[init] can not connect to mqtt server(host = %s, port = %s), check mqtt server's address and port ", setting_conf[CONF_BROKER], setting_conf[CONF_PORT])
            except Exception as e:
                _LOGGER.error("[init] fail to check whether ca.crt is latest, cause by %s", repr(e))
            _LOGGER.error("[init] fail to init havcs")
            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_disconnect()
            return False

        async def async_stop_mqtt(event: Event):
            """Stop MQTT component."""
            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_disconnect()

        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_mqtt)

        async def async_http_proxy_handler(mqtt_msg, topic, start_time = None):
            response = None
            url = ha_url + mqtt_msg['uri']
            _LOGGER.debug("[http_proxy] request: url = %s", url)
            if('content' in mqtt_msg):
                _LOGGER.debug("[http_proxy] use POST method")
                platform = mqtt_msg.get('platform', havcs_util.get_platform_from_command(mqtt_msg['content']))
                auth_type, auth_value = mqtt_msg.get('headers', {}).get('Authorization',' ').split(' ', 1)
                _LOGGER.debug("[http_proxy] platform = %s, auth_type = %s, access_token = %s", platform, auth_type, auth_value)

                try:
                    session = async_get_clientsession(hass, verify_ssl=False)
                    with async_timeout.timeout(5, loop=hass.loop):
                        response = await session.post(url, data=mqtt_msg['content'], headers = mqtt_msg.get('headers'))
                except(asyncio.TimeoutError, aiohttp.ClientError):
                    _LOGGER.error("[http_proxy] fail to access %s in local network: timeout", url)
                except:
                    _LOGGER.error("[http_proxy] fail to access %s in local network: %s", url, traceback.format_exc())
            else:
                _LOGGER.debug("[http_proxy] use GET method")
                try:
                    session = async_get_clientsession(hass, verify_ssl=False)
                    with async_timeout.timeout(5, loop=hass.loop):
                        response = await session.get(url, headers = mqtt_msg.get('headers'))
                except(asyncio.TimeoutError, aiohttp.ClientError):
                    _LOGGER.error("[http_proxy] fail to access %s in local network: timeout", url)
                except:
                    _LOGGER.error("[http_proxy] fail to access %s in local network: %s", url, traceback.format_exc())
                # _LOGGER.debug("[http_proxy] %s", response.history) #查看重定向信息
            if response is not None:
                if response.status != 200:
                    _LOGGER.error("[http_proxy] fail to access %s in local network: status=%d",url,response.status)
                if('image' in response.headers['Content-Type'] or 'stream' in response.headers['Content-Type']):
                    result = await response.read()
                    result = b64encode(result).decode()
                else:
                    result = await response.text()
                headers = {
                    'Content-Type': response.headers['Content-Type'] + ';charset=utf-8'
                }
                res = {
                    'headers': headers,
                    'status': response.status,
                    'content': result.encode('utf-8').decode('unicode_escape'),
                    'msgId': mqtt_msg.get('msgId')
                }
                _LOGGER.debug("[http_proxy] response: uri = %s, msgid = %s, type = %s", mqtt_msg['uri'].split('?')[0], mqtt_msg.get('msgId'), response.headers['Content-Type'])
            else:
                res = {
                    'status': 500,
                    'content': '{"error":"time_out"}',
                    'msgId': mqtt_msg.get('msgId')
                }
                _LOGGER.debug("[http_proxy] response: uri = %s, msgid = %s", mqtt_msg['uri'].split('?')[0], mqtt_msg.get('msgId'))
            res = havcs_util.AESCipher(decrypt_key).encrypt(json.dumps(res, ensure_ascii = False).encode('utf8'))

            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_publish(topic.replace('/request/','/response/'), res, 2, False)
            end_time = datetime.now()
            _LOGGER.debug("[mqtt] -------- mqtt task finish at %s, Running time: %ss --------", end_time.strftime('%Y-%m-%d %H:%M:%S'), (end_time - start_time).total_seconds())

        async def async_module_handler(mqtt_msg, topic, start_time = None):
            platform = mqtt_msg.get('platform', havcs_util.get_platform_from_command(mqtt_msg['content']))
            if platform == 'unknown':
                _LOGGER.error("[skill] receive command from unsupport platform \"%s\"", platform)
                return
            if platform not in hass.data[DOMAIN][DATA_HAVCS_HANDLER]:
                _LOGGER.error("[skill] receive command from uninitialized platform \"%s\" , check up your configuration.yaml", platform)
                return
            try:
                response = await hass.data[DOMAIN][DATA_HAVCS_HANDLER][platform].handleRequest(json.loads(mqtt_msg['content']), auth = True, request_from = "mqtt")
            except:
                response = '{"error":"service error"}'
                _LOGGER.error("[skill] %s", traceback.format_exc())
            res = {
                    'headers': {'Content-Type': 'application/json;charset=utf-8'},
                    'status': 200,
                    'content': json.dumps(response).encode('utf-8').decode('unicode_escape'),
                    'msgId': mqtt_msg.get('msgId')
                }
            res = havcs_util.AESCipher(decrypt_key).encrypt(json.dumps(res, ensure_ascii = False).encode('utf8'))

            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_publish(topic.replace('/request/','/response/'), res, 2, False)
            end_time = datetime.now()
            _LOGGER.debug("[mqtt] -------- mqtt task finish at %s, Running time: %ss --------", end_time.strftime('%Y-%m-%d %H:%M:%S'), (end_time - start_time).total_seconds())
            
        async def async_publish_error(mqtt_msg,topic):
            res = {
                    'headers': {'Content-Type': 'application/json;charset=utf-8'},
                    'status': 404,
                    'content': '',
                    'msgId': mqtt_msg.get('msgId')
                }                    
            res = havcs_util.AESCipher(decrypt_key).encrypt(json.dumps(res, ensure_ascii = False).encode('utf8'))
            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_publish(topic.replace('/request/','/response/'), res, 2, False)

        @callback
        def message_received(*args): # 0.90 传参变化
            if isinstance(args[0], str):
                topic = args[0]
                payload = args[1]
                # qos = args[2]
            else:
                topic = args[0].topic
                payload = args[0].payload
                # qos = args[0].qos
            """Handle new MQTT state messages."""
            # _LOGGER.debug("[mqtt] get encrypt message: \n {}".format(payload))
            try:
                start_time = datetime.now()
                end_time = None
                _LOGGER.debug("[mqtt] -------- start handle task from mqtt at %s --------", start_time.strftime('%Y-%m-%d %H:%M:%S'))

                payload = havcs_util.AESCipher(decrypt_key).decrypt(payload)
                # _LOGGER.debug("[mqtt] get raw message: \n {}".format(payload))
                req = json.loads(payload)
                if req.get('msgType') == 'hello':
                    _LOGGER.info("[mqtt] get hello message: %s", req.get('content'))
                    end_time = datetime.now() 
                    _LOGGER.debug("[mqtt] -------- mqtt task finish at %s, Running time: %ss --------", end_time.strftime('%Y-%m-%d %H:%M:%S'), (end_time - start_time).total_seconds())
                    return
                
                _LOGGER.debug("[mqtt] raw message: %s", req)
                if req.get('platform') == 'h2m2h':
                    if('http_proxy' not in MODE):
                        _LOGGER.info("[http_proxy] havcs not run in http_proxy mode, ignore request: %s", req)
                        raise RuntimeError
                    if(allowed_uri and req.get('uri','/').split('?')[0] not in allowed_uri):
                        _LOGGER.info("[http_proxy] uri not allowed: %s", req.get('uri','/'))
                        hass.add_job(async_publish_error(req, topic))
                        raise RuntimeError
                    hass.add_job(async_http_proxy_handler(req, topic, start_time))
                else:
                    if('skill' not in MODE):
                        _LOGGER.info("[skill] havcs not run in skill mode, ignore request: %s", req)
                        raise RuntimeError 
                    hass.add_job(async_module_handler(req, topic, start_time))

            except (json.decoder.JSONDecodeError, UnicodeDecodeError, binascii.Error):
                import sys
                ex_type, ex_val, ex_stack = sys.exc_info()
                log = ''
                for stack in traceback.extract_tb(ex_stack):
                    log += str(stack)
                _LOGGER.debug("[mqtt] fail to decrypt message, abandon[%s][%s]: %s", ex_type, ex_val, log)
                end_time = datetime.now()
            except:
                _LOGGER.error("[mqtt] fail to handle %s", traceback.format_exc())
                end_time = datetime.now()
            if end_time:    
                _LOGGER.debug("[mqtt] -------- mqtt task finish at %s, Running time: %ss --------", end_time.strftime('%Y-%m-%d %H:%M:%S'), (end_time - start_time).total_seconds())

        await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_subscribe("ai-home/http2mqtt2hass/"+app_key+"/request/#", message_received, 2, 'utf-8')
        _LOGGER.info("[init] mqtt initialization finished, waiting for welcome message of mqtt server.")

    async def start_havcs(event: Event):
        async def async_load_settings():
            _LOGGER.info("loading settings from file")
            try:
                settings_config = await hass.async_add_executor_job(
                    conf_util.load_yaml_config_file, havc_settings_config_path
                )
                hass.data[DOMAIN][DATA_HAVCS_SETTINGS] = SETTINGS_CONFIG_SCHEMA(settings_config)
                
            except HomeAssistantError as err:
                _LOGGER.error("Error loading %s: %s", havc_settings_config_path, err)
                return None
            except vol.error.Error as exception:
                _LOGGER.warning("failed to load all settings from file, find invalid data: %s", exception)
            except:
                _LOGGER.error("Error loading %s: %s", havc_settings_config_path, traceback.format_exc())
                return None
        await async_load_settings()

        async def async_load_device_info():
            _LOGGER.info("loading device info from file")
            try:
                device_config = await hass.async_add_executor_job(
                    conf_util.load_yaml_config_file, havc_device_config_path
                )
                hass.data[DOMAIN][DATA_HAVCS_ITEMS] = DEVICE_CONFIG_SCHEMA(device_config)
            except HomeAssistantError as err:
                _LOGGER.error("Error loading %s: %s", havc_device_config_path, err)
                return None
            except vol.error.Error as exception:
                _LOGGER.warning("failed to load all devices from file, find invalid data: %s", exception)
            except:
                _LOGGER.error("Error loading %s: %s", havc_device_config_path, traceback.format_exc())
                return None

        async def async_init_sub_entry():
            # create when config change
            mode = []
            if CONF_HTTP in conf:
                mode.append(CONF_HTTP)
                if CONF_HTTP_PROXY in conf:
                    mode.append(CONF_HTTP_PROXY)
            if CONF_SKILL in conf:
                mode.append(CONF_SKILL)

            havcs_entries = hass.config_entries.async_entries(DOMAIN)
            # sub entry for every platform
            entry_platforms = set([entry.data.get('platform') for entry in havcs_entries if entry.source == SOURCE_PLATFORM])
            conf_platforms = set(conf.get(CONF_PLATFORM))
            new_platforms = conf_platforms - entry_platforms
            _LOGGER.debug("[post-task] load new platform entry %s", new_platforms)
            for platform in new_platforms:
                # 如果在async_setup_entry中执行无法await,async_init所触发的component_setup会不断等待之前的component_setup任务
                await hass.async_create_task(hass.config_entries.flow.async_init(
                    DOMAIN, context={'source': SOURCE_PLATFORM},
                    data={'platform': platform, 'mode': mode}
                ))
            remove_platforms = entry_platforms - conf_platforms
            _LOGGER.debug("[post-task] remove old platform entry %s", remove_platforms)
            for entry in [entry for entry in havcs_entries if entry.source == SOURCE_PLATFORM]:
                if entry.data.get('platform') in remove_platforms:
                    await hass.async_create_task(hass.config_entries.async_remove(entry.entry_id))
                else:
                    entry.title=f"接入平台[{entry.data.get('platform')}-{DEVICE_PLATFORM_DICT[entry.data.get('platform')]['cn_name']}],接入方式{mode}"
                    hass.config_entries.async_update_entry(entry)

            # await async_load_device_info()

            for platform in platforms:
                for ent in hass.config_entries.async_entries(DOMAIN):
                    if ent.source == SOURCE_PLATFORM and ent.data.get('platform') == platform:
                        try:
                            module = importlib.import_module('custom_components.{}.{}'.format(DOMAIN,platform))
                            _LOGGER.info("[post-task] import %s.%s", DOMAIN, platform)
                            hass.data[DOMAIN][DATA_HAVCS_HANDLER][platform] = await module.createHandler(hass, ent)
                            # hass.data[DOMAIN][DATA_HAVCS_HANDLER][platform].vcdm.all(hass, True)
                        except ImportError as err:
                            _LOGGER.error("[post-task] Unable to import %s.%s, %s", DOMAIN, platform, err)
                            return False
                        except:
                            _LOGGER.error("[post-task] fail to create %s handler: %s", platform , traceback.format_exc())
                            return False
                        break
        await async_init_sub_entry()

        if DATA_HAVCS_MQTT in hass.data[DOMAIN]:
            await hass.data[DOMAIN][DATA_HAVCS_MQTT].async_publish("ai-home/http2mqtt2hass/"+app_key+"/response/test", 'init', 2, False)

        async def async_handler_service(service):
    
            if service.service == SERVICE_RELOAD:
                await async_load_device_info()
                for platform in hass.data[DOMAIN][DATA_HAVCS_HANDLER]:
                    devices = hass.data[DOMAIN][DATA_HAVCS_HANDLER][platform].vcdm.all(hass, True)
                    await hass.data[DOMAIN][DATA_HAVCS_HANDLER][platform].vcdm.async_reregister_devices(hass)
                    _LOGGER.info("[service] ------------%s 平台加载设备信息------------\n%s", platform, [device.attributes for device in devices])
                    mind_devices = [device.attributes for device in devices if None in device.attributes.values() or [] in device.attributes.values()]
                    if mind_devices:
                        _LOGGER.debug("!!!!!!!! 以下设备信息不完整,检查值为None或[]的属性并进行设置 !!!!!!!!")
                        for mind_device in mind_devices:
                            _LOGGER.debug("%s", mind_device)
                    _LOGGER.info("[service] ------------%s 平台加载设备信息------------\n", platform)
                if bind_device:
                    await hass.data[DOMAIN][DATA_HAVCS_BIND_MANAGER].async_bind_device()

            elif service.service == SERVICE_DEBUG_DISCOVERY:
                for platform, handler in hass.data[DOMAIN][DATA_HAVCS_HANDLER].items():
                    err_result, discovery_devices, entity_ids = handler.process_discovery_command("service_call")
                    _LOGGER.info("[service][%s] trigger discovery command, response: %s", platform, discovery_devices)
            else:
                pass
        hass.services.async_register(DOMAIN, SERVICE_RELOAD, async_handler_service, schema=HAVCS_SERVICE_SCHEMA)
        hass.services.async_register(DOMAIN, SERVICE_DEBUG_DISCOVERY, async_handler_service, schema=HAVCS_SERVICE_SCHEMA)

        await hass.services.async_call(DOMAIN, SERVICE_RELOAD)

        if CONF_HTTP in conf or CONF_HTTP_PROXY in conf:
            hass.async_create_task(http_manager.async_check_http_oauth())

    if config_entry.source == config_entries.SOURCE_IMPORT:
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_havcs)
    elif config_entry.source == config_entries.SOURCE_USER:
        hass.async_create_task(start_havcs(None))

    _LOGGER.info("[init] havcs initialization finished.")
    return True
Ejemplo n.º 19
0
async def test_reload(hass, hass_admin_user, hass_read_only_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = await entity_registry.async_get_registry(hass)

    assert await async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: {
                "test_1": {
                    "options":
                    ["first option", "middle option", "last option"],
                    "initial": "middle option",
                },
                "test_2": {
                    "options": ["an option", "not an option"],
                    "initial": "an option",
                },
            }
        },
    )

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_select.test_1")
    state_2 = hass.states.get("input_select.test_2")
    state_3 = hass.states.get("input_select.test_3")

    assert state_1 is not None
    assert state_2 is not None
    assert state_3 is None
    assert "middle option" == state_1.state
    assert "an option" == state_2.state
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is None

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: {
                    "test_2": {
                        "options": ["an option", "reloaded option"],
                        "initial": "reloaded option",
                    },
                    "test_3": {
                        "options": ["new option", "newer option"],
                        "initial": "newer option",
                    },
                }
            },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                DOMAIN,
                SERVICE_RELOAD,
                blocking=True,
                context=Context(user_id=hass_read_only_user.id),
            )
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )
        await hass.async_block_till_done()

    assert count_start + 2 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("input_select.test_1")
    state_2 = hass.states.get("input_select.test_2")
    state_3 = hass.states.get("input_select.test_3")

    assert state_1 is None
    assert state_2 is not None
    assert state_3 is not None
    assert "an option" == state_2.state
    assert "newer option" == state_3.state
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_1") is None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_2") is not None
    assert ent_reg.async_get_entity_id(DOMAIN, DOMAIN, "test_3") is not None
Ejemplo n.º 20
0
def registration_context(registration: dict) -> Context:
    """Generate a context from a request."""
    return Context(user_id=registration[CONF_USER_ID])
Ejemplo n.º 21
0
async def test_configure(hass, hass_admin_user):
    """Test that setting values through configure works."""
    assert await async_setup_component(
        hass, "counter",
        {"counter": {
            "test": {
                "maximum": "10",
                "initial": "10"
            }
        }})

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "10"
    assert 10 == state.attributes.get("maximum")

    # update max
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "maximum": 0
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "0"
    assert 0 == state.attributes.get("maximum")

    # disable max
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "maximum": None
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "0"
    assert state.attributes.get("maximum") is None

    # update min
    assert state.attributes.get("minimum") is None
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "minimum": 5
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "5"
    assert 5 == state.attributes.get("minimum")

    # disable min
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "minimum": None
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "5"
    assert state.attributes.get("minimum") is None

    # update step
    assert 1 == state.attributes.get("step")
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "step": 3
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "5"
    assert 3 == state.attributes.get("step")

    # update value
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "value": 6
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "6"

    # update initial
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "initial": 5
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "6"
    assert 5 == state.attributes.get("initial")

    # update all
    await hass.services.async_call(
        "counter",
        "configure",
        {
            "entity_id": state.entity_id,
            "step": 5,
            "minimum": 0,
            "maximum": 9,
            "value": 5,
            "initial": 6,
        },
        True,
        Context(user_id=hass_admin_user.id),
    )

    state = hass.states.get("counter.test")
    assert state is not None
    assert state.state == "5"
    assert 5 == state.attributes.get("step")
    assert 0 == state.attributes.get("minimum")
    assert 9 == state.attributes.get("maximum")
    assert 6 == state.attributes.get("initial")
Ejemplo n.º 22
0
async def test_if_fires_on_zone_enter(hass, calls):
    """Test for firing on zone enter."""
    context = Context()
    hass.states.async_set("test.entity", "hello", {
        "latitude": 32.881011,
        "longitude": -117.234758
    })
    await hass.async_block_till_done()

    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: {
                "trigger": {
                    "platform": "zone",
                    "entity_id": "test.entity",
                    "zone": "zone.test",
                    "event": "enter",
                },
                "action": {
                    "service": "test.automation",
                    "data_template": {
                        "some":
                        "{{ trigger.%s }}" % "}} - {{ trigger.".join((
                            "platform",
                            "entity_id",
                            "from_state.state",
                            "to_state.state",
                            "zone.name",
                        ))
                    },
                },
            }
        },
    )

    hass.states.async_set(
        "test.entity",
        "hello",
        {
            "latitude": 32.880586,
            "longitude": -117.237564
        },
        context=context,
    )
    await hass.async_block_till_done()

    assert len(calls) == 1
    assert calls[0].context.parent_id == context.id
    assert "zone - test.entity - hello - hello - test" == calls[0].data["some"]

    # Set out of zone again so we can trigger call
    hass.states.async_set("test.entity", "hello", {
        "latitude": 32.881011,
        "longitude": -117.234758
    })
    await hass.async_block_till_done()

    await common.async_turn_off(hass)
    await hass.async_block_till_done()

    hass.states.async_set("test.entity", "hello", {
        "latitude": 32.880586,
        "longitude": -117.237564
    })
    await hass.async_block_till_done()

    assert len(calls) == 1
Ejemplo n.º 23
0
async def test_if_fires_on_zone_enter(hass, calls):
    """Test for firing on zone enter."""
    context = Context()
    hass.states.async_set('geo_location.entity', 'hello', {
        'latitude': 32.881011,
        'longitude': -117.234758,
        'source': 'test_source'
    })
    await hass.async_block_till_done()

    assert await async_setup_component(
        hass, automation.DOMAIN, {
            automation.DOMAIN: {
                'trigger': {
                    'platform': 'geo_location',
                    'source': 'test_source',
                    'zone': 'zone.test',
                    'event': 'enter',
                },
                'action': {
                    'service': 'test.automation',
                    'data_template': {
                        'some':
                        '{{ trigger.%s }}' % '}} - {{ trigger.'.join(
                            ('platform', 'entity_id', 'from_state.state',
                             'to_state.state', 'zone.name'))
                    },
                }
            }
        })

    hass.states.async_set('geo_location.entity',
                          'hello', {
                              'latitude': 32.880586,
                              'longitude': -117.237564
                          },
                          context=context)
    await hass.async_block_till_done()

    assert 1 == len(calls)
    assert calls[0].context is context
    assert 'geo_location - geo_location.entity - hello - hello - test' == \
        calls[0].data['some']

    # Set out of zone again so we can trigger call
    hass.states.async_set('geo_location.entity', 'hello', {
        'latitude': 32.881011,
        'longitude': -117.234758
    })
    await hass.async_block_till_done()

    await common.async_turn_off(hass)
    await hass.async_block_till_done()

    hass.states.async_set('geo_location.entity', 'hello', {
        'latitude': 32.880586,
        'longitude': -117.237564
    })
    await hass.async_block_till_done()

    assert 1 == len(calls)
Ejemplo n.º 24
0
 def context(self) -> Context:  # type: ignore[override]
     """State context."""
     if self._context is None:
         self._context = Context(id=None)  # type: ignore[arg-type]
     return self._context
Ejemplo n.º 25
0
async def test_shared_context(hass, calls):
    """Test that the shared context is passed down the chain."""
    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "alias": "hello",
                    "trigger": {"platform": "event", "event_type": "test_event"},
                    "action": {"event": "test_event2"},
                },
                {
                    "alias": "bye",
                    "trigger": {"platform": "event", "event_type": "test_event2"},
                    "action": {"service": "test.automation"},
                },
            ]
        },
    )

    context = Context()
    first_automation_listener = Mock()
    event_mock = Mock()

    hass.bus.async_listen("test_event2", first_automation_listener)
    hass.bus.async_listen(EVENT_AUTOMATION_TRIGGERED, event_mock)
    hass.bus.async_fire("test_event", context=context)
    await hass.async_block_till_done()

    # Ensure events was fired
    assert first_automation_listener.call_count == 1
    assert event_mock.call_count == 2

    # Verify automation triggered evenet for 'hello' automation
    args, _ = event_mock.call_args_list[0]
    first_trigger_context = args[0].context
    assert first_trigger_context.parent_id == context.id
    # Ensure event data has all attributes set
    assert args[0].data.get(ATTR_NAME) is not None
    assert args[0].data.get(ATTR_ENTITY_ID) is not None
    assert args[0].data.get(ATTR_SOURCE) is not None

    # Ensure context set correctly for event fired by 'hello' automation
    args, _ = first_automation_listener.call_args
    assert args[0].context is first_trigger_context

    # Ensure the 'hello' automation state has the right context
    state = hass.states.get("automation.hello")
    assert state is not None
    assert state.context is first_trigger_context

    # Verify automation triggered evenet for 'bye' automation
    args, _ = event_mock.call_args_list[1]
    second_trigger_context = args[0].context
    assert second_trigger_context.parent_id == first_trigger_context.id
    # Ensure event data has all attributes set
    assert args[0].data.get(ATTR_NAME) is not None
    assert args[0].data.get(ATTR_ENTITY_ID) is not None
    assert args[0].data.get(ATTR_SOURCE) is not None

    # Ensure the service call from the second automation
    # shares the same context
    assert len(calls) == 1
    assert calls[0].context is second_trigger_context
Ejemplo n.º 26
0
async def test_if_fires_on_entity_creation_and_removal(hass, calls):
    """Test for firing on entity creation and removal, with to/from constraints."""
    # set automations for multiple combinations to/from
    assert await async_setup_component(
        hass,
        automation.DOMAIN,
        {
            automation.DOMAIN: [
                {
                    "trigger": {
                        "platform": "state",
                        "entity_id": "test.entity_0"
                    },
                    "action": {
                        "service": "test.automation"
                    },
                },
                {
                    "trigger": {
                        "platform": "state",
                        "from": "hello",
                        "entity_id": "test.entity_1",
                    },
                    "action": {
                        "service": "test.automation"
                    },
                },
                {
                    "trigger": {
                        "platform": "state",
                        "to": "world",
                        "entity_id": "test.entity_2",
                    },
                    "action": {
                        "service": "test.automation"
                    },
                },
            ],
        },
    )
    await hass.async_block_till_done()

    # use contexts to identify trigger entities
    context_0 = Context()
    context_1 = Context()
    context_2 = Context()

    # automation with match_all triggers on creation
    hass.states.async_set("test.entity_0", "any", context=context_0)
    await hass.async_block_till_done()
    assert len(calls) == 1
    assert calls[0].context.parent_id == context_0.id

    # create entities, trigger on test.entity_2 ('to' matches, no 'from')
    hass.states.async_set("test.entity_1", "hello", context=context_1)
    hass.states.async_set("test.entity_2", "world", context=context_2)
    await hass.async_block_till_done()
    assert len(calls) == 2
    assert calls[1].context.parent_id == context_2.id

    # removal of both, trigger on test.entity_1 ('from' matches, no 'to')
    assert hass.states.async_remove("test.entity_1", context=context_1)
    assert hass.states.async_remove("test.entity_2", context=context_2)
    await hass.async_block_till_done()
    assert len(calls) == 3
    assert calls[2].context.parent_id == context_1.id

    # automation with match_all triggers on removal
    assert hass.states.async_remove("test.entity_0", context=context_0)
    await hass.async_block_till_done()
    assert len(calls) == 4
    assert calls[3].context.parent_id == context_0.id
Ejemplo n.º 27
0
async def test_reload(hass, hass_admin_user, hass_read_only_user):
    """Test reload service."""
    count_start = len(hass.states.async_entity_ids())
    ent_reg = await entity_registry.async_get_registry(hass)

    assert await setup.async_setup_component(
        hass,
        DOMAIN,
        {
            DOMAIN: [
                {
                    "name": "yaml 1",
                    "latitude": 1,
                    "longitude": 2
                },
                {
                    "name": "yaml 2",
                    "latitude": 3,
                    "longitude": 4
                },
            ],
        },
    )

    assert count_start + 3 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("zone.yaml_1")
    state_2 = hass.states.get("zone.yaml_2")
    state_3 = hass.states.get("zone.yaml_3")

    assert state_1 is not None
    assert state_1.attributes["latitude"] == 1
    assert state_1.attributes["longitude"] == 2
    assert state_2 is not None
    assert state_2.attributes["latitude"] == 3
    assert state_2.attributes["longitude"] == 4
    assert state_3 is None
    assert len(ent_reg.entities) == 0

    with patch(
            "homeassistant.config.load_yaml_config_file",
            autospec=True,
            return_value={
                DOMAIN: [
                    {
                        "name": "yaml 2",
                        "latitude": 3,
                        "longitude": 4
                    },
                    {
                        "name": "yaml 3",
                        "latitude": 5,
                        "longitude": 6
                    },
                ]
            },
    ):
        with pytest.raises(Unauthorized):
            await hass.services.async_call(
                DOMAIN,
                SERVICE_RELOAD,
                blocking=True,
                context=Context(user_id=hass_read_only_user.id),
            )
        await hass.services.async_call(
            DOMAIN,
            SERVICE_RELOAD,
            blocking=True,
            context=Context(user_id=hass_admin_user.id),
        )
        await hass.async_block_till_done()

    assert count_start + 3 == len(hass.states.async_entity_ids())

    state_1 = hass.states.get("zone.yaml_1")
    state_2 = hass.states.get("zone.yaml_2")
    state_3 = hass.states.get("zone.yaml_3")

    assert state_1 is None
    assert state_2 is not None
    assert state_2.attributes["latitude"] == 3
    assert state_2.attributes["longitude"] == 4
    assert state_3 is not None
    assert state_3.attributes["latitude"] == 5
    assert state_3.attributes["longitude"] == 6
Ejemplo n.º 28
0
 def context(self):
     """State context."""
     if not self._context:
         self._context = Context(id=None)
     return self._context
Ejemplo n.º 29
0
 def context(self, msg):
     """Return a context."""
     user = self.user
     if user is None:
         return Context()
     return Context(user_id=user.id)
Ejemplo n.º 30
0
async def test_service_call_params(hass):
    """Test that hass params get set properly on service calls."""
    with patch.object(hass.services, "async_call") as call, patch.object(
            Function, "service_has_service", return_value=True):
        Function.init(hass)
        await Function.service_call("test",
                                    "test",
                                    context=Context(id="test"),
                                    blocking=True,
                                    limit=1,
                                    other_service_data="test")
        assert call.called
        assert call.call_args[0] == ("test", "test", {
            "other_service_data": "test"
        })
        assert call.call_args[1] == {
            "context": Context(id="test"),
            "blocking": True,
            "limit": 1
        }
        call.reset_mock()

        await Function.service_call("test",
                                    "test",
                                    context=Context(id="test"),
                                    blocking=False,
                                    other_service_data="test")
        assert call.called
        assert call.call_args[0] == ("test", "test", {
            "other_service_data": "test"
        })
        assert call.call_args[1] == {
            "context": Context(id="test"),
            "blocking": False
        }
        call.reset_mock()

        await Function.get("test.test")(context=Context(id="test"),
                                        blocking=True,
                                        limit=1,
                                        other_service_data="test")
        assert call.called
        assert call.call_args[0] == ("test", "test", {
            "other_service_data": "test"
        })
        assert call.call_args[1] == {
            "context": Context(id="test"),
            "blocking": True,
            "limit": 1
        }
        call.reset_mock()

        await Function.get("test.test")(context=Context(id="test"),
                                        blocking=False,
                                        other_service_data="test")
        assert call.called
        assert call.call_args[0] == ("test", "test", {
            "other_service_data": "test"
        })
        assert call.call_args[1] == {
            "context": Context(id="test"),
            "blocking": False
        }

    # Stop all tasks to avoid conflicts with other tests
    await Function.waiter_stop()
    await Function.reaper_stop()