Compare commits

..

38 Commits
0.2.0 ... 0.6.0

Author SHA1 Message Date
b84a4cfd7f Shipping v0.6.0 2021-04-11 17:27:23 +02:00
5c77acbdb7 Fire more home assistant event 2021-04-11 17:22:42 +02:00
5e83ed1c68 Fix small mistakes 2021-04-11 17:00:33 +02:00
8a44d22d41 Migreate to 0.6.0 and new device architecture 2021-04-11 13:38:15 +02:00
56a8a35479 Fix old Swirhc entity to odometer 2021-04-11 01:31:00 +02:00
7d25405c2f Use coordinator to imporoo reactivituy 2021-04-11 01:07:31 +02:00
dbbe1f33b9 Add space betteween useless refresh 2021-04-11 00:23:25 +02:00
660aaa0180 Fix switch response 2021-04-11 00:05:52 +02:00
0113102a7f Centralize update loop 2021-04-10 23:51:51 +02:00
093bebc391 Trying to refactor the tracker entity_status 2021-04-10 23:08:52 +02:00
ec63a36c81 Change tracker update mangment 2021-04-10 20:13:49 +02:00
283a1074e1 Move connect_socket into the context 2021-04-10 16:01:32 +02:00
5cab811c85 Fix binnary sensors 2021-04-10 13:57:23 +02:00
e09f8eb7e7 Add new sensor 2021-04-10 13:10:30 +02:00
be4e68e152 Add version in manifet, and add_executor_job on config_flow 2021-04-10 12:54:42 +02:00
bcaa4bf957 Add hass object on entity items 2021-04-10 12:43:43 +02:00
af1df1cd84 Fix tracker retrivial 2021-04-10 12:25:47 +02:00
b4871b9374 Fix param gived to async_add_executor_job 2021-04-10 12:09:49 +02:00
f72481236d Add get_tracker to await 2021-04-10 12:05:38 +02:00
1e21de655f Move all external call to asyn/await 2021-04-10 11:58:58 +02:00
3c6f39f7ba Add async for refresh_tken 2021-04-10 11:34:43 +02:00
02e1656aab Merge branch 'develop' 2020-12-01 19:24:26 +01:00
4593c694d5 GeoRide wording update 2020-05-26 22:39:09 +02:00
9b651f933e Shipping v0.5.0 2020-05-23 15:07:27 +02:00
a8274f7fd1 Update to prepare new home assisntant version (0.111) 2020-05-23 15:01:52 +02:00
ef783db79b HotFix deveice_tracker.py du to HA Update 2020-03-28 23:59:38 +01:00
18de851e4d Merge pull request #2 from limeryls/fix_login
Fix setting login credentials using config_flow and fix mistakes in spelling
2020-03-28 17:04:19 +01:00
Kévin SIMON-QUESNEL
cbed2cebb3 Fix setting login credentials using config_flow and fix mistakes in spelling 2020-03-28 04:38:52 +01:00
e5bd90ae6f Shipping v0.4.1 2019-11-11 13:37:28 +01:00
a2e65694fc Increase interval to refresh each 10 minutes 2019-11-11 13:33:17 +01:00
66bd335a8e Add component to default HACS 2019-11-09 10:04:47 +01:00
14300c5278 Add polling off all tracker each hours 2019-11-09 09:11:56 +01:00
bc3da8f0e5 Shipping v0.4.0 2019-11-05 22:01:46 +01:00
ecf2a23c70 Fix socket thread launch after first time 2019-11-05 21:52:07 +01:00
991370fcec index on master: 7a80670 Add better polling 2019-11-03 19:15:58 +01:00
649c97b8bb Shipping v0.3.0 2019-11-02 21:49:31 +01:00
9ffc8de8a7 Add odometter sensor 2019-11-02 21:49:04 +01:00
9399c8e11a Add login failed treatment and renew token 2019-11-02 19:55:41 +01:00
15 changed files with 782 additions and 307 deletions

View File

