Shipping v0.3.0

master 0.3.0
Matthieu DUVAL 5 years ago
commit 649c97b8bb

@ -1,8 +1,33 @@
# Georide Home assistant # 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
Official georide website: https://georide.fr/ [![hacs_badge](https://img.shields.io/badge/HACS-Custom-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/
## 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>
```

@ -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"

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

@ -3,14 +3,17 @@ from collections import defaultdict
import logging import logging
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
@ -25,10 +28,12 @@ from homeassistant.setup import async_when_setup
from .const import ( from .const import (
CONF_EMAIL, CONF_EMAIL,
CONF_PASSWORD, CONF_PASSWORD,
TRACKER_ID CONF_TOKEN,
TRACKER_ID,
TOKEN_SAFE_DAY,
DOMAIN
) )
DOMAIN = "georide"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -47,12 +52,16 @@ 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
@ -66,6 +75,8 @@ def connect_socket(hass, component):
socket.subscribe_device(context.on_device_callback) socket.subscribe_device(context.on_device_callback)
socket.subscribe_position(context.on_position_callback) socket.subscribe_position(context.on_position_callback)
context.socket = socket
socket.init() socket.init()
socket.connect(context.async_get_token()) socket.connect(context.async_get_token())
@ -75,34 +86,32 @@ async def async_setup_entry(hass, 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:
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
trackers = GeorideApi.get_trackers(token)
context.georide_trackers = 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 thread = Thread(target=connect_socket, args=(hass, entry))
trackers = GeorideApi.get_trackers(account.auth_token) thread.start()
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 return True
async def async_unload_entry(hass, entry): async def async_unload_entry(hass, entry):
@ -110,6 +119,10 @@ async def async_unload_entry(hass, 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")
context = hass.data[DOMAIN]["context"]
context.socket.disconnect()
hass.data[DOMAIN]["unsub"]() hass.data[DOMAIN]["unsub"]()
@ -126,7 +139,7 @@ class GeorideContext:
self._password = password self._password = password
self._georide_trackers = defaultdict(set) self._georide_trackers = defaultdict(set)
self._token = token self._token = token
self._pending_msg = [] self._socket = None
@property @property
def hass(self): def hass(self):
@ -158,27 +171,41 @@ class GeorideContext:
""" georide tracker list """ """ georide tracker list """
self._georide_trackers = trackers self._georide_trackers = trackers
@callback def get_token(self):
def async_get_token(self): """ here we return the current valid tocken """
""" here we return the current valid tocken, TODO: add here token expiration control"""
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 = 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 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"""
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 None
@callback @property
def async_see(self, **data): def socket(self):
"""Send a see message to the device tracker.""" """ hold the georide socket """
_LOGGER.info("sync_see") return self._socket
self._pending_msg.append(data)
@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):

@ -3,9 +3,11 @@
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
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -14,47 +16,52 @@ _LOGGER = logging.getLogger(__name__)
class GeorideConfigFlow(config_entries.ConfigFlow): class GeorideConfigFlow(config_entries.ConfigFlow):
"""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, async def async_step_georide_login(self, user_input):
data={ """ try to seupt GeoRide Account """
CONF_EMAIL: user_input[CONF_EMAIL], errors = {}
CONF_PASSWORD: user_input[CONF_PASSWORD] try:
} account = GeorideApi.get_authorisation_token(
) user_input[CONF_EMAIL],
user_input[CONF_PASSWORD])
return self.async_create_entry(
title=user_input[CONF_EMAIL],
data={
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_TOKEN: account.auth_token
}
)
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", errors=errors)
except:
_LOGGER.error("Unknown error")
errors["base"] = "faulty_credentials"
return self.async_show_form(step_id="georide_login", errors=errors)

@ -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,5 @@ CONF_PASSWORD = "password"
CONF_TOKEN = "token" CONF_TOKEN = "token"
TRACKER_ID = "trackerId" TRACKER_ID = "trackerId"
TOKEN_SAFE_DAY = 432000 # five days

@ -2,13 +2,12 @@
import logging 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 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 import georideapilib.api as GeorideApi
from . import DOMAIN as GEORIDE_DOMAIN from .const import DOMAIN as GEORIDE_DOMAIN
_LOGGER = logging.getLogger(__name__) _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"] georide_context = hass.data[GEORIDE_DOMAIN]["context"]
if georide_context.token is None: if georide_context.get_token() is None:
return False 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 = [] tracker_entities = []
for tracker in trackers: for tracker in trackers:
entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.async_get_token, entity = GeorideTrackerEntity(tracker.tracker_id, georide_context.get_token,
georide_context.async_get_tracker, tracker) georide_context.get_tracker, tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
@ -120,7 +119,6 @@ class GeorideTrackerEntity(TrackerEntity):
async def async_update(self): async def async_update(self):
""" update the current tracker""" """ update the current tracker"""
_LOGGER.info('async_update ')
self._data = self._get_tracker_callback(self._tracker_id) self._data = self._get_tracker_callback(self._tracker_id)
self._name = self._data.tracker_name self._name = self._data.tracker_name
return return

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

@ -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('async_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"
}

@ -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"

@ -8,7 +8,7 @@ from homeassistant.components.switch import ENTITY_ID_FORMAT
import georideapilib.api as GeorideApi import georideapilib.api as GeorideApi
from . import DOMAIN as GEORIDE_DOMAIN from .const import DOMAIN as GEORIDE_DOMAIN
_LOGGER = logging.getLogger(__name__) _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.""" """Set up Georide tracker based off an entry."""
georide_context = hass.data[GEORIDE_DOMAIN]["context"] georide_context = hass.data[GEORIDE_DOMAIN]["context"]
if georide_context.async_get_token() is None: if georide_context.get_token() is None:
return False 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())
lock_switch_entities = [] lock_switch_entities = []
for tracker in trackers: for tracker in trackers:
entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.async_get_token, entity = GeorideLockSwitchEntity(tracker.tracker_id, georide_context.get_token,
georide_context.async_get_tracker, data=tracker) georide_context.get_tracker, data=tracker)
hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity hass.data[GEORIDE_DOMAIN]["devices"][tracker.tracker_id] = entity
lock_switch_entities.append(entity) lock_switch_entities.append(entity)
@ -49,7 +49,7 @@ class GeorideLockSwitchEntity(SwitchDevice):
self._get_tracker_callback = get_tracker_callback self._get_tracker_callback = get_tracker_callback
self._name = data.tracker_name self._name = data.tracker_name
self._is_on = data.is_locked 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 = {} self._state = {}

@ -3,5 +3,6 @@
"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": "0.100.0"
} }
Loading…
Cancel
Save