diff --git a/custom_components/georide/__init__.py b/custom_components/georide/__init__.py index 0a830e4..9c09610 100644 --- a/custom_components/georide/__init__.py +++ b/custom_components/georide/__init__.py @@ -32,7 +32,7 @@ from homeassistant.helpers.update_coordinator import ( ) -from .device import Device +from .device import Device, DeviceBeacon from .const import ( CONF_EMAIL, CONF_PASSWORD, @@ -40,7 +40,8 @@ from .const import ( TRACKER_ID, TOKEN_SAFE_DAY, MIN_UNTIL_REFRESH, - DOMAIN + DOMAIN, + SIREN_ACTIVATION_DELAY ) @@ -105,6 +106,8 @@ async def async_setup_entry(hass, entry): hass.config_entries.async_forward_entry_setup(entry, "sensor")) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "binary_sensor")) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "siren")) return True @@ -115,6 +118,7 @@ async def async_unload_entry(hass, entry): await hass.config_entries.async_forward_entry_unload(entry, "switch") await hass.config_entries.async_forward_entry_unload(entry, "sensor") await hass.config_entries.async_forward_entry_unload(entry, "binary_sensor") + await hass.config_entries.async_forward_entry_unload(entry, "siren") context = hass.data[DOMAIN]["context"] @@ -133,6 +137,8 @@ class GeoRideContext: self._email = email self._password = password self._georide_trackers_coordoned = [] + self._georide_trackers_beacon_coordoned = [] + self._georide_trackers_beacon = [] self._georide_trackers = [] self._token = token self._socket = None @@ -176,7 +182,7 @@ class GeoRideContext: socket.subscribe_device(self.on_device_callback) socket.subscribe_position(self.on_position_callback) socket.subscribe_alarm(self.on_alarm_callback) - + socket.subscribe_refresh_tracker(self.on_refresh_tracker_callback) self._socket = socket socket.init() @@ -208,17 +214,36 @@ class GeoRideContext: return tracker return {} + async def get_tracker_beacon(self, beacon_id): + """ here we return last tracker_beacon by id""" + for tracker_beacon in self._georide_trackers_beacon: + if tracker_beacon.beacon_id == beacon_id: + return tracker_beacon + return {} + + async def get_tracker_beacons_by_tracker_id(self, tracker_id): + """ here we return last tracker_beacon by id""" + filtered_beacon = [] + for tracker_beacon in self._georide_trackers_beacon: + if tracker_beacon.linked_tracker_id == tracker_id: + filtered_beacon.append(tracker_beacon) + return filtered_beacon + async def refresh_trackers(self): """ here we return last tracker by id""" _LOGGER.debug("Call refresh tracker") epoch_min = math.floor(time.time()/60) - #if (epoch_min % MIN_UNTIL_REFRESH) == 0: if epoch_min != self._previous_refresh: self._previous_refresh = epoch_min await self.force_refresh_trackers() - #else: - # _LOGGER.debug("We wil dont refresh the tracker list") - + for tracker in self._georide_trackers: + if tracker.is_siren_on: + if time.time() - SIREN_ACTIVATION_DELAY > tracker.siren_last_on_date: + tracker.is_siren_on = False + + async def refresh_trackers_beacon(self): + """ here we return last tracker by id""" + _LOGGER.debug("Do nothing, updated by another way") async def force_refresh_trackers(self): """Used to refresh the tracker list""" @@ -230,9 +255,32 @@ class GeoRideContext: for tracker in self._georide_trackers: if tracker.tracker_id == refreshed_tracker.tracker_id: tracker.update_all_data(refreshed_tracker) + if tracker.version > 2: + await self.force_refresh_trackers_beacon(tracker.tracker_id) found = True if not found: self._georide_trackers.append(refreshed_tracker) + if refreshed_tracker.version > 2: + await self.force_refresh_trackers_beacon(refreshed_tracker.tracker_id) + if not self._thread_started: + _LOGGER.info("Start the thread") + # We refresh the tracker list each hours + self._thread_started = True + await self.connect_socket() + + async def force_refresh_trackers_beacon(self, tracker_id): + """Used to refresh the tracker list""" + _LOGGER.info("Tracker beacon refresh") + new_georide_tracker_beacons = await self._hass.async_add_executor_job(GeoRideApi.get_tracker_beacons, + await self.get_token(), tracker_id) + for new_georide_tracker_beacon in new_georide_tracker_beacons: + found = False + for tracker_beacon in self._georide_trackers_beacon: + if tracker_beacon.beacon_id == new_georide_tracker_beacon.beacon_id: + tracker_beacon.update_all_data(new_georide_tracker_beacon) + found = True + if not found: + self._georide_trackers_beacon.append(new_georide_tracker_beacon) if not self._thread_started: _LOGGER.info("Start the thread") # We refresh the tracker list each hours @@ -255,17 +303,41 @@ class GeoRideContext: update_method=self.refresh_trackers, update_interval=update_interval ) - self._georide_trackers_coordoned.append({ - "tracker_device": Device(tracker), - "coordinator": coordinator - }) + + coordoned_tracker = { + "tracker_device": Device(tracker), + "coordinator": coordinator + } + if tracker.version > 2: + tracker_beacons = await self.get_tracker_beacons_by_tracker_id(tracker.tracker_id) + for tracker_beacon in tracker_beacons: + beacon_coordinator = DataUpdateCoordinator[Mapping[str, Any]]( + hass, + _LOGGER, + name=tracker_beacon.name, + update_method=self.refresh_trackers_beacon, + update_interval=update_interval + ) + coordoned_beacon = { + "tracker_beacon": DeviceBeacon(tracker_beacon), + "coordinator": beacon_coordinator + } + self._georide_trackers_beacon_coordoned.append(coordoned_beacon) + self._georide_trackers_coordoned.append(coordoned_tracker) - def get_coordoned_trackers(self): + @property + def georide_trackers_coordoned(self): """Return coordoned trackers""" return self._georide_trackers_coordoned + @property + def georide_trackers_beacon_coordoned(self): + """Return coordoned trackers""" + + return self._georide_trackers_beacon_coordoned + @property def socket(self): """ hold the GeoRide socket """ @@ -281,7 +353,8 @@ class GeoRideContext: """on lock callback""" _LOGGER.info("On lock received") for coordoned_tracker in self._georide_trackers_coordoned: - tracker = coordoned_tracker['tracker_device'].tracker + tracker_device = coordoned_tracker['tracker_device'] + tracker = tracker_device.tracker coordinator = coordoned_tracker['coordinator'] if tracker.tracker_id == data['trackerId']: tracker.locked_latitude = data['lockedLatitude'] @@ -289,8 +362,8 @@ class GeoRideContext: tracker.is_locked = data['isLocked'] event_data = { - "device_id": tracker.tracker_id, - "device_name": tracker.tracker_name + "device_id": tracker_device.unique_id, + "device_name": tracker_device.name, } self._hass.bus.async_fire(f"{DOMAIN}_lock_event", event_data) @@ -299,34 +372,53 @@ class GeoRideContext: ).result() break - @callback def on_device_callback(self, data): """on device callback""" _LOGGER.info("On device received") for coordoned_tracker in self._georide_trackers_coordoned: - tracker = coordoned_tracker['tracker_device'].tracker + tracker_device = coordoned_tracker['tracker_device'] + tracker = tracker_device.tracker coordinator = coordoned_tracker['coordinator'] if tracker.tracker_id == data['trackerId']: tracker.status = data['status'] - event_data = { - "device_id": tracker.tracker_id, - "device_name": tracker.tracker_name, + "device_id": tracker_device.unique_id, + "device_name": tracker_device.name, } self._hass.bus.async_fire(f"{DOMAIN}_device_event", event_data) - asyncio.run_coroutine_threadsafe( coordinator.async_request_refresh(), self._hass.loop ).result() break + @callback + def on_refresh_tracker_callback(self): + """on device callback""" + _LOGGER.info("On refresh tracker received") + self._previous_refresh = math.floor(time.time()/60) + self.force_refresh_trackers() + + for coordoned_tracker in self._georide_trackers_coordoned: + tracker_device = coordoned_tracker['tracker_device'] + tracker = tracker_device.tracker + coordinator = coordoned_tracker['coordinator'] + event_data = { + "device_id": tracker_device.unique_id, + "device_name": tracker_device.name, + } + self._hass.bus.async_fire(f"{DOMAIN}_refresh_tracker_event", event_data) + asyncio.run_coroutine_threadsafe( + coordinator.async_request_refresh(), self._hass.loop + ).result() @callback def on_alarm_callback(self, data): """on device callback""" _LOGGER.info("On alarm received") for coordoned_tracker in self._georide_trackers_coordoned: - tracker = coordoned_tracker['tracker_device'].tracker + tracker_device = coordoned_tracker['tracker_device'] + tracker = tracker_device.tracker + coordinator = coordoned_tracker['coordinator'] if tracker.tracker_id == data['trackerId']: if data['name'] == 'vibration': @@ -355,12 +447,13 @@ class GeoRideContext: _LOGGER.info("magnetOff detected") elif data['name'] == 'sonorAlarmOn': _LOGGER.info("sonorAlarmOn detected") + tracker.is_siren_on = True else: _LOGGER.warning("Unmanaged alarm: %s", data["name"]) event_data = { - "device_id": tracker.tracker_id, - "device_name": tracker.tracker_name, + "device_id": tracker_device.unique_id, + "device_name": tracker_device.name, "type": f"alarm_{data['name']}" } self._hass.bus.async_fire(f"{DOMAIN}_alarm_event", event_data) @@ -374,7 +467,8 @@ class GeoRideContext: """on position callback""" _LOGGER.info("On position received") for coordoned_tracker in self._georide_trackers_coordoned: - tracker = coordoned_tracker['tracker_device'].tracker + tracker_device = coordoned_tracker['tracker_device'] + tracker = tracker_device.tracker coordinator = coordoned_tracker['coordinator'] if tracker.tracker_id == data['trackerId']: tracker.latitude = data['latitude'] @@ -384,8 +478,8 @@ class GeoRideContext: tracker.fixtime = data['fixtime'] event_data = { - "device_id": tracker.tracker_id, - "device_name": tracker.tracker_name + "device_id": tracker_device.unique_id, + "device_name": tracker_device.name, } self._hass.bus.async_fire(f"{DOMAIN}_position_event", event_data) asyncio.run_coroutine_threadsafe( diff --git a/custom_components/georide/binary_sensor.py b/custom_components/georide/binary_sensor.py index a733420..da6660d 100644 --- a/custom_components/georide/binary_sensor.py +++ b/custom_components/georide/binary_sensor.py @@ -5,6 +5,7 @@ import logging from typing import Any, Mapping from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT from homeassistant.helpers.update_coordinator import ( @@ -14,14 +15,14 @@ from homeassistant.helpers.update_coordinator import ( from .const import DOMAIN as GEORIDE_DOMAIN -from .device import Device +from .device import Device, DeviceBeacon _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 """Set up GeoRide tracker based off an entry.""" georide_context = hass.data[GEORIDE_DOMAIN]["context"] entities = [] - coordoned_trackers = georide_context.get_coordoned_trackers() + coordoned_trackers = georide_context.georide_trackers_coordoned for coordoned_tracker in coordoned_trackers: tracker_device = coordoned_tracker['tracker_device'] coordinator = coordoned_tracker['coordinator'] @@ -34,6 +35,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d entities.append(GeoRideMovingBinarySensorEntity(coordinator, tracker_device)) hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator + + coordoned_beacons = georide_context.georide_trackers_beacon_coordoned + for coordoned_beacon in coordoned_beacons: + tracker_beacon = coordoned_beacon['tracker_beacon'] + coordinator = coordoned_beacon['coordinator'] + entities.append(GeoRideBeaconUpdatedBinarySensorEntity(coordinator, tracker_beacon)) + hass.data[GEORIDE_DOMAIN]["devices"][tracker_beacon.beacon.beacon_id] = coordinator + async_add_entities(entities, True) @@ -57,6 +66,26 @@ class GeoRideBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): """Return the device info.""" return self._tracker_device.device_info +class GeoRideBeaconBinarySensorEntity(CoordinatorEntity, BinarySensorEntity): + """Represent a tracked device.""" + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_device_beacon: DeviceBeacon): + """Set up Georide entity.""" + super().__init__(coordinator) + self._tracker_device_beacon = tracker_device_beacon + self._name = tracker_device_beacon.beacon.name + self.entity_id = f"{ENTITY_ID_FORMAT.format('binary_sensor')}.{tracker_device_beacon.beacon.beacon_id}"# pylint: disable=C0301 + self._is_on = False + + @property + def entity_category(self): + return None + + @property + def device_info(self): + """Return the device info.""" + return self._tracker_device_beacon.device_info + class GeoRideStolenBinarySensorEntity(GeoRideBinarySensorEntity): """Represent a tracked device.""" def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], @@ -83,7 +112,7 @@ class GeoRideStolenBinarySensorEntity(GeoRideBinarySensorEntity): @property def name(self): """ GeoRide odometer name """ - return f"{self._name} is stolen" + return f"{self._name} is not stolen" class GeoRideCrashedBinarySensorEntity(GeoRideBinarySensorEntity): @@ -113,7 +142,7 @@ class GeoRideCrashedBinarySensorEntity(GeoRideBinarySensorEntity): @property def name(self): """ GeoRide odometer name """ - return f"{self._name} is crashed" + return f"{self._name} is not crashed" class GeoRideActiveSubscriptionBinarySensorEntity(GeoRideBinarySensorEntity): """Represent a tracked device.""" @@ -124,6 +153,10 @@ class GeoRideActiveSubscriptionBinarySensorEntity(GeoRideBinarySensorEntity): super().__init__(coordinator, tracker_device) self.entity_id = f"{ENTITY_ID_FORMAT.format('is_active_subscription_')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + @property def unique_id(self): """Return the unique ID.""" @@ -132,9 +165,15 @@ class GeoRideActiveSubscriptionBinarySensorEntity(GeoRideBinarySensorEntity): @property def is_on(self): """state value property""" - if self._tracker_device.tracker.subscription_id is not None: - return True - return False + tracker = self._tracker_device.tracker + if tracker.is_oldsubscription: + if tracker.subscription_id is not None: + return True + return False + else: + if tracker.subscription is not None and tracker.subscription.subscription_id is not None: + return True + return False @property def name(self): @@ -176,6 +215,10 @@ class GeoRideNetworkBinarySensorEntity(GeoRideBinarySensorEntity): super().__init__(coordinator, tracker_device) self.entity_id = f"{ENTITY_ID_FORMAT.format('have_network')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + @property def unique_id(self): """Return the unique ID.""" @@ -207,6 +250,10 @@ class GeoRideMovingBinarySensorEntity(GeoRideBinarySensorEntity): super().__init__(coordinator, tracker_device) self.entity_id = f"{ENTITY_ID_FORMAT.format('moving')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + @property def unique_id(self): """Return the unique ID.""" @@ -226,3 +273,35 @@ class GeoRideMovingBinarySensorEntity(GeoRideBinarySensorEntity): def name(self): """ GeoRide name """ return f"{self._name} is moving" + +class GeoRideBeaconUpdatedBinarySensorEntity(GeoRideBeaconBinarySensorEntity): + """Represent a tracked device.""" + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_beacon_device: DeviceBeacon): + """Set up Georide entity.""" + super().__init__(coordinator, tracker_beacon_device) + self.entity_id = f"{ENTITY_ID_FORMAT.format('update')}.{tracker_beacon_device.beacon.beacon_id}"# pylint: disable=C0301 + + @property + def unique_id(self): + """Return the unique ID.""" + return f"update_{self._tracker_device_beacon.beacon.beacon_id}" + + @property + def device_class(self): + """Return the device class.""" + return "update" + + @property + def is_on(self): + """state value property""" + return not self._tracker_device_beacon.beacon.is_updated + + @property + def name(self): + """ GeoRide name """ + return f"{self._name} have an update" \ No newline at end of file diff --git a/custom_components/georide/const.py b/custom_components/georide/const.py index abe697b..03e11ee 100644 --- a/custom_components/georide/const.py +++ b/custom_components/georide/const.py @@ -10,4 +10,6 @@ TRACKER_ID = "trackerId" MIN_UNTIL_REFRESH = 10 +SIREN_ACTIVATION_DELAY = 30 + TOKEN_SAFE_DAY = 432000 # five days diff --git a/custom_components/georide/device.py b/custom_components/georide/device.py index b3137e9..a7276c4 100644 --- a/custom_components/georide/device.py +++ b/custom_components/georide/device.py @@ -1,5 +1,5 @@ """Home Assistant representation of an GeoRide Tracker device.""" -import georideapilib.objects as GeoRideTracker +from georideapilib.objects import GeoRideTracker, GeoRideTrackerBeacon from .const import DOMAIN as GEORIDE_DOMAIN @@ -28,11 +28,15 @@ class Device: @property def model_name(self) -> str: """Get the model name.""" - name = "GeoRide 1" - if self._tracker.is_old_tracker: - name = "Prototype / GeoRide 1" - elif self._tracker.is_second_gen: - name = "GeoRide 2 / GeoRide 3" + name = None + if self._tracker.version == 1: + name = "GeoRide 1" + elif self._tracker.version == 2: + name = "GeoRide 2" + elif self._tracker.version == 3: + name = "GeoRide 3" + else: + name = "Prototype / Unknown" return name @property @@ -40,7 +44,7 @@ class Device: """Return the device info.""" return { "name": self.name, - "identifiers": {(GEORIDE_DOMAIN, self._tracker.tracker_id)}, + "identifiers": self.unique_id, "manufacturer": "GeoRide", "model": self.model_name, "suggested_area": "Garage" @@ -54,4 +58,53 @@ class Device: def __str__(self) -> str: """Get string representation.""" - return f"GeoRide Device: {self.name}::{self.model_name}::self.unique_id" + return f"GeoRide Device: {self.name}::{self.model_name}::{self.unique_id}" + +class DeviceBeacon: + """Home Assistant representation of a GeoRide Tracker device.""" + + def __init__(self, beacon): + """Initialize GeoRideTracker device.""" + self._beacon: GeoRideTrackerBeacon = beacon + + @property + def beacon(self): + """return the tracker beacon""" + return self._beacon + + @property + def name(self) -> str: + """Get the name.""" + return self._beacon.name + + @property + def manufacturer(self) -> str: + """Get the manufacturer.""" + return "GeoRide" + + @property + def model_name(self) -> str: + """Get the model name.""" + name = "GeoRide Beacon" + return name + + @property + def device_info(self): + """Return the device info.""" + return { + "name": self.name, + "identifiers": self.unique_id, + "manufacturer": "GeoRide", + "model": self.model_name, + "suggested_area": "Garage" + } + + @property + def unique_id(self) -> str: + """Get the unique id.""" + + return {(GEORIDE_DOMAIN, self._beacon.beacon_id)} + + def __str__(self) -> str: + """Get string representation.""" + return f"GeoRide Device: {self.name}::{self.model_name}::{self.unique_id}" \ No newline at end of file diff --git a/custom_components/georide/device_tracker.py b/custom_components/georide/device_tracker.py index 8a5fc03..e945043 100644 --- a/custom_components/georide/device_tracker.py +++ b/custom_components/georide/device_tracker.py @@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 """Set up Georide tracker based off an entry.""" georide_context = hass.data[GEORIDE_DOMAIN]["context"] - coordoned_trackers = georide_context.get_coordoned_trackers() + coordoned_trackers = georide_context.georide_trackers_coordoned entities = [] for coordoned_tracker in coordoned_trackers: @@ -47,6 +47,10 @@ class GeoRideTrackerEntity(CoordinatorEntity, TrackerEntity): self.entity_id = DOMAIN + ".{}".format(tracker_device.tracker.tracker_id) self._hass = hass + @property + def entity_category(self): + return None + @property def unique_id(self): """Return the unique ID.""" diff --git a/custom_components/georide/manifest.json b/custom_components/georide/manifest.json index 800ea0d..3fc7bf1 100644 --- a/custom_components/georide/manifest.json +++ b/custom_components/georide/manifest.json @@ -6,10 +6,10 @@ "issue_tracker": "https://github.com/ptimatth/GeorideHA/issues", "iot_class": "cloud_polling", "requirements": [ - "georideapilib>=0.7.0", + "georideapilib>=0.8.4", "pyjwt==2.1.0" ], "dependencies": [], "codeowners": ["ptimatth"], - "version": "0.8.2" + "version": "0.9.0" } \ No newline at end of file diff --git a/custom_components/georide/sensor.py b/custom_components/georide/sensor.py index d02f732..1ad10a8 100644 --- a/custom_components/georide/sensor.py +++ b/custom_components/georide/sensor.py @@ -4,6 +4,7 @@ import logging from typing import Any, Mapping from homeassistant.core import callback +from homeassistant.helpers.entity import DeviceInfo, EntityCategory from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import ENTITY_ID_FORMAT from homeassistant.helpers.update_coordinator import ( @@ -12,7 +13,7 @@ from homeassistant.helpers.update_coordinator import ( ) from .const import DOMAIN as GEORIDE_DOMAIN -from .device import Device +from .device import Device, DeviceBeacon _LOGGER = logging.getLogger(__name__) @@ -21,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 """Set up GeoRide tracker based off an entry.""" georide_context = hass.data[GEORIDE_DOMAIN]["context"] - coordoned_trackers = georide_context.get_coordoned_trackers() + coordoned_trackers = georide_context.georide_trackers_coordoned entities = [] for coordoned_tracker in coordoned_trackers: @@ -30,9 +31,18 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator entities.append(GeoRideOdometerSensorEntity(coordinator, tracker_device, hass)) entities.append(GeoRideOdometerKmSensorEntity(coordinator, tracker_device, hass)) - entities.append(GeoRideInternalBatterySensorEntity(coordinator, tracker_device)) - entities.append(GeoRideExternalBatterySensorEntity(coordinator, tracker_device)) + entities.append(GeoRideSpeedSensorEntity(coordinator, tracker_device,hass)) entities.append(GeoRideFixtimeSensorEntity(coordinator, tracker_device)) + if tracker_device.tracker.version > 2: + entities.append(GeoRideInternalBatterySensorEntity(coordinator, tracker_device)) + entities.append(GeoRideExternalBatterySensorEntity(coordinator, tracker_device)) + + coordoned_beacons = georide_context.georide_trackers_beacon_coordoned + for coordoned_beacon in coordoned_beacons: + tracker_beacon = coordoned_beacon['tracker_beacon'] + coordinator = coordoned_beacon['coordinator'] + entities.append(GeoRideBeaconBatterySensorEntity(coordinator, tracker_beacon)) + hass.data[GEORIDE_DOMAIN]["devices"][tracker_beacon.beacon.beacon_id] = coordinator async_add_entities(entities) @@ -53,6 +63,10 @@ class GeoRideOdometerSensorEntity(CoordinatorEntity, SensorEntity): self._state = 0 self._hass = hass + @property + def entity_category(self): + return None + @property def unique_id(self): """Return the unique ID.""" @@ -80,7 +94,7 @@ class GeoRideOdometerSensorEntity(CoordinatorEntity, SensorEntity): return "mdi:counter" @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._tracker_device.device_info @@ -95,7 +109,6 @@ class GeoRideOdometerKmSensorEntity(CoordinatorEntity, SensorEntity): self._name = tracker_device.tracker.tracker_name self._unit_of_measurement = "km" self.entity_id = f"{ENTITY_ID_FORMAT.format('odometer_km')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 - self._state = 0 self._hass = hass @@ -126,12 +139,62 @@ class GeoRideOdometerKmSensorEntity(CoordinatorEntity, SensorEntity): return "mdi:counter" @property - def device_info(self): + def device_info(self) -> DeviceInfo: + """Return the device info.""" + return self._tracker_device.device_info + +class GeoRideSpeedSensorEntity(CoordinatorEntity, SensorEntity): + """Represent a tracked device.""" + + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_device:Device, hass): + """Set up GeoRide entity.""" + super().__init__(coordinator) + self._tracker_device = tracker_device + self._name = tracker_device.tracker.tracker_name + self._unit_of_measurement = "km/h" + self.entity_id = f"{ENTITY_ID_FORMAT.format('speed')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + self._state = 0 + self._hass = hass + self._state_class = "measurement" + + @property + def unique_id(self): + """Return the unique ID.""" + return f"speed_{self._tracker_device.tracker.tracker_id}" + + @property + def state(self): + """state property""" + return self._tracker_device.tracker.speed + + @property + def state_class(self): + return self._state_class + + @property + def unit_of_measurement(self): + """unit of mesurment property""" + return self._unit_of_measurement + + @property + def name(self): + """ GeoRide odometer name """ + return f"{self._name} speed" + + @property + def icon(self): + """icon getter""" + return "mdi:speedometer" + + @property + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._tracker_device.device_info class GeoRideInternalBatterySensorEntity(CoordinatorEntity, SensorEntity): """Represent a tracked device.""" + entity_category = EntityCategory.DIAGNOSTIC def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], tracker_device:Device): @@ -141,8 +204,21 @@ class GeoRideInternalBatterySensorEntity(CoordinatorEntity, SensorEntity): self._name = tracker_device.tracker.tracker_name self._unit_of_measurement = "V" self.entity_id = f"{ENTITY_ID_FORMAT.format('internal_battery_voltage')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 - self._state = 0 + self._state_class = "measurement" + self._device_class = "voltage" + + @property + def state_class(self): + return self._state_class + + @property + def device_class(self): + return self._device_class + + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC @property def unique_id(self): @@ -170,7 +246,7 @@ class GeoRideInternalBatterySensorEntity(CoordinatorEntity, SensorEntity): return "mdi:battery" @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._tracker_device.device_info @@ -185,8 +261,21 @@ class GeoRideExternalBatterySensorEntity(CoordinatorEntity, SensorEntity): self._name = tracker_device.tracker.tracker_name self._unit_of_measurement = "V" self.entity_id = f"{ENTITY_ID_FORMAT.format('external_battery_voltage')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 - self._state = 0 + self._state_class = "measurement" + self._device_class = "voltage" + + @property + def state_class(self): + return self._state_class + + @property + def device_class(self): + return self._device_class + + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC @property def unique_id(self): @@ -214,7 +303,7 @@ class GeoRideExternalBatterySensorEntity(CoordinatorEntity, SensorEntity): return "mdi:battery" @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._tracker_device.device_info @@ -228,10 +317,13 @@ class GeoRideFixtimeSensorEntity(CoordinatorEntity, SensorEntity): self._tracker_device = tracker_device self._name = tracker_device.tracker.tracker_name self.entity_id = f"{ENTITY_ID_FORMAT.format('fixtime')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 - self._state = 0 self._device_class = "timestamp" + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + @property def unique_id(self): """Return the unique ID.""" @@ -253,6 +345,53 @@ class GeoRideFixtimeSensorEntity(CoordinatorEntity, SensorEntity): return "mdi:map-clock" @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Return the device info.""" return self._tracker_device.device_info + +class GeoRideBeaconBatterySensorEntity(CoordinatorEntity, SensorEntity): + """Represent a tracked device.""" + + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_beacon: DeviceBeacon): + """Set up GeoRide entity.""" + super().__init__(coordinator) + self._tracker_device = tracker_beacon + self._name = tracker_beacon.beacon.name + self._unit_of_measurement = "%" + self.entity_id = f"{ENTITY_ID_FORMAT.format('battery_percent')}.{tracker_beacon.beacon.beacon_id}"# pylint: disable=C0301 + self._state = 0 + + @property + def entity_category(self): + return EntityCategory.DIAGNOSTIC + + @property + def unique_id(self): + """Return the unique ID.""" + return f"battery_percent_{self._tracker_device.beacon.beacon_id}" + + @property + def state(self): + """state property""" + return self._tracker_device.beacon.battery_level + + @property + def unit_of_measurement(self): + """unit of mesurment property""" + return self._unit_of_measurement + + @property + def name(self): + """ GeoRide internal_battery_voltage name """ + return f"{self._name} battery percent" + + @property + def icon(self): + """icon getter""" + return "mdi:battery" + + @property + def device_info(self) -> DeviceInfo: + """Return the device info.""" + return self._tracker_device.device_info \ No newline at end of file diff --git a/custom_components/georide/siren.py b/custom_components/georide/siren.py new file mode 100644 index 0000000..748f4fd --- /dev/null +++ b/custom_components/georide/siren.py @@ -0,0 +1,96 @@ +""" device tracker for GeoRide object """ + +import logging + + +from typing import Any, Mapping + +from homeassistant.components.siren import SirenEntity + +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) + +import georideapilib.api as GeoRideApi + +from .const import DOMAIN as GEORIDE_DOMAIN +from .device import Device + +ENTITY_ID_FORMAT = GEORIDE_DOMAIN + ".{}" + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 + """Set up GeoRide tracker based off an entry.""" + georide_context = hass.data[GEORIDE_DOMAIN]["context"] + coordoned_trackers = georide_context.georide_trackers_coordoned + + entities = [] + for coordoned_tracker in coordoned_trackers: + tracker_device = coordoned_tracker['tracker_device'] + coordinator = coordoned_tracker['coordinator'] + hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator + if tracker_device.tracker.version > 2: + entities.append(GeoRideSirenEntity(coordinator, tracker_device, hass)) + + async_add_entities(entities) + + return True + +class GeoRideSirenEntity(CoordinatorEntity, SirenEntity): + """Represent a tracked device.""" + + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_device:Device, hass): + """Set up GeoRide entity.""" + super().__init__(coordinator) + self._tracker_device = tracker_device + self._name = tracker_device.tracker.tracker_name + self.entity_id = f"{ENTITY_ID_FORMAT.format('eco_mode')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + self._hass = hass + + @property + def entity_category(self): + return None + + async def async_turn_on(self, **kwargs): + """ lock the GeoRide tracker """ + _LOGGER.info('async_turn_on eco %s', kwargs) + georide_context = self._hass.data[GEORIDE_DOMAIN]["context"] + token = await georide_context.get_token() + success = await self._hass.async_add_executor_job(GeoRideApi.change_tracker_siren_state, + token, self._tracker_device.tracker.tracker_id, True) + if success: + self._tracker_device.tracker.is_siren_on = True + + async def async_turn_off(self, **kwargs): + """ unlock the GeoRide tracker """ + _LOGGER.info('async_turn_off eco %s', kwargs) + georide_context = self._hass.data[GEORIDE_DOMAIN]["context"] + token = await georide_context.get_token() + success = await self._hass.async_add_executor_job(GeoRideApi.change_tracker_siren_state, + token, self._tracker_device.tracker.tracker_id, False) + if success: + self._tracker_device.tracker.is_siren_on = False + + @property + def unique_id(self): + """Return the unique ID.""" + return f"siren_{self._tracker_device.tracker.tracker_id}" + + @property + def name(self): + """ GeoRide odometer name """ + return f"{self._name} siren" + + @property + def is_on(self): + """ GeoRide switch status """ + return self._tracker_device.tracker.is_siren_on + + @property + def device_info(self): + """Return the device info.""" + return self._tracker_device.device_info \ No newline at end of file diff --git a/custom_components/georide/switch.py b/custom_components/georide/switch.py index 79f0a0b..0559264 100644 --- a/custom_components/georide/switch.py +++ b/custom_components/georide/switch.py @@ -24,17 +24,18 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 """Set up GeoRide tracker based off an entry.""" georide_context = hass.data[GEORIDE_DOMAIN]["context"] - coordoned_trackers = georide_context.get_coordoned_trackers() + coordoned_trackers = georide_context.georide_trackers_coordoned - lock_switch_entities = [] + entities = [] for coordoned_tracker in coordoned_trackers: tracker_device = coordoned_tracker['tracker_device'] coordinator = coordoned_tracker['coordinator'] - entity = GeoRideLockSwitchEntity(coordinator, tracker_device, hass) hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator - lock_switch_entities.append(entity) + entities.append(GeoRideLockSwitchEntity(coordinator, tracker_device, hass)) + if tracker_device.tracker.version > 2: + entities.append(GeoRideEcoModeSwitchEntity(coordinator, tracker_device, hass)) - async_add_entities(lock_switch_entities) + async_add_entities(entities) return True @@ -50,6 +51,10 @@ class GeoRideLockSwitchEntity(CoordinatorEntity, SwitchEntity): self.entity_id = f"{ENTITY_ID_FORMAT.format('lock')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 self._hass = hass + @property + def entity_category(self): + return None + async def async_turn_on(self, **kwargs): """ lock the GeoRide tracker """ _LOGGER.info('async_turn_on %s', kwargs) @@ -105,3 +110,66 @@ class GeoRideLockSwitchEntity(CoordinatorEntity, SwitchEntity): def device_info(self): """Return the device info.""" return self._tracker_device.device_info + +class GeoRideEcoModeSwitchEntity(CoordinatorEntity, SwitchEntity): + """Represent a tracked device.""" + + def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]], + tracker_device:Device, hass): + """Set up GeoRide entity.""" + super().__init__(coordinator) + self._tracker_device = tracker_device + self._name = tracker_device.tracker.tracker_name + self.entity_id = f"{ENTITY_ID_FORMAT.format('eco_mode')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301 + self._hass = hass + + @property + def entity_category(self): + return None + + async def async_turn_on(self, **kwargs): + """ lock the GeoRide tracker """ + _LOGGER.info('async_turn_on eco %s', kwargs) + georide_context = self._hass.data[GEORIDE_DOMAIN]["context"] + token = await georide_context.get_token() + success = await self._hass.async_add_executor_job(GeoRideApi.change_tracker_eco_mode_state, + token, self._tracker_device.tracker.tracker_id, True) + if success: + self._tracker_device.tracker.is_in_eco = True + + async def async_turn_off(self, **kwargs): + """ unlock the GeoRide tracker """ + _LOGGER.info('async_turn_off eco %s', kwargs) + georide_context = self._hass.data[GEORIDE_DOMAIN]["context"] + token = await georide_context.get_token() + success = await self._hass.async_add_executor_job(GeoRideApi.change_tracker_eco_mode_state, + token, self._tracker_device.tracker.tracker_id, False) + if success: + self._tracker_device.tracker.is_in_eco = False + + @property + def unique_id(self): + """Return the unique ID.""" + return f"eco_mode_{self._tracker_device.tracker.tracker_id}" + + @property + def name(self): + """ GeoRide odometer name """ + return f"{self._name} eco mode" + + @property + def is_on(self): + """ GeoRide switch status """ + return self._tracker_device.tracker.is_in_eco + + @property + def icon(self): + """return the entity icon""" + if self._tracker_device.tracker.is_in_eco: + return "mdi:battery-heart-variant" + return "mdi:battery" + + @property + def device_info(self): + """Return the device info.""" + return self._tracker_device.device_info \ No newline at end of file