@@ -1,8 +1,32 @@
# Georide Home assistant # GeoRide Home assistant
![Logo Georide](georide-logo.png) ![Logo GeoRide](https://brands.home-assistant.io/georide/logo.png)
⚠️ this is not an official implementation ⚠️ This is not an official implementation
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg?style=for-the-badge)](https://www.gnu.org/licenses/gpl-3.0)
Official georide website: https://georide.fr/ Official GeoRide website: https://georide.fr/
## Description
This component add some sensor for GeoRide Tracker
## Options
| Name | Type | Requirement | `default` Description
| ---- | ---- | ------- | -----------
| email | string | **Required** | GeoRide email
| password | string | **Required** | GeoRide password
## Installation
### Option 1
- Just folow the integration config steps.
### Option 2
- Add the folowing line in your configuration.yml
```yaml
georide:
email: <your-email>@exmple.com
password: <your-password>
```

View File

@@ -1,23 +0,0 @@
{
"config": {
"title": "GeoRide",
"step": {
"user": {
"title": "Set up GeoRide",
"description": "Are you sure you want to set up GeoRide ?",
"data": {
"email": "email",
"password": "password"
}
}
},
"abort": {
"one_instance_allowed": "Only a single instance is allowed.",
"no_credential": "You need to add your credentails."
},
"create_entry": {
"default": "\n\nLogin succes"
}
}
}

View File

@@ -1,18 +1,22 @@
""" georide custom conpennt """ """ georide custom conpennt """
from collections import defaultdict from collections import defaultdict
import asyncio
import logging import logging
from typing import Any, Mapping
from datetime import timedelta from datetime import timedelta
import voluptuous as vol import math
import time
import json import json
import jwt
from threading import Thread from threading import Thread
import voluptuous as vol
import jwt
from aiohttp.web import json_response from aiohttp.web import json_response
from georideapilib.objects import GeorideAccount from georideapilib.objects import GeoRideAccount
import georideapilib.api as GeorideApi import georideapilib.api as GeoRideApi
from georideapilib.socket import GeorideSocket
from georideapilib.socket import GeoRideSocket
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_WEBHOOK_ID from homeassistant.const import CONF_WEBHOOK_ID
@@ -21,14 +25,25 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.event as ha_event import homeassistant.helpers.event as ha_event
from homeassistant.setup import async_when_setup from homeassistant.setup import async_when_setup
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .device import Device
from .const import ( from .const import (
CONF_EMAIL, CONF_EMAIL,
CONF_PASSWORD, CONF_PASSWORD,
TRACKER_ID CONF_TOKEN,
TRACKER_ID,
TOKEN_SAFE_DAY,
MIN_UNTIL_REFRESH,
DOMAIN
) )
DOMAIN = "georide"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -45,89 +60,84 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass, config): async def async_setup(hass, config):
"""Setup Georide component.""" """Setup GeoRide component."""
hass.data[DOMAIN] = {"config": config[DOMAIN], "devices": {}, "unsub": None} hass.data[DOMAIN] = {"config": config[DOMAIN], "devices": {}, "unsub": None}
result = await hass.config_entries.flow.async_init( hass.async_create_task(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={} hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_IMPORT
},
data={}
)
) )
_LOGGER.info("Georide-setup success: %s", result)
# Return boolean to indicate that initialization was successful. # Return boolean to indicate that initialization was successful.
return True return True
def connect_socket(hass, component):
"""subscribe to georide socket"""
context = hass.data[DOMAIN]["context"]
socket = GeorideSocket()
socket.subscribe_locked(context.on_lock_callback)
socket.subscribe_device(context.on_device_callback)
socket.subscribe_position(context.on_position_callback)
socket.init()
socket.connect(context.async_get_token())
async def async_setup_entry(hass, entry): async def async_setup_entry(hass, entry):
"""Set up Georide entry.""" """Set up GeoRide entry."""
config = hass.data[DOMAIN]["config"] config = hass.data[DOMAIN]["config"]
email = config.get(CONF_EMAIL) or entry.data[CONF_EMAIL] email = config.get(CONF_EMAIL) or entry.data[CONF_EMAIL]
password = config.get(CONF_PASSWORD) or entry.data[CONF_PASSWORD] password = config.get(CONF_PASSWORD) or entry.data[CONF_PASSWORD]
token = config.get(CONF_TOKEN) or entry.data[CONF_TOKEN]
context = GeoRideContext(
hass,
email,
password,
token
)
if email is None or password is None: _LOGGER.info("Context-setup and start the thread")
return False _LOGGER.info("Thread started")
try:
account = GeorideApi.get_authorisation_token(email, password)
context = GeorideContext(
hass,
email,
password,
account.auth_token
)
hass.data[DOMAIN]["context"] = context
hass.data[DOMAIN]["context"] = context # We add trackers to the context
await context.init_context(hass)
# We add trackers to the context hass.async_create_task(
trackers = GeorideApi.get_trackers(account.auth_token) hass.config_entries.async_forward_entry_setup(entry, "device_tracker"))
context.georide_trackers = trackers hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch"))
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "device_tracker")) hass.config_entries.async_forward_entry_setup(entry, "sensor"))
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch")) hass.config_entries.async_forward_entry_setup(entry, "binary_sensor"))
thread = Thread(target=connect_socket, args=(hass, entry))
thread.start()
except:
return False
return True return True
async def async_unload_entry(hass, entry): async def async_unload_entry(hass, entry):
"""Unload an Georide config entry.""" """Unload an GeoRide config entry."""
await hass.config_entries.async_forward_entry_unload(entry, "device_tracker") await hass.config_entries.async_forward_entry_unload(entry, "device_tracker")
await hass.config_entries.async_forward_entry_unload(entry, "switch") 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")
hass.data[DOMAIN]["unsub"]()
context = hass.data[DOMAIN]["context"]
context.socket.disconnect()
return True return True
class GeorideContext:
"""Hold the current Georide context.""" class GeoRideContext:
"""Hold the current GeoRide context."""
def __init__(self, hass, email, password, token): def __init__(self, hass, email, password, token):
"""Initialize an Georide context.""" """Initialize an GeoRide context."""
self._hass = hass self._hass = hass
self._email = email self._email = email
self._password = password self._password = password
self._georide_trackers = defaultdict(set) self._georide_trackers_coordoned = []
self._georide_trackers = []
self._token = token self._token = token
self._pending_msg = [] self._socket = None
self._thread_started = False
self._previous_refresh = math.floor(time.time()/60)
@property @property
def hass(self): def hass(self):
""" hass """ """ hass """
@@ -137,7 +147,7 @@ class GeorideContext:
def email(self): def email(self):
""" current email """ """ current email """
return self._email return self._email
@property @property
def password(self): def password(self):
""" password """ """ password """
@@ -150,68 +160,223 @@ class GeorideContext:
@property @property
def georide_trackers(self): def georide_trackers(self):
""" georide tracker list """ """ GeoRide tracker list """
return self._georide_trackers return self._georide_trackers
@georide_trackers.setter @georide_trackers.setter
def georide_trackers(self, trackers): def georide_trackers(self, trackers):
""" georide tracker list """ """ GeoRide tracker list """
self._georide_trackers = trackers self._georide_trackers = trackers
@callback async def connect_socket(self):
def async_get_token(self): """subscribe to GeoRide socket"""
""" here we return the current valid tocken, TODO: add here token expiration control""" _LOGGER.info("GeoRide socket connexion")
socket = GeoRideSocket()
socket.subscribe_locked(self.on_lock_callback)
socket.subscribe_device(self.on_device_callback)
socket.subscribe_position(self.on_position_callback)
socket.subscribe_alarm(self.on_alarm_callback)
self._socket = socket
socket.init()
self._hass.async_add_executor_job(socket.connect, await self.get_token())
async def get_token(self):
""" here we return the current valid tocken """
jwt_data = jwt.decode(self._token, verify=False) jwt_data = jwt.decode(self._token, verify=False)
exp_timestamp = jwt_data['exp'] exp_timestamp = jwt_data['exp']
epoch = math.ceil(time.time())
if (exp_timestamp - TOKEN_SAFE_DAY) < epoch:
_LOGGER.info("Time reached, renew token")
account = await self._hass.async_add_executor_job(GeoRideApi.get_authorisation_token,
self._email, self._password)
config = self._hass.data[DOMAIN]["config"]
config[CONF_TOKEN] = account.auth_token
self._token = account.auth_token
_LOGGER.info("Token exp data: %s", exp_timestamp) _LOGGER.info("Token exp data: %s", exp_timestamp)
return self._token return self._token
@callback async def get_tracker(self, tracker_id):
def async_get_tracker(self, tracker_id):
""" here we return last tracker by id""" """ here we return last tracker by id"""
await self.refresh_trackers()
for tracker in self._georide_trackers: for tracker in self._georide_trackers:
if tracker.tracker_id == tracker_id: if tracker.tracker_id == tracker_id:
return tracker return tracker
return None return {}
@callback async def refresh_trackers(self):
def async_see(self, **data): """ here we return last tracker by id"""
"""Send a see message to the device tracker.""" _LOGGER.debug("Call refresh tracker")
_LOGGER.info("sync_see") epoch_min = math.floor(time.time()/60)
self._pending_msg.append(data) #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")
async def force_refresh_trackers(self):
"""Used to refresh the tracker list"""
_LOGGER.info("Tracker list refresh")
new_georide_trackers = await self._hass.async_add_executor_job(GeoRideApi.get_trackers,
await self.get_token())
for refreshed_tracker in new_georide_trackers:
found = False
for tracker in self._georide_trackers:
if tracker.tracker_id == refreshed_tracker.tracker_id:
tracker.update_all_data(refreshed_tracker)
found = True
if not found:
self._georide_trackers.append(refreshed_tracker)
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 init_context(self, hass):
"""Used to refresh the tracker list"""
_LOGGER.info("Init_context")
await self.force_refresh_trackers()
update_interval = timedelta(minutes=MIN_UNTIL_REFRESH)
for tracker in self._georide_trackers:
coordinator = DataUpdateCoordinator[Mapping[str, Any]](
hass,
_LOGGER,
name=tracker.tracker_name,
update_method=self.refresh_trackers,
update_interval=update_interval
)
self._georide_trackers_coordoned.append({
"tracker_device": Device(tracker),
"coordinator": coordinator
})
def get_coordoned_trackers(self):
"""Return coordoned trackers"""
return self._georide_trackers_coordoned
@property
def socket(self):
""" hold the GeoRide socket """
return self._socket
@socket.setter
def socket(self, socket):
"""set the GeoRide socket"""
self._socket = socket
@callback @callback
def on_lock_callback(self, data): def on_lock_callback(self, data):
"""on lock callback""" """on lock callback"""
_LOGGER.info("On lock received") _LOGGER.info("On lock received")
for tracker in self._georide_trackers: for coordoned_tracker in self._georide_trackers_coordoned:
tracker = coordoned_tracker['tracker_device'].tracker
coordinator = coordoned_tracker['coordinator']
if tracker.tracker_id == data['trackerId']: if tracker.tracker_id == data['trackerId']:
tracker.locked_latitude = data['lockedLatitude'] tracker.locked_latitude = data['lockedLatitude']
tracker.locked_longitude = data['lockedLongitude'] tracker.locked_longitude = data['lockedLongitude']
tracker.is_locked = data['isLocked'] tracker.is_locked = data['isLocked']
return
event_data = {
"device_id": tracker.tracker_id,
"type": "on_lock",
}
self._hass.bus.async_fire(f"{DOMAIN}_event", event_data)
asyncio.run_coroutine_threadsafe(
coordinator.async_request_refresh(), self._hass.loop
).result()
break
@callback @callback
def on_device_callback(self, data): def on_device_callback(self, data):
"""on device callback""" """on device callback"""
_LOGGER.info("On device received") _LOGGER.info("On device received")
for tracker in self._georide_trackers: for coordoned_tracker in self._georide_trackers_coordoned:
tracker = coordoned_tracker['tracker_device'].tracker
coordinator = coordoned_tracker['coordinator']
if tracker.tracker_id == data['trackerId']: if tracker.tracker_id == data['trackerId']:
tracker.status = data['status'] tracker.status = data['status']
return
event_data = {
"device_id": tracker.tracker_id,
"type": "on_device",
}
self._hass.bus.async_fire(f"{DOMAIN}_event", event_data)
asyncio.run_coroutine_threadsafe(
coordinator.async_request_refresh(), self._hass.loop
).result()
break
@callback @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
coordinator = coordoned_tracker['coordinator']
if tracker.tracker_id == data['trackerId']:
if data.name == 'vibration':
_LOGGER.info("Vibration detected")
elif data.name == 'exitZone':
_LOGGER.info("Exit zone detected")
elif data.name == 'crash':
_LOGGER.info("Crash detected")
elif data.name == 'crashParking':
_LOGGER.info("Crash parking detected")
elif data.name == 'deviceOffline':
_LOGGER.info("Device offline detected")
elif data.name == 'deviceOnline':
_LOGGER.info("Device online detected")
elif data.name == 'powerCut':
_LOGGER.info("powerCut detected")
else:
_LOGGER.warning("Unamanged alarm: %s", data.name)
event_data = {
"device_id": tracker.tracker_id,
"type": f"alarm_{data.name}",
}
self._hass.bus.async_fire(f"{DOMAIN}_event", event_data)
asyncio.run_coroutine_threadsafe(
coordinator.async_request_refresh(), self._hass.loop
).result()
break
@callback
def on_position_callback(self, data): def on_position_callback(self, data):
"""on position callback""" """on position callback"""
_LOGGER.info("On position received") _LOGGER.info("On position received")
for tracker in self._georide_trackers: for coordoned_tracker in self._georide_trackers_coordoned:
tracker = coordoned_tracker['tracker_device'].tracker
coordinator = coordoned_tracker['coordinator']
if tracker.tracker_id == data['trackerId']: if tracker.tracker_id == data['trackerId']:
tracker.latitude = data['latitude'] tracker.latitude = data['latitude']
tracker.longitude = data['longitude'] tracker.longitude = data['longitude']
tracker.moving = data['moving'] tracker.moving = data['moving']
tracker.speed = data['speed'] tracker.speed = data['speed']
tracker.fixtime = data['fixtime'] tracker.fixtime = data['fixtime']
return
event_data = {
"device_id": tracker.tracker_id,
"type": "position",
}
self._hass.bus.async_fire(f"{DOMAIN}_event", event_data)
asyncio.run_coroutine_threadsafe(
coordinator.async_request_refresh(), self._hass.loop
).result()
break

