Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow exporting trips/routes to a folder #59

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ lib
pip-selfcheck.json
.pytest_cache
pytype_output
local
8 changes: 6 additions & 2 deletions voc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Usage:
voc [-v|-vv] [options] list
voc [-v|-vv] [options] status
voc [-v|-vv] [options] trips [(--pretty|--json|--csv)]
voc [-v|-vv] [options] export-trips <export-folder>
voc [-v|-vv] [options] owntracks
voc [-v|-vv] [options] print [<attribute>]
voc [-v|-vv] [options] (lock | unlock)
Expand Down Expand Up @@ -148,7 +149,7 @@ async def main(args):

return await mqtt.run(connection, config)

journal = args["trips"] or args["dashboard"] or args["mqtt"]
journal = args["trips"] or args["dashboard"] or args["mqtt"] or args['export-trips']
res = await connection.update(journal=journal)

if not res:
Expand Down Expand Up @@ -192,7 +193,10 @@ async def main(args):
trip["endPosition"]["city"],
)
)

elif args["export-trips"]:
export_folder = args['<export-folder>']
exported, skipped = await connection.export_trips(folder=export_folder)
print("Exported %i trips, %i trips already existed (use -v for more details)" % (exported, skipped))
elif args["print"]:
attr = args["<attribute>"]
if attr:
Expand Down
93 changes: 92 additions & 1 deletion volvooncall/volvooncall.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Communicate with VOC server."""

import json
import logging
from datetime import timedelta
from json import dumps as to_json
from collections import OrderedDict
from os import makedirs, rename
from os.path import join, exists
from sys import argv
from urllib.parse import urljoin
import asyncio
import itertools
from random import randint

from aiohttp import ClientSession, ClientTimeout, BasicAuth
from aiohttp.hdrs import METH_GET, METH_POST
Expand All @@ -28,6 +32,10 @@
SERVICE_URL = "https://vocapi{region}.wirelesscar.net/customerapi/rest/v3.0/"
DEFAULT_SERVICE_URL = SERVICE_URL.format(region="")

# 5 days * 24 hours * 60 minutes * 5 waypoints per minute / 1000 pages
# => 36 pages, should be sufficent
MAX_PAGE_COUNTER_ROUTES = 36

HEADERS = {
"X-Device-Id": "Device",
"X-OS-Type": "Android",
Expand Down Expand Up @@ -124,6 +132,89 @@ async def update_vehicle(self, vehicle, journal=False):
if journal:
self._state[url].update(await self.get("trips", rel=url))

async def export_trips(self, folder):
"""
export trips/routes of all vehicles to the given folder

update(journal=true) must be called before calling this method
"""
exported = 0
skipped = 0
for vehicle in self.vehicles:
this_exported, this_skipped = await self.export_trips_of_vehicle(vehicle, folder=folder)
exported += this_exported
skipped += this_skipped
return exported, skipped

async def export_trips_of_vehicle(self, vehicle, folder):
exported = 0
skipped = 0
out_path = join(folder, vehicle.vin)
makedirs(out_path, exist_ok=True)

for trip in vehicle.trips:
was_exported = await self.export_single_trip_to_folder(vehicle, trip, out_path)
if was_exported:
exported += 1
else:
skipped += 1

return exported, skipped

async def export_single_trip_to_folder(self, vehicle, trip, out_path):
trip_start_time = trip['tripDetails'][0]['startTime']
trip_ident = "%s_%s" % (trip_start_time.isoformat(), trip['id'])
trip_folder = join(out_path, trip_ident)

if exists(trip_folder):
_LOGGER.info("EXPORT: SKIP %s: is already exported" % trip_ident)
return False

# first generate into a temp folder
trip_folder_tmp = trip_folder + "-tmp-%x" % (randint(0, 10000))
makedirs(trip_folder_tmp)

trip_file = join(trip_folder_tmp, "trip.json")
waypoints_file = join(trip_folder_tmp, "waypoints.json")

await self.write_trip_file(trip_file, trip)

if 'routeDetails' in trip:
waypoints_count = await self.write_waypoints_file(vehicle, trip, waypoints_file)
else:
waypoints_count = 0

rename(trip_folder_tmp, trip_folder)
_LOGGER.info(("EXPORT: DONE %s: exported (%i waypoints)" % (trip_ident, waypoints_count)))
return True

async def write_waypoints_file(self, vehicle, trip, waypoints_file):
with open(waypoints_file, "w") as fh:
waypoints = await self._fetch_waypoints_for_trip(trip['id'], vehicle._url)
json.dump(waypoints, fh, indent=4, sort_keys=True, default=str)
return len(waypoints)

async def write_trip_file(self, trip_file, trip):
with open(trip_file, "w") as fh:
json.dump(trip, fh, indent=4, sort_keys=True, default=str)

async def _fetch_waypoints_for_trip(self, trip_id, rel_url):
waypoints = []
for page in itertools.count():
route_url = "trips/%i/route?page=%i&page_size=1000" % (trip_id, page)
route_details_page = await self.get(route_url, rel=rel_url)

this_page_waypoints = route_details_page['waypoints']
if len(this_page_waypoints) == 0:
break

if page > MAX_PAGE_COUNTER_ROUTES:
raise RuntimeError("Fetched more than %i pages, something is broken for %s" % (
page, route_url))

waypoints.extend((this_page_waypoints))
return waypoints

@property
def vehicles(self):
"""Return vehicle state."""
Expand Down