Xee integration

I’ve been looking at the forums and nothing is related to the integration of the French device xee with home assistant.

It would be possible to do the integration?, I put here the code that is used for the eedomus platform, which passes the data through the Xee api V4.

Author: Thibautg16

<?php

// Version 1 / 24 février 2016		// Initial version
// Version 2 / 30 mai 2016				// multiple Xee modules support
// version 3 / 16 aout 2016       // API v3 update
// version 4 / 9 janvier 2018     // Correction réassociation

$api_url = 'https://cloud.xee.com/v3/';

// Code could change if we link again the eedomusXee app
if ($_GET['mode'] != 'verify')
{
	// first time we didn't have any car_id
	$refresh_token = loadVariable('refresh_token'.$_GET['car_id']);
	if ($refresh_token == '')
	{
		$refresh_token = loadVariable('refresh_token');
	}
	
	$expire_time = loadVariable('expire_time'.$_GET['car_id']);
	// if not expired we can keep the old access_token
  if (time() < $expire_time)
  {
    $access_token = loadVariable('access_token'.$_GET['car_id']);
  }
}

// we need an access token
if ($access_token == '')
{
	if (strlen($refresh_token) > 1)
	{
		// we need to get the refresh token
		$grant_type = 'refresh_token';
		$postdata = 'grant_type='.$grant_type.'&refresh_token='.$refresh_token;
	}
	else
	{
		// Initial code fetching
		$code = $_GET['oauth_code'];
		$grant_type = 'authorization_code';
		$postdata = 'grant_type='.$grant_type.'&code='.$code;
	}
	
	$response = httpQuery($api_url.'auth/access_token', 'POST', $postdata, 'xee_oauth');
	$params = sdk_json_decode($response);

	if ($params['error'] != '')
	{
		// on reessaie avec le token global (l'utilisateur a pu refaire une association)
		if ($params['error'] == 'invalid_request' && $grant_type == 'refresh_token')
		{
			$refresh_token = loadVariable('refresh_token');
			$postdata = 'grant_type='.$grant_type.'&refresh_token='.$refresh_token;
			$response = httpQuery($api_url.'auth/access_token', 'POST', $postdata, 'xee_oauth');
			$params = sdk_json_decode($response);
		}
		
		if ($params['error'] != '')
		{
			var_dump($api_url.'auth/access_token', $postdata);
			die("<br><br>Auth error: <b>".$params['error'].'</b> (grant_type = '.$grant_type.')<br><br>'.$response);
		}
	}

	// save on eedomus gateway for further use
	if (isset($params['refresh_token']))
	{
		$access_token = $params['access_token'];
		saveVariable('access_token'.$_GET['car_id'], $access_token);
		saveVariable('refresh_token'.$_GET['car_id'], $params['refresh_token']);
		saveVariable('expire_time'.$_GET['car_id'], time()+$params['expires_in']);
		saveVariable('code'.$_GET['car_id'], $code);
		
		// sans car_id, dans le cas de plusieurs Xee, permet de récupérer le code principal
		saveVariable('access_token', $access_token);
		saveVariable('refresh_token', $params['refresh_token']);
	}
	else if ($access_token == '')
	{
    //var_dump($api_url.'auth/access_token', $postdata, $response);
		die("Auth error :(<br><br>".$response);
	}
}

$HEADERS = array("Authorization: Bearer $access_token");