View File

@@ -0,0 +1,157 @@
""" binary_sensor for GeoRide object """
import logging
from typing import Any, Mapping
from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator
)
from .const import DOMAIN as GEORIDE_DOMAIN
from .device import Device
_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()
for coordoned_tracker in coordoned_trackers:
tracker_device = coordoned_tracker['tracker_device']
coordinator = coordoned_tracker['coordinator']
entities.append(GeoRideStolenBinarySensorEntity(coordinator, tracker_device))
entities.append(GeoRideCrashedBinarySensorEntity(coordinator, tracker_device))
entities.append(GeoRideOwnerBinarySensorEntity(coordinator, tracker_device))
entities.append(GeoRideActiveSubscriptionBinarySensorEntity(coordinator, tracker_device))
hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator
async_add_entities(entities, True)
return True
class GeoRideBinarySensorEntity(CoordinatorEntity, BinarySensorEntity):
"""Represent a tracked device."""
def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
tracker_device: Device):
"""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('binary_sensor')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
self._is_on = False
@property
def device_info(self):
"""Return the device info."""
return self._tracker_device.device_info
class GeoRideStolenBinarySensorEntity(GeoRideBinarySensorEntity):
"""Represent a tracked device."""
def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
tracker_device: Device):
"""Set up Georide entity."""
super().__init__(coordinator, tracker_device)
self.entity_id = f"{ENTITY_ID_FORMAT.format('is_stolen')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
@property
def unique_id(self):
"""Return the unique ID."""
return f"is_stolen_{self._tracker_device.tracker.tracker_id}"
@property
def is_on(self):
"""state value property"""
return self._tracker_device.tracker.is_stolen
@property
def name(self):
""" GeoRide odometer name """
return f"{self._name} is stolen"
class GeoRideCrashedBinarySensorEntity(GeoRideBinarySensorEntity):
"""Represent a tracked device."""
def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
tracker_device: Device):
"""Set up Georide entity."""
super().__init__(coordinator, tracker_device)
self.entity_id = f"{ENTITY_ID_FORMAT.format('is_crashed')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
@property
def unique_id(self):
"""Return the unique ID."""
return f"is_crashed_{self._tracker_device.tracker.tracker_id}"
@property
def is_on(self):
"""state value property"""
return self._tracker_device.tracker.is_crashed
@property
def name(self):
""" GeoRide odometer name """
return f"{self._name} is crashed"
class GeoRideActiveSubscriptionBinarySensorEntity(GeoRideBinarySensorEntity):
"""Represent a tracked device."""
def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
tracker_device: Device):
"""Set up Georide entity."""
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 unique_id(self):
"""Return the unique ID."""
return f"is_active_subscription_{self._tracker_device.tracker.tracker_id}"
@property
def is_on(self):
"""state value property"""
if self._tracker_device.tracker.subscription_id is not None:
return True
return False
@property
def name(self):
""" GeoRide odometer name """
return f"{self._name} has an active subscription"
class GeoRideOwnerBinarySensorEntity(GeoRideBinarySensorEntity):
"""Represent a tracked device."""
def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
tracker_device: Device):
"""Set up Georide entity."""
super().__init__(coordinator, tracker_device)
self.entity_id = f"{ENTITY_ID_FORMAT.format('is_owner')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
@property
def unique_id(self):
"""Return the unique ID."""
return f"is_owner_{self._tracker_device.tracker.tracker_id}"
@property
def is_on(self):
"""state value property"""
if self._tracker_device.tracker.role == "owner":
return True
return False
@property
def name(self):
""" GeoRide odometer name """
return f"{self._name} is own tracker"

View File

@@ -3,58 +3,70 @@
import logging import logging
from homeassistant import config_entries from homeassistant import config_entries
import voluptuous as vol import voluptuous as vol
import georideapilib.api as GeoRideApi
import georideapilib.exception as GeoRideException
from .const import CONF_EMAIL, CONF_PASSWORD from .const import CONF_EMAIL, CONF_PASSWORD, CONF_TOKEN, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@config_entries.HANDLERS.register("georide") @config_entries.HANDLERS.register("georide")
class GeorideConfigFlow(config_entries.ConfigFlow): class GeoRideConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Georide config flow """ """GeoRide config flow """
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None): #pylint: disable=W0613
""" handle info to help to configure georide """ """ handle info to help to configure GeoRide """
if self._async_current_entries(): if self._async_current_entries():
return self.async_abort(reason="one_instance_allowed") return self.async_abort(reason="one_instance_allowed")
if user_input is None: return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({
return self.async_show_form(step_id='user', data_schema=vol.Schema({ vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)),
vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), vol.Required(CONF_PASSWORD): vol.All(str)
vol.Required(CONF_PASSWORD): vol.All(str) }))
}))
return self.async_create_entry( async def async_step_import(self, user_input=None): #pylint: disable=W0613
title=user_input[CONF_EMAIL],
data={
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD]
}
)
async def async_step_import(self, user_input):
"""Import a config flow from configuration.""" """Import a config flow from configuration."""
_LOGGER.info("user email: %", str(user_input))
if self._async_current_entries(): if self._async_current_entries():
return self.async_abort(reason="one_instance_allowed") return self.async_abort(reason="one_instance_allowed")
if user_input is None: return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({
return self.async_show_form(step_id='user', data_schema=vol.Schema({ vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)),
vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)), vol.Required(CONF_PASSWORD): vol.All(str)
vol.Required(CONF_PASSWORD): vol.All(str) }))
}))
return self.async_create_entry(
title=user_input,
data={ async def async_step_georide_login(self, user_input):
CONF_EMAIL: user_input[CONF_EMAIL], """ try to seupt GeoRide Account """
CONF_PASSWORD: user_input[CONF_PASSWORD]
schema = vol.Schema({
vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)),
vol.Required(CONF_PASSWORD): vol.All(str)
})
if user_input is None:
return self.async_show_form(step_id='georide_login', data_schema=schema)
email = user_input[CONF_EMAIL]
password = user_input[CONF_PASSWORD]
try:
account = await self.hass.async_add_executor_job(GeoRideApi.get_authorisation_token, email, password)
data = {
CONF_EMAIL: email,
CONF_PASSWORD: password,
CONF_TOKEN: account.auth_token
} }
) return self.async_create_entry(title=email, data=data)
except (GeoRideException.SeverException, GeoRideException.LoginException):
_LOGGER.error("Invalid credentials provided, config not created")
errors = {"base": "faulty_credentials"}
return self.async_show_form(step_id="georide_login", data_schema=schema, errors=errors)
except:
_LOGGER.error("Unknown error")
errors = {"base": "unkonwn"}
return self.async_show_form(step_id="georide_login", data_schema=schema, errors=errors)

