async def test_doorbell_event_thread(hass, subscriber, setup_platform): """Test a series of pubsub messages in the same thread.""" events = async_capture_events(hass, NEST_EVENT) await setup_platform() registry = er.async_get(hass) entry = registry.async_get("camera.front") assert entry is not None event_message_data = { "eventId": "some-event-id-ignored", "resourceUpdate": { "name": DEVICE_ID, "events": { "sdm.devices.events.CameraMotion.Motion": { "eventSessionId": EVENT_SESSION_ID, "eventId": "n:1", }, "sdm.devices.events.CameraClipPreview.ClipPreview": { "eventSessionId": EVENT_SESSION_ID, "previewUrl": "image-url-1", }, }, }, "eventThreadId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...", "resourcegroup": [DEVICE_ID], } # Publish message #1 that starts the event thread timestamp1 = utcnow() message_data_1 = event_message_data.copy() message_data_1.update( { "timestamp": timestamp1.isoformat(timespec="seconds"), "eventThreadState": "STARTED", } ) await subscriber.async_receive_event(EventMessage(message_data_1, auth=None)) # Publish message #2 that sends a no-op update to end the event thread timestamp2 = timestamp1 + datetime.timedelta(seconds=1) message_data_2 = event_message_data.copy() message_data_2.update( { "timestamp": timestamp2.isoformat(timespec="seconds"), "eventThreadState": "ENDED", } ) await subscriber.async_receive_event(EventMessage(message_data_2, auth=None)) await hass.async_block_till_done() # The event is only published once assert len(events) == 1 assert event_view(events[0].data) == { "device_id": entry.device_id, "type": "camera_motion", "timestamp": timestamp1.replace(microsecond=0), }
async def test_thermostat_target_temp(hass, auth): """Test a thermostat changing hvac modes and affected on target temps.""" subscriber = await setup_climate( hass, { "sdm.devices.traits.ThermostatHvac": { "status": "HEATING", }, "sdm.devices.traits.ThermostatMode": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "HEAT", }, "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 20.1, }, "sdm.devices.traits.ThermostatTemperatureSetpoint": { "heatCelsius": 23.0, }, }, auth=auth, ) assert len(hass.states.async_all()) == 1 thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_HEAT assert thermostat.attributes[ATTR_TEMPERATURE] == 23.0 assert thermostat.attributes[ATTR_TARGET_TEMP_LOW] is None assert thermostat.attributes[ATTR_TARGET_TEMP_HIGH] is None # Simulate pubsub message changing modes event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": "some-device-id", "traits": { "sdm.devices.traits.ThermostatMode": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "HEATCOOL", }, "sdm.devices.traits.ThermostatTemperatureSetpoint": { "heatCelsius": 22.0, "coolCelsius": 28.0, }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_HEAT_COOL assert thermostat.attributes[ATTR_TARGET_TEMP_LOW] == 22.0 assert thermostat.attributes[ATTR_TARGET_TEMP_HIGH] == 28.0 assert thermostat.attributes[ATTR_TEMPERATURE] is None
def make_motion_event( event_id: str = MOTION_EVENT_ID, event_session_id: str = EVENT_SESSION_ID, timestamp: datetime.datetime = None, ) -> EventMessage: """Create an EventMessage for a motion event.""" if not timestamp: timestamp = utcnow() return EventMessage( { "eventId": "some-event-id", # Ignored; we use the resource updated event id below "timestamp": timestamp.isoformat(timespec="seconds"), "resourceUpdate": { "name": DEVICE_ID, "events": { "sdm.devices.events.CameraMotion.Motion": { "eventSessionId": event_session_id, "eventId": event_id, }, }, }, }, auth=None, )
async def test_structure_update_event(hass): """Test a pubsub message for a new device being added.""" events = async_capture_events(hass, NEST_EVENT) subscriber = await async_setup_devices( hass, "sdm.devices.types.DOORBELL", create_device_traits(["sdm.devices.traits.DoorbellChime"]), ) # Entity for first device is registered registry = er.async_get(hass) assert registry.async_get("camera.front") new_device = Device.MakeDevice( { "name": "device-id-2", "type": "sdm.devices.types.CAMERA", "traits": { "sdm.devices.traits.Info": { "customName": "Back", }, "sdm.devices.traits.CameraLiveStream": {}, }, }, auth=None, ) device_manager = await subscriber.async_get_device_manager() device_manager.add_device(new_device) # Entity for new devie has not yet been loaded assert not registry.async_get("camera.back") # Send a message that triggers the device to be loaded message = EventMessage( { "eventId": "some-event-id", "timestamp": utcnow().isoformat(timespec="seconds"), "relationUpdate": { "type": "CREATED", "subject": "enterprise/example/foo", "object": "enterprise/example/devices/some-device-id2", }, }, auth=None, ) with patch( "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation" ), patch("homeassistant.components.nest.PLATFORMS", [PLATFORM]), patch( "homeassistant.components.nest.api.GoogleNestSubscriber", return_value=subscriber, ): await subscriber.async_receive_event(message) await hass.async_block_till_done() # No home assistant events published assert not events assert registry.async_get("camera.front") # Currently need a manual reload to detect the new entity assert not registry.async_get("camera.back")
def create_event_message(event_id, event_data, timestamp): """Create an EventMessage for a single event type.""" return EventMessage( { "eventId": f"{event_id}-{timestamp}", "timestamp": timestamp.isoformat(timespec="seconds"), "resourceUpdate": { "name": DEVICE_ID, "events": event_data, }, }, auth=None, )
def create_events(events, device_id=DEVICE_ID): """Create an EventMessage for events.""" return EventMessage( { "eventId": "some-event-id", "timestamp": utcnow().isoformat(timespec="seconds"), "resourceUpdate": { "name": device_id, "events": events, }, }, auth=None, )
async def create_event(traits: dict[str, Any]) -> None: await subscriber.async_receive_event( EventMessage( { "eventId": EVENT_ID, "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": DEVICE_ID, "traits": traits, }, }, auth=auth, )) await hass.async_block_till_done()
def create_event_message(event_data, timestamp, device_id=None): """Create an EventMessage for a single event type.""" if device_id is None: device_id = DEVICE_ID return EventMessage( { "eventId": f"{EVENT_ID}-{timestamp}", "timestamp": timestamp.isoformat(timespec="seconds"), "resourceUpdate": { "name": device_id, "events": event_data, }, }, auth=None, )
async def test_event_updates_sensor(hass): """Test a pubsub message received by subscriber to update temperature.""" devices = { "some-device-id": Device.MakeDevice( { "name": "some-device-id", "type": THERMOSTAT_TYPE, "traits": { "sdm.devices.traits.Info": { "customName": "My Sensor", }, "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 25.1, }, }, }, auth=None, ) } subscriber = await async_setup_sensor(hass, devices) temperature = hass.states.get("sensor.my_sensor_temperature") assert temperature is not None assert temperature.state == "25.1" # Simulate a pubsub message received by the subscriber with a trait update event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": "some-device-id", "traits": { "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 26.2, }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal temperature = hass.states.get("sensor.my_sensor_temperature") assert temperature is not None assert temperature.state == "26.2"
async def test_event_message_without_device_event(hass, subscriber, setup_platform): """Test a pubsub message for an unknown event type.""" events = async_capture_events(hass, NEST_EVENT) await setup_platform() timestamp = utcnow() event = EventMessage( { "eventId": "some-event-id", "timestamp": timestamp.isoformat(timespec="seconds"), }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() assert len(events) == 0
async def test_subscriber_automation( hass: HomeAssistant, calls: list, create_device: CreateDevice, setup_platform: PlatformSetup, subscriber: FakeSubscriber, ) -> None: """Test end to end subscriber triggers automation.""" create_device.create(raw_data=make_camera( device_id=DEVICE_ID, traits={ "sdm.devices.traits.CameraMotion": {}, }, )) await setup_platform() device_registry = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) assert await setup_automation(hass, device_entry.id, "camera_motion") # Simulate a pubsub message received by the subscriber with a motion event event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": DEVICE_ID, "events": { "sdm.devices.events.CameraMotion.Motion": { "eventSessionId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...", "eventId": "FWWVQVUdGNUlTU2V4MGV2aTNXV...", }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].data == DATA_MESSAGE
async def test_event_updates_sensor( hass: HomeAssistant, subscriber: FakeSubscriber, create_device: CreateDevice, setup_platform: PlatformSetup, ) -> None: """Test a pubsub message received by subscriber to update temperature.""" create_device.create( { "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 25.1, }, } ) await setup_platform() temperature = hass.states.get("sensor.my_sensor_temperature") assert temperature is not None assert temperature.state == "25.1" # Simulate a pubsub message received by the subscriber with a trait update event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": DEVICE_ID, "traits": { "sdm.devices.traits.Temperature": { "ambientTemperatureCelsius": 26.2, }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal temperature = hass.states.get("sensor.my_sensor_temperature") assert temperature is not None assert temperature.state == "26.2"
async def test_event_message_without_device_event(hass): """Test a pubsub message for an unknown event type.""" events = async_capture_events(hass, NEST_EVENT) subscriber = await async_setup_devices( hass, "sdm.devices.types.DOORBELL", create_device_traits("sdm.devices.traits.DoorbellChime"), ) timestamp = utcnow() event = EventMessage( { "eventId": "some-event-id", "timestamp": timestamp.isoformat(timespec="seconds"), }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() assert len(events) == 0
async def test_subscriber_automation(hass, calls): """Test end to end subscriber triggers automation.""" camera = make_camera( device_id=DEVICE_ID, traits={ "sdm.devices.traits.CameraMotion": {}, }, ) subscriber = await async_setup_camera(hass, {DEVICE_ID: camera}) device_registry = await hass.helpers.device_registry.async_get_registry() device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}, connections={}) assert await setup_automation(hass, device_entry.id, "camera_motion") # Simulate a pubsub message received by the subscriber with a motion event event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": DEVICE_ID, "events": { "sdm.devices.events.CameraMotion.Motion": { "eventSessionId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...", "eventId": "FWWVQVUdGNUlTU2V4MGV2aTNXV...", }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() assert len(calls) == 1 assert calls[0].data == DATA_MESSAGE
def MakeEvent(raw_data: dict) -> EventMessage: return EventMessage(raw_data, auth=None)
async def test_thermostat_set_eco_preset(hass, auth): """Test a thermostat put into eco mode.""" subscriber = await setup_climate( hass, { "sdm.devices.traits.ThermostatHvac": { "status": "OFF" }, "sdm.devices.traits.ThermostatEco": { "availableModes": ["MANUAL_ECO", "OFF"], "mode": "OFF", "heatCelsius": 15.0, "coolCelsius": 28.0, }, "sdm.devices.traits.ThermostatMode": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "OFF", }, }, auth=auth, ) assert len(hass.states.async_all()) == 1 thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF assert thermostat.attributes[ATTR_PRESET_MODE] == PRESET_NONE # Turn on eco mode await common.async_set_preset_mode(hass, PRESET_ECO) await hass.async_block_till_done() assert auth.method == "post" assert auth.url == "some-device-id:executeCommand" assert auth.json == { "command": "sdm.devices.commands.ThermostatEco.SetMode", "params": { "mode": "MANUAL_ECO" }, } # Local state does not reflect the update thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF assert thermostat.attributes[ATTR_PRESET_MODE] == PRESET_NONE # Simulate pubsub message when mode changes event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": "some-device-id", "traits": { "sdm.devices.traits.ThermostatEco": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "MANUAL_ECO", "heatCelsius": 15.0, "coolCelsius": 28.0, }, }, }, }, auth=auth, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF assert thermostat.attributes[ATTR_PRESET_MODE] == PRESET_ECO # Turn off eco mode await common.async_set_preset_mode(hass, PRESET_NONE) await hass.async_block_till_done() assert auth.method == "post" assert auth.url == "some-device-id:executeCommand" assert auth.json == { "command": "sdm.devices.commands.ThermostatEco.SetMode", "params": { "mode": "OFF" }, }
async def test_thermostat_set_hvac_mode(hass, auth): """Test a thermostat changing hvac modes.""" subscriber = await setup_climate( hass, { "sdm.devices.traits.ThermostatHvac": { "status": "OFF" }, "sdm.devices.traits.ThermostatMode": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "OFF", }, }, auth=auth, ) assert len(hass.states.async_all()) == 1 thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF await common.async_set_hvac_mode(hass, HVAC_MODE_HEAT) await hass.async_block_till_done() assert auth.method == "post" assert auth.url == "some-device-id:executeCommand" assert auth.json == { "command": "sdm.devices.commands.ThermostatMode.SetMode", "params": { "mode": "HEAT" }, } # Local state does not reflect the update thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_OFF assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF # Simulate pubsub message when mode changes event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": "some-device-id", "traits": { "sdm.devices.traits.ThermostatMode": { "availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"], "mode": "HEAT", }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_HEAT assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_OFF # Simulate pubsub message when the thermostat starts heating event = EventMessage( { "eventId": "some-event-id", "timestamp": "2019-01-01T00:00:01Z", "resourceUpdate": { "name": "some-device-id", "traits": { "sdm.devices.traits.ThermostatHvac": { "status": "HEATING", }, }, }, }, auth=None, ) await subscriber.async_receive_event(event) await hass.async_block_till_done() # Process dispatch/update signal thermostat = hass.states.get("climate.my_thermostat") assert thermostat is not None assert thermostat.state == HVAC_MODE_HEAT assert thermostat.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_HEAT