if ($_GET['car_id'] == '')
{
  // Fetch user infos
  $response = httpQuery($api_url.'users/me?access_token='.$access_token, 'GET', NULL, NULL, $HEADERS);
  $data = sdk_json_decode($response);
  $user_id = $data['id'];

  // Fetch user cars list
  $response = httpQuery($api_url.'users/'.$user_id.'/cars?access_token='.$access_token, 'GET', NULL, NULL, $HEADERS);
  $data = sdk_json_decode($response);

  // car id selection "menu"
  echo "Car identifiers (Copy & paste on in eedomus) :";
  echo "<br>";
  echo "<ul>";
  foreach($data as $cars)
  {
    echo '<li><b>'.$cars['id'].'</b> : '.$cars['name'].'</li>';
  }
  echo "</ul>";
  die();
}
else
{
  // Fetch car status datas
  $response = httpQuery($api_url.'cars/'.$_GET['car_id'].'/status?access_token='.$access_token, 'GET', NULL, NULL, $HEADERS);
  $data = sdk_json_decode($response);
  
  // force token expiration for next query
  if ($data[0]['message'] == "Token has expired")
  {
    saveVariable('expire_time'.$_GET['car_id'], 0);
  }
	
	// "Create a new access token and then retry"
	else if ($data[0]['message'] == "Token has been revoked")
  {
    saveVariable('expire_time'.$_GET['car_id'], 0);
  }
  else if ($data[0]['message'] == "Token does not have the required scope") // Add the status_read scope to your app scopes and reconnect the user
  {
		saveVariable('access_token'.$_GET['car_id'], '');
		saveVariable('refresh_token'.$_GET['car_id'], '');
		saveVariable('expire_time'.$_GET['car_id'], 0);
		saveVariable('code'.$_GET['car_id'], '');
	}

  // updating position channel
  $position_controller_module_id = getArg('position_controller_module_id');
  
  $last_lat_long = loadVariable('last_lat_long'.$_GET['car_id']);
  $last_lat_long_time = loadVariable('last_lat_long_time'.$_GET['car_id']);
	
	foreach($data['signals'] as $signals)
	{
		if ($signals['name'] == 'VehiculeSpeed')
		{
			$speed = $signals['value'];
		}
	}
	
	if ($data['location']['latitude'] != '') // safety
	{
		$lat_long = $data['location']['latitude'].','.$data['location']['longitude'].','.$speed;
		// if no move, update only each 30 minutes
		
		if ($last_lat_long != $lat_long || time() - $last_lat_long_time > 30 /*minutes*/* 60)
		{
			setValue($position_controller_module_id, $lat_long);
			saveVariable('last_lat_long_time'.$_GET['car_id'], time());
			saveVariable('last_lat_long'.$_GET['car_id'], $lat_long);
		}
	}
  
  sdk_header('text/xml');
  echo jsonToXML($response);
}

?>

Hi
Do you have news on integration of XEE on Home assitant please ?

I am waiting after that to add it

Thansk for you help

Sorry. I waiting too

Ok thanks for your reply :sunglasses:

Hi! I integrated it with their API.
I have written a article about how to do it :slight_smile:

2 Likes

Can I buy xee in the UK? And will it work?

I don’t think so, because it relies on a SIM card (That you don’t have to pay). I only know that works in Spain and France, but probably you can contact them and get better information

1 Like

Hi victor from Spain. Thank you for you wonderful article in Medium. I´m not be able to finish it.

I get the token
{
“access_token”: “XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,
“expires_in”: 86400,
“refresh_token”: “XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”,
“scope”: “vehicles.locations.read vehicles.read vehicles.signals.read”,
“token_type”: “Bearer”
}

When in postman I call get with https://api.xee.com/v4/users/me/vehicles and put the bearer token

No received any data []
What´s is wrong? Thank you

It’s your vehicle linked with your account? Try with the Xee application before to check if it’s correct

Yes. It´s linked. I can check the kms, the fuekl, the trips, the gps position…

The refresh_token and the access_token need to be updated every day. A simple curl script is not enough. Does anyone solve this issue?

My two cents:

You can update the token in home assistant without curl in cron.

yaml

sensor:

  • platform: command_line
    command: !secret refresh_xee_command
    name: refresh_xee
    scan_interval: 43200
    value_template: >-
    {% if value_json.access_token is defined -%}
    Token Xee Update
    {%- else -%}
    Token Xee NO Update
    {%- endif -%}

secret

refresh_xee_command: “curl -X POST https://api.xee.com/v4/oauth/token -H ‘Authorization: Basic {{acces_token}}’ -H ‘Content-Type: application/x-www-form-urlencoded’ -H ‘cache-control: no-cache’ -d ‘grant_type=refresh_token&refresh_token={{refresh_token}}&scope=vehicles.locations.read vehicles.read vehicles.signals.read’”

lovelace card

entities:

  • entity: sensor.refresh_xee
    icon: ‘mdi:cookie’
    name: Estado token XEE
    show_header_toggle: false
    type: entities

Many thanks Victor for the guide, now I have my two cars in home assistant.

What about to update the access_token @ElGranLoky ? The refresh_token can be the same in the Xee server response, but the access_token is new everytime the token is refresh.

Up to now my solution is to run the refresh command (and also updating the secrets.yaml with the new access_token) everyday and restart Home Assistant. But this workaround is ugly.

Yes i know, but you can use your username and password to avoid the access_token. Example:

refresh_xee_command: "curl -s -m 10 -X POST https://api.xee.com/v4/oauth/token -H 'Authorization:Basic {{HereYourAccessToken}}' -H 'Content-Type: application/x-www-form-urlencoded' -H 'cache-control: no-cache' -d 'grant_type=password&username={{[email protected]}}&password={{here-your-password-example}}&scope=vehicles.locations.read vehicles.read vehicles.signals.read'"

Sorry for the delay :slightly_smiling_face:

thanks!! I’m using it like this, but how do you modify it so you don’t have to restart home assistant every day