View File

@@ -1,4 +1,5 @@
""" Georide const file """ """ GeoRide const file """
DOMAIN = "georide"
CONF_EMAIL = "email" CONF_EMAIL = "email"
CONF_PASSWORD = "password" CONF_PASSWORD = "password"
@@ -6,3 +7,7 @@ CONF_PASSWORD = "password"
CONF_TOKEN = "token" CONF_TOKEN = "token"
TRACKER_ID = "trackerId" TRACKER_ID = "trackerId"
MIN_UNTIL_REFRESH = 10
TOKEN_SAFE_DAY = 432000 # five days

View File

@@ -0,0 +1,57 @@
"""Home Assistant representation of an GeoRide Tracker device."""
import georideapilib.objects as GeoRideTracker
from .const import DOMAIN as GEORIDE_DOMAIN
class Device:
"""Home Assistant representation of a GeoRide Tracker device."""
def __init__(self, tracker):
"""Initialize GeoRideTracker device."""
self._tracker: GeoRideTracker = tracker
@property
def tracker(self):
"""return the tracker"""
return self._tracker
@property
def name(self) -> str:
"""Get the name."""
return self._tracker.tracker_name
@property
def manufacturer(self) -> str:
"""Get the manufacturer."""
return "GeoRide"
@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"
return name
@property
def device_info(self):
"""Return the device info."""
return {
"name": self.name,
"identifiers": {(GEORIDE_DOMAIN, self._tracker.tracker_id)},
"manufacturer": "GeoRide",
"model": self.model_name,
"suggested_area": "Garage"
}
@property
def unique_id(self) -> str:
"""Get the unique id."""
return {(GEORIDE_DOMAIN, self._tracker.tracker_id)}
def __str__(self) -> str:
"""Get string representation."""
return f"GeoRide Device: {self.name}::{self.model_name}::self.unique_id"

