diff --git a/homeassistant/components/homekit_controller/binary_sensor.py b/homeassistant/components/homekit_controller/binary_sensor.py index 11c81e7e251..c980e31b50c 100644 --- a/homeassistant/components/homekit_controller/binary_sensor.py +++ b/homeassistant/components/homekit_controller/binary_sensor.py @@ -10,9 +10,11 @@ from homeassistant.components.binary_sensor import ( ) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import KNOWN_DEVICES +from .connection import HKDevice from .entity import HomeKitEntity @@ -106,6 +108,29 @@ class HomeKitLeakSensor(HomeKitEntity, BinarySensorEntity): return self.service.value(CharacteristicsTypes.LEAK_DETECTED) == 1 +class HomeKitBatteryLowSensor(HomeKitEntity, BinarySensorEntity): + """Representation of a Homekit battery low sensor.""" + + _attr_device_class = BinarySensorDeviceClass.BATTERY + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def get_characteristic_types(self) -> list[str]: + """Define the homekit characteristics the entity is tracking.""" + return [CharacteristicsTypes.STATUS_LO_BATT] + + @property + def name(self) -> str: + """Return the name of the sensor.""" + if name := self.accessory.name: + return f"{name} Low Battery" + return "Low Battery" + + @property + def is_on(self) -> bool: + """Return true if low battery is detected from the binary sensor.""" + return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1 + + ENTITY_TYPES = { ServicesTypes.MOTION_SENSOR: HomeKitMotionSensor, ServicesTypes.CONTACT_SENSOR: HomeKitContactSensor, @@ -113,6 +138,17 @@ ENTITY_TYPES = { ServicesTypes.CARBON_MONOXIDE_SENSOR: HomeKitCarbonMonoxideSensor, ServicesTypes.OCCUPANCY_SENSOR: HomeKitOccupancySensor, ServicesTypes.LEAK_SENSOR: HomeKitLeakSensor, + ServicesTypes.BATTERY_SERVICE: HomeKitBatteryLowSensor, +} + +# Only create the entity if it has the required characteristic +REQUIRED_CHAR_BY_TYPE = { + ServicesTypes.BATTERY_SERVICE: CharacteristicsTypes.STATUS_LO_BATT, +} +# Reject the service as another platform can represent it better +# if it has a specific characteristic +REJECT_CHAR_BY_TYPE = { + ServicesTypes.BATTERY_SERVICE: CharacteristicsTypes.BATTERY_LEVEL, } @@ -123,12 +159,20 @@ async def async_setup_entry( ) -> None: """Set up Homekit lighting.""" hkid = config_entry.data["AccessoryPairingID"] - conn = hass.data[KNOWN_DEVICES][hkid] + conn: HKDevice = hass.data[KNOWN_DEVICES][hkid] @callback def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False + if ( + required_char := REQUIRED_CHAR_BY_TYPE.get(service.type) + ) and not service.has(required_char): + return False + if (reject_char := REJECT_CHAR_BY_TYPE.get(service.type)) and service.has( + reject_char + ): + return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) return True diff --git a/homeassistant/components/homekit_controller/number.py b/homeassistant/components/homekit_controller/number.py index 29cad299902..6347ccb2a56 100644 --- a/homeassistant/components/homekit_controller/number.py +++ b/homeassistant/components/homekit_controller/number.py @@ -60,7 +60,7 @@ async def async_setup_entry( ) -> None: """Set up Homekit numbers.""" hkid = config_entry.data["AccessoryPairingID"] - conn = hass.data[KNOWN_DEVICES][hkid] + conn: HKDevice = hass.data[KNOWN_DEVICES][hkid] @callback def async_add_characteristic(char: Characteristic) -> bool: diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 04856a60347..3195cf6ee50 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -410,6 +410,7 @@ class HomeKitBatterySensor(HomeKitSensor): _attr_device_class = SensorDeviceClass.BATTERY _attr_native_unit_of_measurement = PERCENTAGE + _attr_entity_category = EntityCategory.DIAGNOSTIC def get_characteristic_types(self) -> list[str]: """Define the homekit characteristics the entity is tracking.""" @@ -517,6 +518,11 @@ ENTITY_TYPES = { ServicesTypes.BATTERY_SERVICE: HomeKitBatterySensor, } +# Only create the entity if it has the required characteristic +REQUIRED_CHAR_BY_TYPE = { + ServicesTypes.BATTERY_SERVICE: CharacteristicsTypes.BATTERY_LEVEL, +} + async def async_setup_entry( hass: HomeAssistant, @@ -531,6 +537,10 @@ async def async_setup_entry( def async_add_service(service: Service) -> bool: if not (entity_class := ENTITY_TYPES.get(service.type)): return False + if ( + required_char := REQUIRED_CHAR_BY_TYPE.get(service.type) + ) and not service.has(required_char): + return False info = {"aid": service.accessory.aid, "iid": service.iid} async_add_entities([entity_class(conn, info)], True) return True diff --git a/tests/components/homekit_controller/fixtures/netamo_smart_co_alarm.json b/tests/components/homekit_controller/fixtures/netamo_smart_co_alarm.json new file mode 100644 index 00000000000..432fd948d3e --- /dev/null +++ b/tests/components/homekit_controller/fixtures/netamo_smart_co_alarm.json @@ -0,0 +1,362 @@ +[ + { + "aid": 1, + "services": [ + { + "iid": 1, + "type": "000000A2-0000-1000-8000-0026BB765291", + "characteristics": [ + { + "type": "00000037-0000-1000-8000-0026BB765291", + "iid": 19, + "perms": ["pr"], + "format": "string", + "value": "2.2.0", + "description": "Version", + "maxLen": 64 + }, + { + "type": "000000A5-0000-1000-8000-0026BB765291", + "iid": 18, + "perms": ["pr"], + "format": "data", + "value": "" + } + ] + }, + { + "iid": 4, + "type": "EA22EA53-6227-55EA-AC24-73ACF3EEA0E8", + "characteristics": [ + { + "type": "000000A5-0000-1000-8000-0026BB765291", + "iid": 340, + "perms": ["pr"], + "format": "data", + "value": "" + }, + { + "type": "00F44C18-042E-5C4E-9A4C-561D44DCD804", + "iid": 339, + "perms": ["pr", "hd"], + "format": "string", + "value": "g8d8a6c", + "maxLen": 64 + } + ] + }, + { + "iid": 7, + "type": "0000003E-0000-1000-8000-0026BB765291", + "characteristics": [ + { + "type": "00000023-0000-1000-8000-0026BB765291", + "iid": 2, + "perms": ["pr"], + "format": "string", + "value": "Smart CO Alarm", + "description": "Name", + "maxLen": 64 + }, + { + "type": "00000014-0000-1000-8000-0026BB765291", + "iid": 3, + "perms": ["pw"], + "format": "bool", + "description": "Identify" + }, + { + "type": "00000053-0000-1000-8000-0026BB765291", + "iid": 8, + "perms": ["pr"], + "format": "string", + "value": "0", + "description": "Hardware Revision", + "maxLen": 64 + }, + { + "type": "00000020-0000-1000-8000-0026BB765291", + "iid": 4, + "perms": ["pr"], + "format": "string", + "value": "Netatmo", + "description": "Manufacturer", + "maxLen": 64 + }, + { + "type": "00000030-0000-1000-8000-0026BB765291", + "iid": 6, + "perms": ["pr"], + "format": "string", + "value": "1234", + "description": "Serial Number", + "maxLen": 64 + }, + { + "type": "00000021-0000-1000-8000-0026BB765291", + "iid": 5, + "perms": ["pr"], + "format": "string", + "value": "Smart CO Alarm", + "description": "Model", + "maxLen": 64 + }, + { + "type": "00000052-0000-1000-8000-0026BB765291", + "iid": 7, + "perms": ["pr"], + "format": "string", + "value": "1.0.3", + "description": "Firmware Revision", + "maxLen": 64 + }, + { + "type": "34AB8811-AC7F-4340-BAC3-FD6A85F9943B", + "iid": 47, + "perms": ["pr", "hd"], + "format": "string", + "value": "4.1;Sep 27 2021 12:54:49", + "maxLen": 64 + }, + { + "type": "00000220-0000-1000-8000-0026BB765291", + "iid": 325, + "perms": ["pr", "hd"], + "format": "data", + "value": "fa7beb3a4566c1fb" + } + ] + }, + { + "iid": 17, + "type": "00000055-0000-1000-8000-0026BB765291", + "characteristics": [ + { + "type": "0000004C-0000-1000-8000-0026BB765291", + "iid": 203, + "perms": [], + "format": "data", + "description": "Pair Setup" + }, + { + "type": "0000004F-0000-1000-8000-0026BB765291", + "iid": 205, + "perms": [], + "format": "uint8", + "description": "Pairing Features" + }, + { + "type": "0000004E-0000-1000-8000-0026BB765291", + "iid": 204, + "perms": [], + "format": "data", + "description": "Pair Verify" + }, + { + "type": "00000050-0000-1000-8000-0026BB765291", + "iid": 206, + "perms": ["pr", "pw"], + "format": "data", + "value": null, + "description": "Pairing Pairings" + } + ] + }, + { + "iid": 22, + "type": "0000007F-0000-1000-8000-0026BB765291", + "characteristics": [ + { + "type": "00000075-0000-1000-8000-0026BB765291", + "iid": 230, + "perms": ["pr", "ev"], + "format": "bool", + "value": true, + "description": "Status Active" + }, + { + "type": "00000077-0000-1000-8000-0026BB765291", + "iid": 231, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "description": "Status Fault", + "minValue": 0, + "maxValue": 1 + }, + { + "type": "00000023-0000-1000-8000-0026BB765291", + "iid": 42, + "perms": ["pr"], + "format": "string", + "value": "Carbon Monoxide Sensor", + "description": "Name", + "maxLen": 64 + }, + { + "type": "00000091-0000-1000-8000-0026BB765291", + "iid": 41, + "perms": ["pr", "ev"], + "format": "float", + "value": 0.0, + "description": "Carbon Monoxide Peak Level", + "minValue": 0.0, + "maxValue": 1000.0 + }, + { + "type": "00000019-4DDB-598F-A73F-006513F2DB6B", + "iid": 333, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "minValue": 0, + "maxValue": 3 + }, + { + "type": "00000090-0000-1000-8000-0026BB765291", + "iid": 40, + "perms": ["pr", "ev"], + "format": "float", + "value": 0.0, + "description": "Carbon Monoxide Level", + "minValue": 0.0, + "maxValue": 1000.0 + }, + { + "type": "0000000C-4DDB-598F-A73F-006513F2DB6B", + "iid": 326, + "perms": ["pr", "ev"], + "format": "bool", + "value": false + }, + { + "type": "0000007A-0000-1000-8000-0026BB765291", + "iid": 45, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "description": "Status Tampered", + "minValue": 0, + "maxValue": 1 + }, + { + "type": "00000001-4DDB-598F-A73F-006513F2DB6B", + "iid": 327, + "perms": ["pr", "pw", "ev"], + "format": "bool", + "value": false + }, + { + "type": "00000012-4DDB-598F-A73F-006513F2DB6B", + "iid": 328, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "minValue": 0, + "maxValue": 3 + }, + { + "type": "000000A5-0000-1000-8000-0026BB765291", + "iid": 43, + "perms": ["pr"], + "format": "data", + "value": "" + }, + { + "type": "00000069-0000-1000-8000-0026BB765291", + "iid": 229, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "description": "Carbon Monoxide Detected", + "minValue": 0, + "maxValue": 1 + } + ] + }, + { + "iid": 35, + "type": "00001801-0000-1000-8000-00805F9B34FB", + "characteristics": [] + }, + { + "iid": 36, + "type": "00000096-0000-1000-8000-0026BB765291", + "characteristics": [ + { + "type": "00000023-0000-1000-8000-0026BB765291", + "iid": 336, + "perms": ["pr"], + "format": "string", + "value": "Battery Service", + "description": "Name", + "maxLen": 64 + }, + { + "type": "00000079-0000-1000-8000-0026BB765291", + "iid": 337, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "description": "Status Low Battery", + "minValue": 0, + "maxValue": 1 + }, + { + "type": "00000002-4DDB-598F-A73F-006513F2DB6B", + "iid": 335, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "minValue": 0, + "maxValue": 4 + } + ] + }, + { + "iid": 40, + "type": "00000004-4DDB-598F-A73F-006513F2DB6B", + "characteristics": [ + { + "type": "00000007-4DDB-598F-A73F-006513F2DB6B", + "iid": 187, + "perms": ["pr", "ev"], + "format": "uint8", + "value": 0, + "minValue": 0, + "maxValue": 14 + }, + { + "type": "000000A5-0000-1000-8000-0026BB765291", + "iid": 183, + "perms": ["pr"], + "format": "data", + "value": "" + }, + { + "type": "00000009-4DDB-598F-A73F-006513F2DB6B", + "iid": 184, + "perms": ["pw", "hd"], + "format": "uint8", + "minValue": 0, + "maxValue": 1 + }, + { + "type": "00000006-4DDB-598F-A73F-006513F2DB6B", + "iid": 188, + "perms": ["pr"], + "format": "data", + "value": "0000000000000000" + }, + { + "type": "0000000A-4DDB-598F-A73F-006513F2DB6B", + "iid": 324, + "perms": ["pr", "ev"], + "format": "uint32", + "value": 3 + } + ] + } + ] + } +] diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py index f3039e754d8..793fb49af5b 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_switch.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_switch.py @@ -9,6 +9,7 @@ https://github.com/home-assistant/core/pull/39090 from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE +from homeassistant.helpers.entity import EntityCategory from ..common import ( HUB_TEST_ACCESSORY_ID, @@ -43,6 +44,7 @@ async def test_aqara_switch_setup(hass): friendly_name="Programmable Switch Battery Sensor", unique_id="homekit-111a1111a1a111-5", capabilities={"state_class": SensorStateClass.MEASUREMENT}, + entity_category=EntityCategory.DIAGNOSTIC, unit_of_measurement=PERCENTAGE, state="100", ), diff --git a/tests/components/homekit_controller/specific_devices/test_arlo_baby.py b/tests/components/homekit_controller/specific_devices/test_arlo_baby.py index 9a88e992a85..1b2b4bda3d6 100644 --- a/tests/components/homekit_controller/specific_devices/test_arlo_baby.py +++ b/tests/components/homekit_controller/specific_devices/test_arlo_baby.py @@ -2,6 +2,7 @@ from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.helpers.entity import EntityCategory from ..common import ( HUB_TEST_ACCESSORY_ID, @@ -46,6 +47,7 @@ async def test_arlo_baby_setup(hass): entity_id="sensor.arlobabya0_battery", unique_id="homekit-00A0000000000-700", friendly_name="ArloBabyA0 Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=PERCENTAGE, state="82", diff --git a/tests/components/homekit_controller/specific_devices/test_eve_degree.py b/tests/components/homekit_controller/specific_devices/test_eve_degree.py index c3ca4b22f1a..eab2de030db 100644 --- a/tests/components/homekit_controller/specific_devices/test_eve_degree.py +++ b/tests/components/homekit_controller/specific_devices/test_eve_degree.py @@ -60,6 +60,7 @@ async def test_eve_degree_setup(hass): entity_id="sensor.eve_degree_aa11_battery", unique_id="homekit-AA00A0A00000-17", friendly_name="Eve Degree AA11 Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unit_of_measurement=PERCENTAGE, state="65", diff --git a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py index ac6d8bdafa6..1092bb4f82c 100644 --- a/tests/components/homekit_controller/specific_devices/test_hue_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_hue_bridge.py @@ -2,6 +2,7 @@ from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE +from homeassistant.helpers.entity import EntityCategory from ..common import ( HUB_TEST_ACCESSORY_ID, @@ -44,6 +45,7 @@ async def test_hue_bridge_setup(hass): entity_id="sensor.hue_dimmer_switch_battery", capabilities={"state_class": SensorStateClass.MEASUREMENT}, friendly_name="Hue dimmer switch battery", + entity_category=EntityCategory.DIAGNOSTIC, unique_id="homekit-6623462389072572-644245094400", unit_of_measurement=PERCENTAGE, state="100", diff --git a/tests/components/homekit_controller/specific_devices/test_netamo_smart_co_alarm.py b/tests/components/homekit_controller/specific_devices/test_netamo_smart_co_alarm.py new file mode 100644 index 00000000000..b2c83a005f8 --- /dev/null +++ b/tests/components/homekit_controller/specific_devices/test_netamo_smart_co_alarm.py @@ -0,0 +1,50 @@ +""" +Regression tests for Netamo Smart CO Alarm. + +https://github.com/home-assistant/core/issues/78903 +""" +from homeassistant.helpers.entity import EntityCategory + +from ..common import ( + HUB_TEST_ACCESSORY_ID, + DeviceTestInfo, + EntityTestInfo, + assert_devices_and_entities_created, + setup_accessories_from_file, + setup_test_accessories, +) + + +async def test_netamo_smart_co_alarm_setup(hass): + """Test that a Netamo Smart CO Alarm can be correctly setup in HA.""" + accessories = await setup_accessories_from_file(hass, "netamo_smart_co_alarm.json") + await setup_test_accessories(hass, accessories) + + await assert_devices_and_entities_created( + hass, + DeviceTestInfo( + unique_id=HUB_TEST_ACCESSORY_ID, + name="Smart CO Alarm", + model="Smart CO Alarm", + manufacturer="Netatmo", + sw_version="1.0.3", + hw_version="0", + serial_number="1234", + devices=[], + entities=[ + EntityTestInfo( + entity_id="binary_sensor.smart_co_alarm_carbon_monoxide_sensor", + friendly_name="Smart CO Alarm Carbon Monoxide Sensor", + unique_id="homekit-1234-22", + state="off", + ), + EntityTestInfo( + entity_id="binary_sensor.smart_co_alarm_low_battery", + friendly_name="Smart CO Alarm Low Battery", + entity_category=EntityCategory.DIAGNOSTIC, + unique_id="homekit-1234-36", + state="off", + ), + ], + ), + ) diff --git a/tests/components/homekit_controller/specific_devices/test_ryse_smart_bridge.py b/tests/components/homekit_controller/specific_devices/test_ryse_smart_bridge.py index aa9dee89be2..a0c84472429 100644 --- a/tests/components/homekit_controller/specific_devices/test_ryse_smart_bridge.py +++ b/tests/components/homekit_controller/specific_devices/test_ryse_smart_bridge.py @@ -3,6 +3,7 @@ from homeassistant.components.cover import CoverEntityFeature from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE +from homeassistant.helpers.entity import EntityCategory from ..common import ( HUB_TEST_ACCESSORY_ID, @@ -53,6 +54,7 @@ async def test_ryse_smart_bridge_setup(hass): EntityTestInfo( entity_id="sensor.master_bath_south_ryse_shade_battery", friendly_name="Master Bath South RYSE Shade Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unique_id="homekit-00:00:00:00:00:00-2-64", unit_of_measurement=PERCENTAGE, @@ -80,6 +82,7 @@ async def test_ryse_smart_bridge_setup(hass): EntityTestInfo( entity_id="sensor.ryse_smartshade_ryse_shade_battery", friendly_name="RYSE SmartShade RYSE Shade Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unique_id="homekit-00:00:00:00:00:00-3-64", unit_of_measurement=PERCENTAGE, @@ -130,6 +133,7 @@ async def test_ryse_smart_bridge_four_shades_setup(hass): EntityTestInfo( entity_id="sensor.lr_left_ryse_shade_battery", friendly_name="LR Left RYSE Shade Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unique_id="homekit-00:00:00:00:00:00-2-64", unit_of_measurement=PERCENTAGE, @@ -157,6 +161,7 @@ async def test_ryse_smart_bridge_four_shades_setup(hass): EntityTestInfo( entity_id="sensor.lr_right_ryse_shade_battery", friendly_name="LR Right RYSE Shade Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unique_id="homekit-00:00:00:00:00:00-3-64", unit_of_measurement=PERCENTAGE, @@ -184,6 +189,7 @@ async def test_ryse_smart_bridge_four_shades_setup(hass): EntityTestInfo( entity_id="sensor.br_left_ryse_shade_battery", friendly_name="BR Left RYSE Shade Battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, unique_id="homekit-00:00:00:00:00:00-4-64", unit_of_measurement=PERCENTAGE, @@ -210,6 +216,7 @@ async def test_ryse_smart_bridge_four_shades_setup(hass): ), EntityTestInfo( entity_id="sensor.rzss_ryse_shade_battery", + entity_category=EntityCategory.DIAGNOSTIC, capabilities={"state_class": SensorStateClass.MEASUREMENT}, friendly_name="RZSS RYSE Shade Battery", unique_id="homekit-00:00:00:00:00:00-5-64", diff --git a/tests/components/homekit_controller/test_device_trigger.py b/tests/components/homekit_controller/test_device_trigger.py index 22404063663..a09525d9dec 100644 --- a/tests/components/homekit_controller/test_device_trigger.py +++ b/tests/components/homekit_controller/test_device_trigger.py @@ -98,7 +98,7 @@ async def test_enumerate_remote(hass, utcnow): "entity_id": "sensor.testdevice_battery", "platform": "device", "type": "battery_level", - "metadata": {"secondary": False}, + "metadata": {"secondary": True}, }, { "device_id": device.id, @@ -146,7 +146,7 @@ async def test_enumerate_button(hass, utcnow): "entity_id": "sensor.testdevice_battery", "platform": "device", "type": "battery_level", - "metadata": {"secondary": False}, + "metadata": {"secondary": True}, }, { "device_id": device.id, @@ -193,7 +193,7 @@ async def test_enumerate_doorbell(hass, utcnow): "entity_id": "sensor.testdevice_battery", "platform": "device", "type": "battery_level", - "metadata": {"secondary": False}, + "metadata": {"secondary": True}, }, { "device_id": device.id,