mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Add sensor
platform for Tractive integration (#54143)
* Add sensor platform * Add extra_state_attributes * Add more constants * Add sensor.py to .coveragerc file * Use native value * Suggested change * Move SENSOR_TYPES to sensor platform * Add domain as prefix to the signal * Use TractiveEntity class * Add model.py to .coveragerc file * Clean up files * Add entity_class attribute to TractiveSensorEntityDescription class * TractiveEntity inherits from Entity * Suggested change * Define _attr_icon as class attribute Co-authored-by: Daniel Hjelseth Høyer <mail@dahoiv.net>
This commit is contained in:
parent
f91d214ba4
commit
09b872d51f
@ -1096,6 +1096,8 @@ omit =
|
||||
homeassistant/components/trackr/device_tracker.py
|
||||
homeassistant/components/tractive/__init__.py
|
||||
homeassistant/components/tractive/device_tracker.py
|
||||
homeassistant/components/tractive/entity.py
|
||||
homeassistant/components/tractive/sensor.py
|
||||
homeassistant/components/tradfri/*
|
||||
homeassistant/components/trafikverket_train/sensor.py
|
||||
homeassistant/components/trafikverket_weatherstation/sensor.py
|
||||
|
@ -530,7 +530,7 @@ homeassistant/components/totalconnect/* @austinmroczek
|
||||
homeassistant/components/tplink/* @rytilahti @thegardenmonkey
|
||||
homeassistant/components/traccar/* @ludeeus
|
||||
homeassistant/components/trace/* @home-assistant/core
|
||||
homeassistant/components/tractive/* @Danielhiversen @zhulik
|
||||
homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu
|
||||
homeassistant/components/tradfri/* @janiversen
|
||||
homeassistant/components/trafikverket_train/* @endor-force
|
||||
homeassistant/components/trafikverket_weatherstation/* @endor-force
|
||||
|
@ -7,21 +7,29 @@ import logging
|
||||
import aiotractive
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
CONF_EMAIL,
|
||||
CONF_PASSWORD,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import (
|
||||
ATTR_DAILY_GOAL,
|
||||
ATTR_MINUTES_ACTIVE,
|
||||
DOMAIN,
|
||||
RECONNECT_INTERVAL,
|
||||
SERVER_UNAVAILABLE,
|
||||
TRACKER_ACTIVITY_STATUS_UPDATED,
|
||||
TRACKER_HARDWARE_STATUS_UPDATED,
|
||||
TRACKER_POSITION_UPDATED,
|
||||
)
|
||||
|
||||
PLATFORMS = ["device_tracker"]
|
||||
PLATFORMS = ["device_tracker", "sensor"]
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -112,14 +120,15 @@ class TractiveClient:
|
||||
if server_was_unavailable:
|
||||
_LOGGER.debug("Tractive is back online")
|
||||
server_was_unavailable = False
|
||||
if event["message"] != "tracker_status":
|
||||
continue
|
||||
|
||||
if "hardware" in event:
|
||||
self._send_hardware_update(event)
|
||||
if event["message"] == "activity_update":
|
||||
self._send_activity_update(event)
|
||||
else:
|
||||
if "hardware" in event:
|
||||
self._send_hardware_update(event)
|
||||
|
||||
if "position" in event:
|
||||
self._send_position_update(event)
|
||||
if "position" in event:
|
||||
self._send_position_update(event)
|
||||
except aiotractive.exceptions.TractiveError:
|
||||
_LOGGER.debug(
|
||||
"Tractive is not available. Internet connection is down? Sleeping %i seconds and retrying",
|
||||
@ -133,11 +142,20 @@ class TractiveClient:
|
||||
continue
|
||||
|
||||
def _send_hardware_update(self, event):
|
||||
payload = {"battery_level": event["hardware"]["battery_level"]}
|
||||
payload = {ATTR_BATTERY_LEVEL: event["hardware"]["battery_level"]}
|
||||
self._dispatch_tracker_event(
|
||||
TRACKER_HARDWARE_STATUS_UPDATED, event["tracker_id"], payload
|
||||
)
|
||||
|
||||
def _send_activity_update(self, event):
|
||||
payload = {
|
||||
ATTR_MINUTES_ACTIVE: event["progress"]["achieved_minutes"],
|
||||
ATTR_DAILY_GOAL: event["progress"]["goal_minutes"],
|
||||
}
|
||||
self._dispatch_tracker_event(
|
||||
TRACKER_ACTIVITY_STATUS_UPDATED, event["pet_id"], payload
|
||||
)
|
||||
|
||||
def _send_position_update(self, event):
|
||||
payload = {
|
||||
"latitude": event["position"]["latlong"][0],
|
||||
|
@ -6,7 +6,11 @@ DOMAIN = "tractive"
|
||||
|
||||
RECONNECT_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
ATTR_DAILY_GOAL = "daily_goal"
|
||||
ATTR_MINUTES_ACTIVE = "minutes_active"
|
||||
|
||||
TRACKER_HARDWARE_STATUS_UPDATED = f"{DOMAIN}_tracker_hardware_status_updated"
|
||||
TRACKER_POSITION_UPDATED = f"{DOMAIN}_tracker_position_updated"
|
||||
TRACKER_ACTIVITY_STATUS_UPDATED = f"{DOMAIN}_tracker_activity_updated"
|
||||
|
||||
SERVER_UNAVAILABLE = f"{DOMAIN}_server_unavailable"
|
||||
|
@ -14,6 +14,7 @@ from .const import (
|
||||
TRACKER_HARDWARE_STATUS_UPDATED,
|
||||
TRACKER_POSITION_UPDATED,
|
||||
)
|
||||
from .entity import TractiveEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -45,29 +46,22 @@ async def create_trackable_entity(client, trackable):
|
||||
)
|
||||
|
||||
|
||||
class TractiveDeviceTracker(TrackerEntity):
|
||||
class TractiveDeviceTracker(TractiveEntity, TrackerEntity):
|
||||
"""Tractive device tracker."""
|
||||
|
||||
_attr_icon = "mdi:paw"
|
||||
|
||||
def __init__(self, user_id, trackable, tracker_details, hw_info, pos_report):
|
||||
"""Initialize tracker entity."""
|
||||
self._user_id = user_id
|
||||
super().__init__(user_id, trackable, tracker_details)
|
||||
|
||||
self._battery_level = hw_info["battery_level"]
|
||||
self._latitude = pos_report["latlong"][0]
|
||||
self._longitude = pos_report["latlong"][1]
|
||||
self._accuracy = pos_report["pos_uncertainty"]
|
||||
self._tracker_id = tracker_details["_id"]
|
||||
|
||||
self._attr_name = f"{self._tracker_id} {trackable['details']['name']}"
|
||||
self._attr_unique_id = trackable["_id"]
|
||||
self._attr_icon = "mdi:paw"
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, self._tracker_id)},
|
||||
"name": f"Tractive ({self._tracker_id})",
|
||||
"manufacturer": "Tractive GmbH",
|
||||
"sw_version": tracker_details["fw_version"],
|
||||
"model": tracker_details["model_number"],
|
||||
}
|
||||
|
||||
@property
|
||||
def source_type(self):
|
||||
|
22
homeassistant/components/tractive/entity.py
Normal file
22
homeassistant/components/tractive/entity.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""A entity class for Tractive integration."""
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class TractiveEntity(Entity):
|
||||
"""Tractive entity class."""
|
||||
|
||||
def __init__(self, user_id, trackable, tracker_details):
|
||||
"""Initialize tracker entity."""
|
||||
self._attr_device_info = {
|
||||
"identifiers": {(DOMAIN, tracker_details["_id"])},
|
||||
"name": f"Tractive ({tracker_details['_id']})",
|
||||
"manufacturer": "Tractive GmbH",
|
||||
"sw_version": tracker_details["fw_version"],
|
||||
"model": tracker_details["model_number"],
|
||||
}
|
||||
self._user_id = user_id
|
||||
self._tracker_id = tracker_details["_id"]
|
||||
self._trackable = trackable
|
@ -8,7 +8,8 @@
|
||||
],
|
||||
"codeowners": [
|
||||
"@Danielhiversen",
|
||||
"@zhulik"
|
||||
"@zhulik",
|
||||
"@bieniu"
|
||||
],
|
||||
"iot_class": "cloud_push"
|
||||
}
|
||||
|
173
homeassistant/components/tractive/sensor.py
Normal file
173
homeassistant/components/tractive/sensor.py
Normal file
@ -0,0 +1,173 @@
|
||||
"""Support for Tractive sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
PERCENTAGE,
|
||||
TIME_MINUTES,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_DAILY_GOAL,
|
||||
ATTR_MINUTES_ACTIVE,
|
||||
DOMAIN,
|
||||
SERVER_UNAVAILABLE,
|
||||
TRACKER_ACTIVITY_STATUS_UPDATED,
|
||||
TRACKER_HARDWARE_STATUS_UPDATED,
|
||||
)
|
||||
from .entity import TractiveEntity
|
||||
|
||||
|
||||
@dataclass
|
||||
class TractiveSensorEntityDescription(SensorEntityDescription):
|
||||
"""Class describing Tractive sensor entities."""
|
||||
|
||||
attributes: tuple = ()
|
||||
entity_class: type[TractiveSensor] | None = None
|
||||
|
||||
|
||||
class TractiveSensor(TractiveEntity, SensorEntity):
|
||||
"""Tractive sensor."""
|
||||
|
||||
def __init__(self, user_id, trackable, tracker_details, unique_id, description):
|
||||
"""Initialize sensor entity."""
|
||||
super().__init__(user_id, trackable, tracker_details)
|
||||
|
||||
self._attr_unique_id = unique_id
|
||||
self.entity_description = description
|
||||
|
||||
@callback
|
||||
def handle_server_unavailable(self):
|
||||
"""Handle server unavailable."""
|
||||
self._attr_available = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class TractiveHardwareSensor(TractiveSensor):
|
||||
"""Tractive hardware sensor."""
|
||||
|
||||
def __init__(self, user_id, trackable, tracker_details, unique_id, description):
|
||||
"""Initialize sensor entity."""
|
||||
super().__init__(user_id, trackable, tracker_details, unique_id, description)
|
||||
self._attr_name = f"{self._tracker_id} {description.name}"
|
||||
|
||||
@callback
|
||||
def handle_hardware_status_update(self, event):
|
||||
"""Handle hardware status update."""
|
||||
self._attr_native_value = event[self.entity_description.key]
|
||||
self._attr_available = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{TRACKER_HARDWARE_STATUS_UPDATED}-{self._tracker_id}",
|
||||
self.handle_hardware_status_update,
|
||||
)
|
||||
)
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{SERVER_UNAVAILABLE}-{self._user_id}",
|
||||
self.handle_server_unavailable,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TractiveActivitySensor(TractiveSensor):
|
||||
"""Tractive active sensor."""
|
||||
|
||||
def __init__(self, user_id, trackable, tracker_details, unique_id, description):
|
||||
"""Initialize sensor entity."""
|
||||
super().__init__(user_id, trackable, tracker_details, unique_id, description)
|
||||
self._attr_name = f"{trackable['details']['name']} {description.name}"
|
||||
|
||||
@callback
|
||||
def handle_activity_status_update(self, event):
|
||||
"""Handle activity status update."""
|
||||
self._attr_native_value = event[self.entity_description.key]
|
||||
self._attr_extra_state_attributes = {
|
||||
attr: event[attr] if attr in event else None
|
||||
for attr in self.entity_description.attributes
|
||||
}
|
||||
self._attr_available = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Handle entity which will be added."""
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{TRACKER_ACTIVITY_STATUS_UPDATED}-{self._trackable['_id']}",
|
||||
self.handle_activity_status_update,
|
||||
)
|
||||
)
|
||||
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{SERVER_UNAVAILABLE}-{self._user_id}",
|
||||
self.handle_server_unavailable,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
SENSOR_TYPES = (
|
||||
TractiveSensorEntityDescription(
|
||||
key=ATTR_BATTERY_LEVEL,
|
||||
name="Battery Level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
entity_class=TractiveHardwareSensor,
|
||||
),
|
||||
TractiveSensorEntityDescription(
|
||||
key=ATTR_MINUTES_ACTIVE,
|
||||
name="Minutes Active",
|
||||
icon="mdi:clock-time-eight-outline",
|
||||
native_unit_of_measurement=TIME_MINUTES,
|
||||
attributes=(ATTR_DAILY_GOAL,),
|
||||
entity_class=TractiveActivitySensor,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up Tractive device trackers."""
|
||||
client = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
trackables = await client.trackable_objects()
|
||||
|
||||
entities = []
|
||||
|
||||
async def _prepare_sensor_entity(item):
|
||||
"""Prepare sensor entities."""
|
||||
trackable = await item.details()
|
||||
tracker = client.tracker(trackable["device_id"])
|
||||
tracker_details = await tracker.details()
|
||||
for description in SENSOR_TYPES:
|
||||
unique_id = f"{trackable['_id']}_{description.key}"
|
||||
entities.append(
|
||||
description.entity_class(
|
||||
client.user_id,
|
||||
trackable,
|
||||
tracker_details,
|
||||
unique_id,
|
||||
description,
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*(_prepare_sensor_entity(item) for item in trackables))
|
||||
|
||||
async_add_entities(entities)
|
Loading…
x
Reference in New Issue
Block a user