View File

@@ -1,87 +1,81 @@
""" device tracker for Georide object """ """ device tracker for GeoRide object """
import logging import logging
from typing import Any, Mapping
from homeassistant.core import callback from homeassistant.components.device_tracker.const import DOMAIN, SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.const import ENTITY_ID_FORMAT, SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.config_entry import TrackerEntity from homeassistant.components.device_tracker.config_entry import TrackerEntity
import georideapilib.api as GeorideApi from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from . import DOMAIN as GEORIDE_DOMAIN from .device import Device
from .const import DOMAIN as GEORIDE_DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613
"""Set up Georide tracker based off an entry.""" """Set up Georide tracker based off an entry."""
georide_context = hass.data[GEORIDE_DOMAIN]["context"] georide_context = hass.data[GEORIDE_DOMAIN]["context"]
coordoned_trackers = georide_context.get_coordoned_trackers()
if georide_context.token is None:
return False
_LOGGER.info('Current georide token: %s', georide_context.async_get_token()) entities = []
for coordoned_tracker in coordoned_trackers:
tracker_device = coordoned_tracker['tracker_device']
coordinator = coordoned_tracker['coordinator']
entity = GeoRideTrackerEntity(coordinator, tracker_device, hass)
hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator
entities.append(entity)
async_add_entities(entities)
trackers = GeorideApi.get_trackers(georide_context.async_get_token())
tracker_entities = []
for tracker in trackers:
entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.async_get_token,
georide_context.async_get_tracker, tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
tracker_entities.append(entity)
async_add_entities(tracker_entities)
return True return True
class GeorideTrackerEntity(TrackerEntity): class GeoRideTrackerEntity(CoordinatorEntity, TrackerEntity):
"""Represent a tracked device.""" """Represent a tracked device."""
def __init__(self, tracker_id, get_token_callback, get_tracker_callback, tracker): def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
"""Set up Georide entity.""" tracker_device: Device, hass):
self._tracker_id = tracker_id """Set up GeoRide entity."""
self._get_token_callback = get_token_callback super().__init__(coordinator)
self._get_tracker_callback = get_tracker_callback self._name = tracker_device.tracker.tracker_name
self._name = tracker.tracker_name self._tracker_device = tracker_device
self._data = tracker or {} self.entity_id = DOMAIN + ".{}".format(tracker_device.tracker.tracker_id)
self.entity_id = ENTITY_ID_FORMAT.format(tracker_id) self._hass = hass
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique ID.""" """Return the unique ID."""
return self._tracker_id return f"georide_tracker_{self._tracker_device.tracker.tracker_id}"
@property @property
def name(self): def name(self):
return self._name """ GeoRide odometer name """
return f"{self._name} position"
@property @property
def latitude(self): def latitude(self):
"""Return latitude value of the device.""" """Return latitude value of the device."""
if self._data.latitude: if self._tracker_device.tracker.latitude:
return self._data.latitude return self._tracker_device.tracker.latitude
return None return None
@property @property
def longitude(self): def longitude(self):
"""Return longitude value of the device.""" """Return longitude value of the device."""
if self._data.longitude: if self._tracker_device.tracker.longitude:
return self._data.longitude return self._tracker_device.tracker.longitude
return None return None
@property @property
def source_type(self): def source_type(self):
"""Return the source type, eg gps or router, of the device.""" """Return the source type, eg gps or router, of the device."""
return SOURCE_TYPE_GPS return SOURCE_TYPE_GPS
@property @property
def location_accuracy(self): def location_accuracy(self):
""" return the gps accuracy of georide (could not be aquired, then 10) """ """ return the gps accuracy of georide (could not be aquired, then 10) """
@@ -89,39 +83,10 @@ class GeorideTrackerEntity(TrackerEntity):
@property @property
def icon(self): def icon(self):
"""return the entity icon"""
return "mdi:map-marker" return "mdi:map-marker"
@property @property
def device_info(self): def device_info(self):
"""Return the device info.""" """Return the device info."""
return { return self._tracker_device.device_info
"name": self.name,
"identifiers": {(GEORIDE_DOMAIN, self._tracker_id)},
"manufacturer": "GeoRide",
"odometer": "{} km".format(self._data.odometer)
}
@property
def get_tracker_callback(self):
""" get tracker callaback"""
return self._get_tracker_callback
@property
def get_token_callback(self):
""" get token callaback"""
return self._get_token_callback
@property
def should_poll(self):
"""No polling needed."""
return True
async def async_update(self):
""" update the current tracker"""
_LOGGER.info('async_update ')
self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name
return

View File

@@ -1,12 +1,13 @@
{ {
"domain": "georide", "domain": "georide",
"name": "Georide", "name": "GeoRide",
"config_flow": true, "config_flow": true,
"documentation": "https://git.tontontux.fr/mduval/GeorideHA", "documentation": "https://github.com/ptimatth/GeorideHA",
"requirements": [ "requirements": [
"georideapilib>=0.4.1", "georideapilib>=0.6.0",
"pyjwt>=1.7.1" "pyjwt>=1.7.1"
], ],
"dependencies": [], "dependencies": [],
"codeowners": ["@ptimatth"] "codeowners": ["ptimatth"],
"version": "0.6.0"
} }

View File

@@ -0,0 +1,81 @@
""" odometter sensor for GeoRide object """
import logging
from typing import Any, Mapping
from homeassistant.core import callback
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .const import DOMAIN as GEORIDE_DOMAIN
from .device import Device
_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()
entities = []
for coordoned_tracker in coordoned_trackers:
tracker_device = coordoned_tracker['tracker_device']
coordinator = coordoned_tracker['coordinator']
entity = GeoRideOdometerSensorEntity(coordinator, tracker_device, hass)
hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator
entities.append(entity)
async_add_entities(entities)
return True
class GeoRideOdometerSensorEntity(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 = "m"
self.entity_id = f"{ENTITY_ID_FORMAT.format('odometer')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
self._state = 0
self._hass = hass
@property
def unique_id(self):
"""Return the unique ID."""
return f"odometer_{self._tracker_device.tracker.tracker_id}"
@property
def state(self):
"""state property"""
return self._tracker_device.tracker.odometer
@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} odometer"
@property
def icon(self):
"""icon getter"""
return "mdi:counter"
@property
def device_info(self):
"""Return the device info."""
return self._tracker_device.device_info

View File

@@ -2,19 +2,21 @@
"config": { "config": {
"title": "GeoRide", "title": "GeoRide",
"step": { "step": {
"user": { "georide_login": {
"title": "Set up GeoRide", "title": "Set up GeoRide",
"description": "Are you sure you want to set up GeoRide ?", "description": "Sign-in to GeoRide",
"data": { "data": {
"email": "email", "email": "email",
"password": "password" "password": "password"
} }
} }
}, },
"error": {
"faulty_credentials": "Authorization failed",
"unknown": "Unknown error"
},
"abort": { "abort": {
"one_instance_allowed": "Only a single instance is allowed.", "one_instance_allowed": "Only a single instance is allowed."
"no_credential": "You need to add your credentails."
}, },
"create_entry": { "create_entry": {
"default": "\n\nLogin succes" "default": "\n\nLogin succes"

View File

@@ -1,129 +1,107 @@
""" device tracker for Georide object """ """ device tracker for GeoRide object """
import logging import logging
from homeassistant.core import callback
from homeassistant.components.switch import SwitchDevice from typing import Any, Mapping
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import ENTITY_ID_FORMAT from homeassistant.components.switch import ENTITY_ID_FORMAT
import georideapilib.api as GeorideApi from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from . import DOMAIN as GEORIDE_DOMAIN import georideapilib.api as GeoRideApi
from .const import DOMAIN as GEORIDE_DOMAIN
from .device import Device
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613 async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: disable=W0613
"""Set up Georide tracker based off an entry.""" """Set up GeoRide tracker based off an entry."""
georide_context = hass.data[GEORIDE_DOMAIN]["context"] georide_context = hass.data[GEORIDE_DOMAIN]["context"]
coordoned_trackers = georide_context.get_coordoned_trackers()
if georide_context.async_get_token() is None:
return False
_LOGGER.info('Current georide token: %s', georide_context.async_get_token())
trackers = GeorideApi.get_trackers(georide_context.async_get_token())
lock_switch_entities = [] lock_switch_entities = []
for tracker in trackers: for coordoned_tracker in coordoned_trackers:
entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.async_get_token, tracker_device = coordoned_tracker['tracker_device']
georide_context.async_get_tracker, data=tracker) coordinator = coordoned_tracker['coordinator']
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity entity = GeoRideLockSwitchEntity(coordinator, tracker_device, hass)
hass.data[GEORIDE_DOMAIN]["devices"][tracker_device.tracker.tracker_id] = coordinator
lock_switch_entities.append(entity) lock_switch_entities.append(entity)
async_add_entities(lock_switch_entities) async_add_entities(lock_switch_entities)
return True return True
class GeoRideLockSwitchEntity(CoordinatorEntity, SwitchEntity):
class GeorideLockSwitchEntity(SwitchDevice):
"""Represent a tracked device.""" """Represent a tracked device."""
def __init__(self, tracker_id, get_token_callback, get_tracker_callback, data): def __init__(self, coordinator: DataUpdateCoordinator[Mapping[str, Any]],
"""Set up Georide entity.""" tracker_device:Device, hass):
self._tracker_id = tracker_id """Set up GeoRide entity."""
self._data = data or {} super().__init__(coordinator)
self._get_token_callback = get_token_callback self._tracker_device = tracker_device
self._get_tracker_callback = get_tracker_callback self._name = tracker_device.tracker.tracker_name
self._name = data.tracker_name self.entity_id = f"{ENTITY_ID_FORMAT.format('lock')}.{tracker_device.tracker.tracker_id}"# pylint: disable=C0301
self._is_on = data.is_locked self._hass = hass
self.entity_id = ENTITY_ID_FORMAT.format("lock."+str(tracker_id))
self._state = {}
async def async_turn_on(self, **kwargs):
def turn_on(self, **kwargs): """ lock the GeoRide tracker """
""" lock the georide tracker """
_LOGGER.info('async_turn_on %s', kwargs) _LOGGER.info('async_turn_on %s', kwargs)
success = GeorideApi.lock_tracker(self._get_token_callback(), self._tracker_id) georide_context = self._hass.data[GEORIDE_DOMAIN]["context"]
token = await georide_context.get_token()
success = await self._hass.async_add_executor_job(GeoRideApi.lock_tracker,
token, self._tracker_device.tracker.tracker_id)
if success: if success:
self._data.is_locked = True self._tracker.is_locked = True
self._is_on = True
async def async_turn_off(self, **kwargs):
def turn_off(self, **kwargs): """ unlock the GeoRide tracker """
""" unlock the georide tracker """
_LOGGER.info('async_turn_off %s', kwargs) _LOGGER.info('async_turn_off %s', kwargs)
success = GeorideApi.unlock_tracker(self._get_token_callback(), self._tracker_id) georide_context = self._hass.data[GEORIDE_DOMAIN]["context"]
token = await georide_context.get_token()
success = await self._hass.async_add_executor_job(GeoRideApi.unlock_tracker,
token, self._tracker_device.tracker.tracker_id)
if success: if success:
self._data.is_locked = False self._tracker.is_locked = False
self._is_on = False
async def async_toggle(self, **kwargs): async def async_toggle(self, **kwargs):
""" toggle lock the georide tracker """ """ toggle lock the georide tracker """
_LOGGER.info('async_toggle %s', kwargs) _LOGGER.info('async_toggle %s', kwargs)
result = GeorideApi.toogle_lock_tracker(self._get_token_callback(), georide_context = self._hass.data[GEORIDE_DOMAIN]["context"]
self._tracker_id) token = await georide_context.get_token()
self._data.is_locked = result result = await self._hass.async_add_executor_job(GeoRideApi.toogle_lock_tracker,
self._is_on = result token, self._tracker_device.tracker.tracker_id)
self._tracker.is_locked = result
def update(self):
""" update the current tracker"""
_LOGGER.info('async_update ')
self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name
self._is_on = self._data.is_locked
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique ID.""" """Return the unique ID."""
return self._tracker_id return f"lock_{self._tracker_device.tracker.tracker_id}"
@property @property
def name(self): def name(self):
""" Georide switch name """ """ GeoRide odometer name """
return self._name return f"{self._name} lock"
@property @property
def is_on(self): def is_on(self):
""" Georide switch status """ """ GeoRide switch status """
return self._is_on return self._tracker_device.tracker.is_locked
@property
def get_token_callback(self):
""" Georide switch token callback method """
return self._get_token_callback
@property
def get_tracker_callback(self):
""" Georide switch token callback method """
return self._get_tracker_callback
@property @property
def icon(self): def icon(self):
if self._is_on: """return the entity icon"""
if self._tracker_device.tracker.is_locked:
return "mdi:lock" return "mdi:lock"
return "mdi:lock-open" return "mdi:lock-open"
@property @property
def device_info(self): def device_info(self):
"""Return the device info.""" """Return the device info."""
return { return self._tracker_device.device_info
"name": self.name,
"identifiers": {(GEORIDE_DOMAIN, self._tracker_id)},
"manufacturer": "GeoRide"
}