I used to be xee plugin maintainer for domogik and use this like that for the moment.

It use the python api3 sdk from quentin
You need to install it before of course :slight_smile:
Create or have a Xee dev accounts.
And then i use a simple cron that run on another container and publish through mqtt:

from xee import Xee
import xee.entities as xee_entities
import sys
import os
import pickle
import pytz
import json
import paho.mqtt.client as paho

broker="YOUR BROKER IP OR DNS NAME"
port=1883
def on_publish(client,userdata,result):             #create function for callback
    print("data published \n")
    pass
client1= paho.Client("xee2mqtt")                           #create client object
client1.on_publish = on_publish                          #assign function to callback
client1.connect(broker,port)                                 #establish connection


xee = Xee(client_id="YOUR_CLIENT_ID_FROM_XEE_DEV_ACCOUNT", 
        client_secret="YOUR_CLIENT_SECRET_FROM_XEE_DEV_ACCOUNT", 
        redirect_uri="http://localhost")


login_url = xee.get_authentication_url()+"&redirect_uri=http://localhost"
xee_config_file = os.path.join(os.path.dirname(__file__), 'xee.json')

try:
    with open(xee_config_file, 'rb') as xee_token_file:
        print ("Opening File")
        token = pickle.load(xee_token_file)
    print ("Getting user")
    user ,error = xee.get_user(token.access_token)
    print (error)
    if error is not None :
        print ("Error getting user, try refreshing with token_refresh from file")
        print (error)
        token,error = xee.get_token_from_refresh_token(token.refresh_token)
        if error != None :
            print (error)
            sys.exit("refreshing token failed from refresh_token")

except:
    print ("Error with file saved or no file saved")
    print("Go to %s allow the app and copy your oauth_verifier" %login_url)
    authorization_code = input('Please enter your authorization_code: ')
    token,error = xee.get_token_from_code(authorization_code)
    if error is not None :
        print ("Error getting token from code")
        print (error)
        print ("Exiting")
        sys.exit("refresh Error")

with open(xee_config_file, 'wb') as xee_token_file:
    pickle.dump(token, xee_token_file)

cars, err = xee.get_cars(token.access_token)
for car in cars:
    try:
        client1.publish("/XEE/" + str(car.id) + "/carname/", str(car.name))
    except:
        print ("error publishing this value")
        print (car)
    Status  ,error = xee.get_status(car.id,token.access_token)
    if error is None:
        for statu in Status:
            if statu is not None:
                for signals in statu:
                    try:
                        client1.publish("/XEE/" + str(car.id) + "/" + str(signals.name)  + "", str(signals.value))
                        print (signals)
                    except:
                        print ("error publishing this value")
                        print (signals)
        locations ,error = xee.get_locations(car.id,token.access_token,limit=1)
        for location in locations:
            try:
                lat=location.latitude
                lon=location.longitude
                client1.publish("/XEE/" + str(car.id) + "/location/", json.dumps({"longitude": lon,"latitude": lat}))
                print (location)
            except:
                print ("error publishing this value")
                print (location)

  1. Create a Xee2mqtt.py with this code
  2. Launch once by hand and it will bring you to xee user account to allow access.
    Then redirect you to a localhost page containing your authorization code.
    Type it in the answer waiting from the cli.
    It will create a json file containing the access token and the refresh token.
    So you never have to look it up again, it will try refresh it automatically (works 99% of the time).
  3. Create a cron that fire this script at the frequencies you wanted.

Benefits it list all the users cars and available signals alone :revolving_hearts:

I hope to have time to make an hacs for this but i have more to do for know, like looking at plcbus again and need to learn how HASS is working.

If anyone has more notion on how to make hacs integration do not hesitate :slight_smile: here are all the base to do it in pure python.

im trying but the api v3 dont work in the first execution… im tryng generate the code with the api v4 but is invalid…
help please!!
root@SuperDocker:/home# python3 Xee2mqtt.py

Error with file saved or no file saved

Go to https://cloud.xee.com/v3/auth/auth?client_id=xxxx&redirect_uri=http://localhost all

ow the app and copy your oauth_verifier

Please enter your authorization_code: ZUFOYm9wWFN2VElBU1Byd0lQTzU0Y0I5cUU5RVAzRmU

Error getting token from code

{“message”:“Invalid authentication”}

Exiting

refresh Error

That seems to be a very bad news…

Maybe the V4 api code do not work with the old V3 api.

I’ll MP you to make some test if your ok

After fast test it seems indeed that new dev account could only access the xee api V4.

So if someone else needs i still have my old V3 api key that i could share, MP me if needed.

oh, I had been looking forward to implementing it.