Package: pylovens
pylovensPython client for the Lovens API.Withpylovensyou can access the data that is available in the Lovens app for bikes with aGPS unit.UsageUsing the client is as simple as:frompylovensimportLovensClientclient=LovensClient("your_username","your_secret_password")bikes=client.get_bikes()Note:only authentication with email/password is supported.MethodsTheLovensClientexposes the following methods:Userget_userBikesget_bikesget_bikeget_stateget_healthRidesiterate_ridesget_ridesget_rideget_locationBatteryget_battery_stateget_battery_statisticsStatisticsget_statisticsGeofencesget_geofencesget_geofenceget_geofence_statsUserget_user- Get information on the user.defget_user(self)->dictReturnsAn extensive dictionary with details on the user, including the following keys and many more:{"id":1234,"name":"Your Name","creation_date":datetime(2023,4,30,23,59,59,tzinfo=ZoneInfo(key='Europe/Amsterdam'),"email":"your@mail.address","timezone":"Europe/Amsterdam",...}Bikesget_bikes- Fetch all bikes accessible to your user.defget_bikes(self)->list[dict]ReturnsA list of dictionaries of the following form:{'id':456,'user_id':1234,'user_name':'Your Name','active_state':0,'name':'Your Bikes Name','last_location':{<mostrecentavailableresultoftheget_locationmethod>},'battery_percentage':50,'owning_user':{<resultofget_usermethod>},'geofences':[<resultofget_geofencesmethod>],...}get_bike- Fetch a bike by its ID.defget_bike(self,bike_id:int)->dictArgumentsbike_id: The ID of the bike.ReturnsA dictionaries of the following form:{'id':456,'user_id':1234,'user_name':'Your Name','active_state':0,'name':'Your Bikes Name','last_location':{<mostrecentavailableresultoftheget_locationmethod>},'battery_percentage':50,'owning_user':{<resultofget_usermethod>},'geofences':[<resultofget_geofencesmethod>],...}get_state- Get the state of a bike.defget_state(self,bike_id:int)->dict[str]Argumentsbike_id: The ID of the bike.ReturnsA dictionary of the following form:{'powered_on':False,'ecu_locked':False,'erl_locked':False,'battery_percentage':50,'charging':False,'last_full_charge':datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),'odometer':300,'range':30}get_health- Get bike health status.defget_health(self,bike_id:int)->list[dict]Argumentsbike_id: The ID of the bike.ReturnsA list of four dictionaries:[{'key':'last_connection','status':True,'value':datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),'value_type':'datetime'},{'key':'last_gps','status':False,'value':datetime(2023,3,31,16,51,22,tzinfo=ZoneInfo(key='Europe/Amsterdam')),'value_type':'datetime'},{'key':'gps_battery','status':True,'value':'75%','value_type':'string'},{'key':'bike_system','status':True,'value':'true','value_type':'bool'}]Ridesiterate_rides- Iterate through the rides of a bike.defiterate_rides(self,bike_id:int,newest_first:bool=True,batch_size:int=50,_offset:int=0)->Iterable[dict]Argumentsbike_id: The ID of the bike.newest_first: If True, fetch the most recent ride first. Defaults toTrue.batch_size: The number of rides to fetch at once._offset: Used in pagination.ReturnsAn iterable of dictionaries describing the rides.
Each dictionary contains, among others, the following keys:{"id":123456,"start_date":datetime(2023,4,1,17,1,0,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"end_date":datetime(2023,4,1,17,6,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"calories":14,"avg_speed":21,"distance_traveled":1234,"bike_id":123,"user_id":1234,"user":{<sameasoutputofget_user()>},"creation_date":datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"active_time":330,"timezone":"Europe/Amsterdam",...}get_rides- Fetch a list of rides of a bike.defget_rides(self,bike_id:int,newest_first:bool=True,n:int=50)->list[dict]If you are interested in fetching all rides of a bike, or are not sure how many you need,
consider usingiterate_rides.Argumentsbike_id: The ID of the bike.newest_first: If True, fetch the most recent ride first. Defaults to True.n: Number of rides to fetch. Defaults to 50.ReturnsAn list of dictionaries describing the rides.
Each dictionary contains, among others, the following keys:{"id":123456,"start_date":datetime(2023,4,1,17,1,0,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"end_date":datetime(2023,4,1,17,6,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"calories":14,"avg_speed":21,"distance_traveled":1234,"bike_id":123,"user_id":1234,"user":{<sameasoutputofget_user()>},"creation_date":datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"active_time":330,"timezone":"Europe/Amsterdam",...}get_ride- Fetch a ride by its ID.defget_ride(self,ride_id:int)->dictArgumentsride_id: The ID of the ride.ReturnsA dictionary describing the ride.
It contains, among others, the following keys:{"id":123456,"start_date":datetime(2023,4,1,17,1,0,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"end_date":datetime(2023,4,1,17,6,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"calories":14,"avg_speed":21,"distance_traveled":1234,"bike_id":123,"user_id":1234,"user":{<sameasoutputofget_user()>},"creation_date":datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"active_time":330,"timezone":"Europe/Amsterdam",...}get_location- Get location history in a time range.defget_location(self,bike_id:int,start_date:datetime|date,end_date:datetime|date)->list[dict,bool,datetime|int|float]Ifstart_dateand/orend_dateis a date object, they are interpreted as the start and end of the day
respectively. Timezone-naive datetime objects are converted to the user's timezone (seeget_user).Argumentsbike_id: The ID of the bike.start_date: Start date of a timespan.end_date: End date of a timespan.ReturnsA list of dictionaries of the following form:{"id":"123456b123",'lat':52.379189,'lon':4.899431,"date":datetime(2023,4,1,17,1,0,tzinfo=ZoneInfo(key='Europe/Amsterdam')),"speed":21,"battery_percentage":0,# Always 0 - never filled"bike_id":123,"is_moving":True}Batteryget_battery_state- Get the state of the battery of a bike.defget_battery_state(self,bike_id:int)->dict[str]Argumentsbike_id: The ID of the bike.ReturnsA dictionary of the following form:{'current':-450,'battery_percentage':69,'last_battery_update':datetime(2023,4,30,0,0,0,tzinfo=ZoneInfo(key='Europe/Amsterdam'),'last_full_charge':datetime(2023,4,30,0,0,0,tzinfo=ZoneInfo(key='Europe/Amsterdam'),'charging':False,'range':30}get_battery_statistics- Get historical state of the battery of a bike.defget_battery_statistics(self,bike_id:int,start_date:date|datetime|None=None,end_date:date|datetime|None=None,)If start_date is not provided or None, it defaults to 24 hours ago.
If end_date is not provided or None, it defaults to now.Ifstart_dateand/orend_dateis a date object, they are interpreted as the start and end of the day
respectively. Timezone-naive datetime objects are converted to the user's timezone (seeget_user).Argumentsbike_id: The ID of the bike.start_date: Optional start date or datetime.end_date: Optional end date or datetime (inclusive).ReturnsA list of dictionaries of the following form, in 15-minute intervals:{'date':datetime(2023,4,30,0,0,0,tzinfo=ZoneInfo(key='Europe/Amsterdam'),'battery_percentage':69,'charging':False,'seconds_in_ride':0,'seconds_charging':0,}Note thatbattery_percentageisNonewhen the battery is removed.Statisticsget_statistics- Get ride statistics for a bike.defget_statistics(self,bike_id:int,start_date:date|datetime,end_date:date|datetime,type:str="daily",)->list[dict]Ifstart_dateand/orend_dateis a date object, they are interpreted as the start and end of the day
respectively. Timezone-naive datetime objects are converted to the user's timezone (seeget_user).Note that this endpoint expects theend_date, if it is a datetime, to be one second before the start of the
next bin. E.g. daily statistics for the month of January 2023 can be obtained by callingget_statistics(123, start_date=datetime(2023, 1, 1, 0, 0), end_date=datetime(2023, 1, 31, 23, 59, 59)).
Passingdatetime(2023, 2, 1, 0, 0)asend_dateinstead will return in an extra 0-second bin at the end.Argumentsbike_id: The ID of the bike.start_date: Start date or datetime.end_date: End date or datetime (inclusive).type: Aggregation level. One of "hourly", "daily" or "monthly". Defaults to "daily".ReturnsA list of dictionaries of the following form:{'from':datetime(2023,4,30,0,0,0,tzinfo=ZoneInfo(key='Europe/Amsterdam'),'till':datetime(2023,4,30,23,59,59,tzinfo=ZoneInfo(key='Europe/Amsterdam'),'co2':2584,'calories':156,'avg_speed':18,'distance_traveled':10379,'avg_power_distribution':83,'shift_advice':0,'top_speed':28,'elevation_up':238,'elevation_down':232}Geofencesget_geofences- Fetch all geofences associated to a bike.defget_geofences(self,bike_id:int)->list[dict[str,datetime|dict[str,float]|int|str]]Argumentsbike_id: The ID of the bike.ReturnsA list of dictionaries of the following form:{'id':456,'bike_id':123,'user_id':1234,'name':'Amsterdam City Center','center':{'lat':52.379189,'lon':4.899431},'radius':200,'active_state':0,'creation_date':datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam'))}get_geofence- Get a single geofence by its ID.defget_geofence(self,geofence_id:int)->dict[str,datetime|dict[str,float]|int|str]Argumentsgeofence_id: The ID of the geofence.ReturnsA dictionary of the following form:{'id':456,'bike_id':123,'user_id':1234,'name':'Amsterdam City Center','center':{'lat':52.379189,'lon':4.899431},'radius':200,'active_state':0,'creation_date':datetime(2023,4,1,17,10,30,tzinfo=ZoneInfo(key='Europe/Amsterdam'))}get_geofence_stats- Get statistics of a geofence.defget_geofence_stats(self,geofence_id:int,start_date:datetime|date|None=None,end_date:datetime|date|None=None,)->dict[str,int]Argumentsgeofence_id: The ID of the geofence.start_date: Start date of a timespan. Optional, must be provided ifend_dateis provided.end_date: End date of a timespan. Optional, must be provided ifstart_dateis provided.ReturnsA dictionary of the following form:{'entries_all_time':1,'entries_in_timespan':0# Only if start_date and end_date provided.}