View File

@@ -0,0 +1,25 @@
{
"config": {
"title": "GeoRide",
"step": {
"georide_login": {
"title": "Set up GeoRide",
"description": "Sign-in to GeoRide",
"data": {
"email": "email",
"password": "password"
}
}
},
"error": {
"faulty_credentials": "Authorization failed",
"unknown": "Unknown error"
},
"abort": {
"one_instance_allowed": "Only one instance is allowed."
},
"create_entry": {
"default": "\n\nLogin success"
}
}
}

View File

@@ -0,0 +1,25 @@
{
"config": {
"title": "GeoRide",
"step": {
"georide_login": {
"title": "Configuration de GeoRide",
"description": "T'es un motard ! V",
"data": {
"email": "email",
"password": "password"
}
}
},
"error": {
"faulty_credentials": "Connexion \u00e9chou\u00e9e",
"unkonwn": "Erreur inconnue"
},
"abort": {
"one_instance_allowed": "Seulement une instance est autoris\u00e9e"
},
"create_entry": {
"default": "\n\nConnexion r\u00e9ussie !"
}
}
}

View File

@@ -1,7 +1,8 @@
{ {
"name": "Georide integration", "name": "GeoRide integration",
"content_in_root": false, "content_in_root": false,
"render_readme": true, "render_readme": true,
"domains": ["devices_tracker", "sensor"], "domains": ["devices_tracker", "sensor"],
"country": ["FR"] "country": ["FR"],
"homeassistant": "2021.4.0"
} }