Skip to content

Commit

Permalink
add pushing to osmandapi
Browse files Browse the repository at this point in the history
  • Loading branch information
krombel committed Jan 27, 2025
1 parent a70b5c7 commit f34238d
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 15 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ With this app you will be able to:
- lock and unlock doors
- get consumption statistics
- visualize your trips on a map or in a table
- get the list of car charging
- get the list of car charging
- visualize battery charging curve
- visualize altitude trip curve
- get car charging CO2 emission
- get car charging price
- send live data to ABetterRoutePlanner

The official API is documented [here](https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article) but it is not totally up to date, and contains some errors.
The official API is documented [here](https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article) but it is not totally up to date, and contains some errors.


## I. Installation
- [Installation on Linux or Windows](docs/Install.md)
- [Installation as Home Assistant addon](https://github.com/flobz/psacc-ha/blob/main/psacc-ha/README.md)
- [Installation as Home Assistant addon](https://github.com/flobz/psacc-ha/blob/main/psacc-ha/README.md)
- [Installation in Docker](docs/Docker.md)
- [Installation on Raspberry Pi with docker-compose (external Tutorial)](https://return2.net/opel-peugeot-electric-vehicle-set-charging-threshold-limit/)
## II. Use the API
Expand All @@ -40,33 +40,34 @@ Look at [API documentation](./docs/psacc_api.md)
You can add the -r argument to record the position of the vehicle and retrieve this information in a dashboard.

``python3 psa-car-controller -f config.json -c charge_config.json -r``

You will be able to visualize your trips, your consumption and some statistics:


![Screenshot_20210128_104519](https://user-images.githubusercontent.com/48728684/106119895-01c98d80-6156-11eb-8969-9e8bc24f3677.png)
- You have to add an API key from https://home.openweathermap.org/ in your config file, to be able to see your consumption vs exterior temperature.
- You have to add an API key from https://co2signal.com/ to have your CO2 emission by KM (in France the key isn't needed).
- You have to add an API key from https://co2signal.com/ to have your CO2 emission by KM (in France the key isn't needed).
### IV. Charge price calculation
The dashboard can give you the price by kilometer and price by kw that you pay.
You just have to set the price in the config file.

After a successful launch of the app, a config.ini file will be created.
In this file you can set the price you pay for electricity in the following format "0.15".

If you have a special price during the night you can set "night price", "night hour start" and "night hour end".
If you have a special price during the night you can set "night price", "night hour start" and "night hour end".
Hours need to be in the following format "23h12".

You can modify a price manually in the dashboard. It can be useful if you use public charge point.
## V. Connection to other services:
- [Domoticz](docs/domoticz/Domoticz.md)
- [HomeAssistant](https://github.com/Flodu31/HomeAssistant-PeugeotIntegration)
- Jeedom (Anyone can share the procedure ?)
- You can send live car status to ABRP (A better Route Planner), see [this page](docs/abrp.md)
- You can send live car status to ABRP (A better Route Planner), see [this page](docs/osmandapi.md)
- You can send live car status to OsmAndApi (A better Route Planner), see [this page](docs/abrp.md)
- [Grafana](https://github.com/flobz/psa_car_controller/issues/161)

## FAQ
If you have a problem or a question, please check if the answer isn't already in the [FAQ](FAQ.md).
If you have a problem or a question, please check if the answer isn't already in the [FAQ](FAQ.md).

## Contribute
If you need information to contribute or edit this program go [here](docs/Develop.md).
Expand Down
27 changes: 27 additions & 0 deletions docs/osmandapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Connect to OsmAnd API recipient

Use OsmAnd APR recipient - e.g. [Traccar](https://www.traccar.org) - to track your device

### Prerequisite
1. A working last version of psa_car_controller
2. Access to an OsmAnd API compatible receiver

### Procedure
1. Go to your OsmAnd receiver and setup a car which matches your VIN or use a self defined identifier
2. Enable OsmAndAPI for your car in "Control" section
3. stop psa_car_controller
4. edit config.json and check the following:
4.1:
"osmandapi": {
"osmand_enable_vin": [
"<VIN of you device - should automatically be here once activated in 2.>"
],
"server_uri": "https://<your osmand api endpoint>"
},
5. If you want to use a self defined identifier (you can skip this if you id should be your VIN): Open cars.json
3.1: set osmand_id to you your chosen device id

11.4 save the file

11.5 restart psa_car_controller
6. Enjoy
79 changes: 79 additions & 0 deletions psa_car_controller/psacc/application/osmand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import logging
from datetime import datetime

import requests

from psa_car_controller.psacc.model.car import Car

logger = logging.getLogger(__name__)
TIMEOUT_IN_S = 10


class OsmAndApi:
def __init__(self, server_uri: str = None, osmand_enable_vin=None):
if osmand_enable_vin is None:
osmand_enable_vin = []
self.__server_uri = server_uri
self.osmand_enable_vin = set(osmand_enable_vin)
self.proxies = None

def enable_osmand(self, vin, enable):
if enable:
self.osmand_enable_vin.add(vin)
else:
self.osmand_enable_vin.discard(vin)

def call(self, car: Car, ext_temp: float = None):
try:
if car.vin in self.osmand_enable_vin:
if self.__server_uri is None:
logger.debug("osmandapi: No Server URI set")
return False

if not car.has_fuel() and not car.has_battery():
logger.debug("Neither fuel nor battery available")
return False

data = {
"id": car.get_osmand_id(),
"timestamp": int(datetime.timestamp(car.status.last_position.properties.updated_at)),
"is_parked": not bool(car.status.is_moving()),
"odometer": car.status.timed_odometer.mileage * 1000,
"speed": getattr(car.status.kinetic, "speed", 0.0),
"lat": car.status.last_position.geometry.coordinates[1],
"lon": car.status.last_position.geometry.coordinates[0],
"altitude": car.status.last_position.geometry.coordinates[2]
}
if car.has_fuel:
fuel = car.status.get_energy('Fuel')
data["fuel"] = fuel.level
if car.has_battery():
energy = car.status.get_energy('Electric')
if energy.battery and energy.battery.health:
data["soh"] = energy.battery.health.resistance
data["soc"] = energy.level
data["batt"] = energy.level
data["power"] = energy.consumption
data["current"] = car.status.battery.current
data["voltage"] = car.status.battery.voltage
data["is_charging"] = energy.charging.status == "InProgress"
data['est_battery_range'] = energy.autonomy

if ext_temp is not None:
data["ext_temp"] = ext_temp

response = requests.request("POST", self.__server_uri, params=data, proxies=self.proxies,
verify=self.proxies is None, timeout=TIMEOUT_IN_S)
logger.debug(response.text)
try:
return response.status_code == 200
except (KeyError):
logger.error("Bad response from OsmAnd API: %s", response.text)
return False
except (AttributeError, IndexError, ValueError):
logger.exception("osmandapi:")
return False

def __iter__(self):
yield "osmand_enable_vin", list(self.osmand_enable_vin)
yield "server_uri", self.__server_uri
15 changes: 12 additions & 3 deletions psa_car_controller/psacc/application/psa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from psa_car_controller.psa.constants import realm_info, AUTHORIZE_SERVICE

from .abrp import Abrp
from .osmand import OsmAndApi
from psa_car_controller.psacc.repository.db import Database
from psa_car_controller.common.mylogger import CustomLogger

Expand All @@ -36,7 +37,7 @@ def connect(self, code: str):

# pylint: disable=too-many-arguments
def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token, customer_id, realm, country_code,
brand=None, proxies=None, weather_api=None, abrp=None, co2_signal_api=None):
brand=None, proxies=None, weather_api=None, abrp=None, osmandapi=None, co2_signal_api=None):
self.realm = realm
self.service_information = ServiceInformation(AUTHORIZE_SERVICE[self.realm],
realm_info[self.realm]['oauth_url'],
Expand Down Expand Up @@ -68,6 +69,10 @@ def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token
self.abrp = Abrp()
else:
self.abrp: Abrp = Abrp(**abrp)
if osmandapi is None:
self.osmandapi = OsmAndApi()
else:
self.osmandapi: OsmAndApi = OsmAndApi(**osmandapi)
self.set_proxies(proxies)
self.config_file = DEFAULT_CONFIG_FILENAME
Ecomix.co2_signal_key = co2_signal_api
Expand All @@ -94,6 +99,7 @@ def set_proxies(self, proxies):
else:
self.api_config.proxy = proxies['http']
self.abrp.proxies = proxies
self.osmandapi.proxies = proxies
self.manager.proxies = proxies

def get_vehicle_info(self, vin, cache=False):
Expand Down Expand Up @@ -168,7 +174,7 @@ def load_config(name="config.json"):
config = {**json.loads(config_str)}
if "country_code" not in config:
config["country_code"] = input("What is your country code ? (ex: FR, GB, DE, ES...)\n")
for new_el in ["abrp", "co2_signal_api"]:
for new_el in ["abrp", "osmandapi", "co2_signal_api"]:
if new_el not in config:
config[new_el] = None
psacc = PSAClient(**config)
Expand Down Expand Up @@ -196,7 +202,9 @@ def record_info(self, car: Car): # pylint: disable=too-many-locals
moving)
Database.record_position(self.weather_api, car.vin, mileage, latitude, longitude, altitude, date, level,
level_fuel, moving)
self.abrp.call(car, Database.get_last_temp(car.vin))
last_temp = Database.get_last_temp(car.vin)
self.abrp.call(car, last_temp)
self.osmandapi.call(car, last_temp)
if car.has_battery():
electric_energy_status = car.status.get_energy('Electric')
try:
Expand Down Expand Up @@ -231,6 +239,7 @@ def default(self, mp: PSAClient): # pylint: disable=arguments-renamed
"refresh_token": mp.manager.refresh_token,
"client_secret": mp.service_information.client_secret,
"abrp": dict(mp.abrp),
"osmandapi": dict(mp.osmandapi),
"remote_refresh_token": mp.remote_client.remoteCredentials.refresh_token,
"customer_id": mp.account_info.customer_id,
"client_id": mp.account_info.client_id,
Expand Down
10 changes: 8 additions & 2 deletions psa_car_controller/psacc/model/car.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# pylint: disable=too-many-arguments
class Car:
def __init__(self, vin, vehicle_id, brand, label=None, battery_power=None, fuel_capacity=None,
max_elec_consumption=None, max_fuel_consumption=None, abrp_name=None):
max_elec_consumption=None, max_fuel_consumption=None, abrp_name=None, osmand_id=None):
self.vin = vin
model = None
if label is not None:
Expand All @@ -23,6 +23,7 @@ def __init__(self, vin, vehicle_id, brand, label=None, battery_power=None, fuel_
self.brand = brand
self._status = None
self.abrp_name = abrp_name or model.abrp_name
self.osmand_id = osmand_id
self.battery_power = battery_power or model.battery_power
self.fuel_capacity = fuel_capacity or model.fuel_capacity
self.max_elec_consumption = max_elec_consumption or model.max_elec_consumption # kwh/100Km
Expand All @@ -46,7 +47,7 @@ def has_battery(self):
def has_fuel(self):
return self.fuel_capacity > 0

def get_status(self):
def get_status(self) -> CarStatus:
if self.status is not None:
return self.status
logger.error("status of %s is None", self.vin)
Expand All @@ -69,6 +70,11 @@ def get_abrp_name(self):
return self.abrp_name
raise ValueError("ABRP model is not set")

def get_osmand_id(self):
if self.osmand_id is not None:
return self.osmand_id
return self.vin

@property
def status(self) -> CarStatus:
return self._status
Expand Down
3 changes: 3 additions & 0 deletions psa_car_controller/web/view/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

REFRESH_SWITCH = "refresh-switch"
ABRP_SWITCH = 'abrp-switch'
OSMANDAPI_SWITCH = 'osmandapi-switch'
CHARGE_SWITCH = "charge-switch"
PRECONDITIONING_SWITCH = "preconditioning-switch"

Expand Down Expand Up @@ -72,5 +73,7 @@ def get_control_tabs(config):
if not config.offline:
buttons_row.append(Switch(ABRP_SWITCH, car.vin, "Send data to ABRP", myp.abrp.enable_abrp,
car.vin in config.myp.abrp.abrp_enable_vin).get_html())
buttons_row.append(Switch(OSMANDAPI_SWITCH, car.vin, "Send data to OsmAndApi", myp.osmandapi.enable_osmand,
car.vin in config.myp.osmandapi.osmand_enable_vin).get_html())
tabs.append(dbc.Tab(label=label, id="tab-" + car.vin, children=[dbc.Row(buttons_row), *el]))
return dbc.Tabs(id="control-tabs", children=tabs)

0 comments on commit f34238d

Please sign in to comment.