Compare commits

...

13 Commits
0.2.0 ... 0.4.3

Author SHA1 Message Date
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
12 changed files with 339 additions and 127 deletions

View File

@@ -1,8 +1,32 @@
# Georide Home assistant
![Logo Georide](georide-logo.png)
![Logo Georide](/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

@@ -2,22 +2,24 @@
"config": {
"title": "GeoRide",
"step": {
"user": {
"georide_login": {
"title": "Set up GeoRide",
"description": "Are you sure you want to 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 a single instance is allowed.",
"no_credential": "You need to add your credentails."
"one_instance_allowed": "Only one instance is allowed."
},
"create_entry": {
"default": "\n\nLogin succes"
"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

@@ -3,14 +3,17 @@ from collections import defaultdict
import logging
from datetime import timedelta
import voluptuous as vol
import math
import time
import json
import jwt
from threading import Thread
import voluptuous as vol
import jwt
from aiohttp.web import json_response
from georideapilib.objects import GeorideAccount
import georideapilib.api as GeorideApi
from georideapilib.socket import GeorideSocket
@@ -25,10 +28,13 @@ from homeassistant.setup import async_when_setup
from .const import (
CONF_EMAIL,
CONF_PASSWORD,
TRACKER_ID
CONF_TOKEN,
TRACKER_ID,
TOKEN_SAFE_DAY,
MIN_UNTIL_REFRESH,
DOMAIN
)
DOMAIN = "georide"
_LOGGER = logging.getLogger(__name__)
@@ -47,74 +53,78 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass, config):
"""Setup Georide component."""
hass.data[DOMAIN] = {"config": config[DOMAIN], "devices": {}, "unsub": None}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data={}
hass.async_create_task(
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 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):
"""Set up Georide entry."""
config = hass.data[DOMAIN]["config"]
email = config.get(CONF_EMAIL) or entry.data[CONF_EMAIL]
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
)
_LOGGER.info("Context-setup and start the thread")
_LOGGER.info("Thread started")
if email is None or password is None:
return False
try:
account = GeorideApi.get_authorisation_token(email, password)
context = GeorideContext(
hass,
email,
password,
account.auth_token
)
hass.data[DOMAIN]["context"] = context
# We add trackers to the context
context.refresh_trackers()
hass.data[DOMAIN]["context"] = context
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "device_tracker"))
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch"))
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor"))
# We add trackers to the context
trackers = GeorideApi.get_trackers(account.auth_token)
context.georide_trackers = trackers
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "device_tracker"))
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "switch"))
thread = Thread(target=connect_socket, args=(hass, entry))
thread.start()
except:
return False
return True
async def async_unload_entry(hass, 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, "switch")
await hass.config_entries.async_forward_entry_unload(entry, "sensor")
context = hass.data[DOMAIN]["context"]
context.socket.disconnect()
hass.data[DOMAIN]["unsub"]()
return True
def connect_socket(context):
"""subscribe to georide socket"""
_LOGGER.info("Georide socket connexion")
socket = GeorideSocket()
socket.subscribe_locked(context.on_lock_callback)
socket.subscribe_device(context.on_device_callback)
socket.subscribe_position(context.on_position_callback)
context.socket = socket
socket.init()
socket.connect(context.get_token())
class GeorideContext:
"""Hold the current Georide context."""
@@ -126,8 +136,9 @@ class GeorideContext:
self._password = password
self._georide_trackers = defaultdict(set)
self._token = token
self._pending_msg = []
self._socket = None
self._thread_started = False
self._previous_refresh = math.floor(time.time()/60)
@property
def hass(self):
""" hass """
@@ -156,29 +167,59 @@ class GeorideContext:
@georide_trackers.setter
def georide_trackers(self, trackers):
""" georide tracker list """
self._georide_trackers = trackers
self._georide_trackers = trackers
@callback
def async_get_token(self):
""" here we return the current valid tocken, TODO: add here token expiration control"""
def get_token(self):
""" here we return the current valid tocken """
jwt_data = jwt.decode(self._token, verify=False)
exp_timestamp = jwt_data['exp']
epoch = math.ceil(time.time())
if (exp_timestamp - TOKEN_SAFE_DAY) < epoch:
_LOGGER.info("Time reached, renew token")
account = 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)
return self._token
@callback
def async_get_tracker(self, tracker_id):
def get_tracker(self, tracker_id):
""" here we return last tracker by id"""
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
self.refresh_trackers()
if not self._thread_started:
_LOGGER.info("Start the thread")
self._hass.async_add_executor_job(connect_socket, self)
# We refresh the tracker list each hours
self._thread_started = True
for tracker in self._georide_trackers:
if tracker.tracker_id == tracker_id:
return tracker
return None
@callback
def async_see(self, **data):
"""Send a see message to the device tracker."""
_LOGGER.info("sync_see")
self._pending_msg.append(data)
def refresh_trackers(self):
"""Used to refresh the tracker list"""
_LOGGER.info("Tracker list refresh")
self._georide_trackers = GeorideApi.get_trackers(self.get_token())
@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
def on_lock_callback(self, data):

View File

@@ -3,9 +3,11 @@
import logging
from homeassistant import config_entries
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
_LOGGER = logging.getLogger(__name__)
@@ -14,47 +16,57 @@ _LOGGER = logging.getLogger(__name__)
class GeorideConfigFlow(config_entries.ConfigFlow):
"""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 """
if self._async_current_entries():
return self.async_abort(reason="one_instance_allowed")
if user_input is None:
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_PASSWORD): vol.All(str)
}))
return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({
vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)),
vol.Required(CONF_PASSWORD): vol.All(str)
}))
return self.async_create_entry(
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):
async def async_step_import(self, user_input=None): #pylint: disable=W0613
"""Import a config flow from configuration."""
_LOGGER.info("user email: %", str(user_input))
if self._async_current_entries():
return self.async_abort(reason="one_instance_allowed")
if user_input is None:
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_PASSWORD): vol.All(str)
}))
return self.async_show_form(step_id='georide_login', data_schema=vol.Schema({
vol.Required(CONF_EMAIL): vol.All(str, vol.Length(min=3)),
vol.Required(CONF_PASSWORD): vol.All(str)
}))
return self.async_create_entry(
title=user_input,
data={
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD]
async def async_step_georide_login(self, user_input):
""" try to seupt GeoRide Account """
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 = 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 """
DOMAIN = "georide"
CONF_EMAIL = "email"
CONF_PASSWORD = "password"
@@ -6,3 +7,7 @@ CONF_PASSWORD = "password"
CONF_TOKEN = "token"
TRACKER_ID = "trackerId"
MIN_UNTIL_REFRESH = 10
TOKEN_SAFE_DAY = 432000 # five days

View File

@@ -2,13 +2,12 @@
import logging
from homeassistant.core import callback
from homeassistant.components.device_tracker.const import ENTITY_ID_FORMAT, SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.const import DOMAIN, SOURCE_TYPE_GPS
from homeassistant.components.device_tracker.config_entry import TrackerEntity
import georideapilib.api as GeorideApi
from . import DOMAIN as GEORIDE_DOMAIN
from .const import DOMAIN as GEORIDE_DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -18,19 +17,19 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d
georide_context = hass.data[GEORIDE_DOMAIN]["context"]
if georide_context.token is None:
if georide_context.get_token() is None:
return False
_LOGGER.info('Current georide token: %s', georide_context.async_get_token())
_LOGGER.info('Current georide token: %s', georide_context.get_token())
trackers = GeorideApi.get_trackers(georide_context.async_get_token())
trackers = GeorideApi.get_trackers(georide_context.get_token())
tracker_entities = []
for tracker in trackers:
entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.async_get_token,
georide_context.async_get_tracker, tracker)
entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.get_token,
georide_context.get_tracker, tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
@@ -51,7 +50,7 @@ class GeorideTrackerEntity(TrackerEntity):
self._get_tracker_callback = get_tracker_callback
self._name = tracker.tracker_name
self._data = tracker or {}
self.entity_id = ENTITY_ID_FORMAT.format(tracker_id)
self.entity_id = DOMAIN + ".{}".format(tracker_id)
@property
def unique_id(self):
@@ -118,10 +117,9 @@ class GeorideTrackerEntity(TrackerEntity):
"""No polling needed."""
return True
async def async_update(self):
def update(self):
""" update the current tracker"""
_LOGGER.info('async_update ')
_LOGGER.info('update')
self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name
return

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://git.tontontux.fr/mduval/GeorideHA",
"requirements": [
"georideapilib>=0.4.1",
"georideapilib>=0.4.4",
"pyjwt>=1.7.1"
],
"dependencies": [],

View File

@@ -0,0 +1,102 @@
""" odometter sensor for Georide object """
import logging
from homeassistant.core import callback
from homeassistant.components.switch import SwitchDevice
from homeassistant.components.switch import ENTITY_ID_FORMAT
import georideapilib.api as GeorideApi
from .const import DOMAIN as 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"]
if georide_context.get_token() is None:
return False
trackers = GeorideApi.get_trackers(georide_context.get_token())
odometer_switch_entities = []
for tracker in trackers:
entity = GeorideOdometerSensorEntity(tracker.tracker_id, georide_context.get_token,
georide_context.get_tracker, data=tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
odometer_switch_entities.append(entity)
async_add_entities(odometer_switch_entities)
return True
class GeorideOdometerSensorEntity(SwitchDevice):
"""Represent a tracked device."""
def __init__(self, tracker_id, get_token_callback, get_tracker_callback, data):
"""Set up Georide entity."""
self._tracker_id = tracker_id
self._data = data or {}
self._get_token_callback = get_token_callback
self._get_tracker_callback = get_tracker_callback
self._name = data.tracker_name
self._unit_of_measurement = "m"
self.entity_id = ENTITY_ID_FORMAT.format("odometer") + "." + str(tracker_id)
self._state = 0
def update(self):
""" update the current tracker"""
_LOGGER.info('update')
self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name
self._state = self._data.odometer
@property
def unique_id(self):
"""Return the unique ID."""
return self._tracker_id
@property
def name(self):
""" Georide switch name """
return self._name
@property
def state(self):
return self._state
@property
def unit_of_measurement(self):
return self._unit_of_measurement
@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
def icon(self):
return "mdi:counter"
@property
def device_info(self):
"""Return the device info."""
return {
"name": self.name,
"identifiers": {(GEORIDE_DOMAIN, self._tracker_id)},
"manufacturer": "GeoRide"
}

View File

@@ -2,19 +2,21 @@
"config": {
"title": "GeoRide",
"step": {
"user": {
"georide_login": {
"title": "Set up GeoRide",
"description": "Are you sure you want to 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 a single instance is allowed.",
"no_credential": "You need to add your credentails."
"one_instance_allowed": "Only a single instance is allowed."
},
"create_entry": {
"default": "\n\nLogin succes"

View File

@@ -8,7 +8,7 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT
import georideapilib.api as GeorideApi
from . import DOMAIN as GEORIDE_DOMAIN
from .const import DOMAIN as GEORIDE_DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -18,17 +18,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities): # pylint: d
"""Set up Georide tracker based off an entry."""
georide_context = hass.data[GEORIDE_DOMAIN]["context"]
if georide_context.async_get_token() is None:
if georide_context.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())
_LOGGER.info('Current georide token: %s', georide_context.get_token())
trackers = GeorideApi.get_trackers(georide_context.get_token())
lock_switch_entities = []
for tracker in trackers:
entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.async_get_token,
georide_context.async_get_tracker, data=tracker)
entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.get_token,
georide_context.get_tracker, data=tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
lock_switch_entities.append(entity)
@@ -49,7 +49,7 @@ class GeorideLockSwitchEntity(SwitchDevice):
self._get_tracker_callback = get_tracker_callback
self._name = data.tracker_name
self._is_on = data.is_locked
self.entity_id = ENTITY_ID_FORMAT.format("lock."+str(tracker_id))
self.entity_id = ENTITY_ID_FORMAT.format("lock") +"." + str(tracker_id)
self._state = {}
@@ -80,7 +80,7 @@ class GeorideLockSwitchEntity(SwitchDevice):
def update(self):
""" update the current tracker"""
_LOGGER.info('async_update ')
_LOGGER.info('update')
self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name
self._is_on = self._data.is_locked

View File

@@ -3,5 +3,6 @@
"content_in_root": false,
"render_readme": true,
"domains": ["devices_tracker", "sensor"],
"country": ["FR"]
"country": ["FR"],
"homeassistant": "0.100.0"
}