Skip to content

Commit

Permalink
vehicle: add basic support for querying data elements and sampling
Browse files Browse the repository at this point in the history
This commit adds basic support in the library and in the CLI for
sampling vehicle data from the device (not streaming at the moment).
Here's an example showing the items available and their current values
using the CLI.  Note that unavailable items (there are many) are ignored
in this case using grep:

```
$ wva vehicle list --value | grep -v Unavailable
AccelPedalLowIdle = 3
AccelPedalPosition = 92.0
BatteryPotential = 690.549988
CruiseControlSetSpeed = 255.0
CruiseControlStatus = 7
EngineCoolantTemp = 162.0
EngineManifoldPressure = 414.0
EngineOilPressure = 808.0
EnginePercentLoadAtCurrentSpeed = 100.0
EngineSpeed = 6425.5
FuelEconomy = 42.620319
FuelLevel = 84.800003
FuelRate = 227.100006
PTOStatus = 31
ParkingBrake = 0
Throttle = 102.0
TotalDistance = 3750.0
TotalEngineHours = 950.0
TripDistance = 1875.0
VehicleSpeed = 178.195618
```
  • Loading branch information
Paul Osborne authored and Paul Osborne committed Mar 20, 2015
1 parent ef6f8bd commit b167bae
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 13 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
six==1.9.0
requests==2.6.0
click==3.3
arrow==0.5.4
70 changes: 66 additions & 4 deletions wva/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
# Copyright (c) 2015 Digi International Inc. All Rights Reserved.
import json
import pprint
import time

from core import WVA
import click
import os
from wva.exceptions import WVAError, WVAHttpServiceUnavailableError


def load_config(ctx):
Expand Down Expand Up @@ -62,6 +64,17 @@ def get_password(ctx, override_password=None):
return get_config_value(ctx, 'password', 'Password', override_password, password=True)


def get_root_ctx(ctx):
while True:
if hasattr(ctx, 'wva'):
return ctx
ctx = ctx.parent


def get_wva(ctx):
return get_root_ctx(ctx).wva


@click.group()
@click.option('--hostname', default=None, help='Force use of the specified hostname')
@click.option('--username', default=None, help='Force use of the specified username')
Expand All @@ -82,18 +95,19 @@ def cli(ctx, hostname, username, password, config_dir):
ctx.wva = WVA(ctx.hostname, ctx.username, ctx.password)


## Low-Level HTTP Access Commands
@cli.command()
@click.argument('path')
@click.pass_context
def get(ctx, path):
http_client = ctx.parent.wva.get_http_client()
http_client = get_wva(ctx).get_http_client()
print(http_client.get(path))


@cli.command()
@click.argument('path')
def delete(ctx, path):
http_client = ctx.parent.wva.get_http_client()
http_client = get_wva(ctx).get_http_client()
print(http_client.delete(path))


Expand All @@ -102,7 +116,7 @@ def delete(ctx, path):
@click.argument('input_file', type=click.File())
@click.pass_context
def post(ctx, path, input_file):
http_client = ctx.parent.wva.get_http_client()
http_client = get_wva(ctx).get_http_client()
print(http_client.post(path, input_file.read()))


Expand All @@ -111,10 +125,58 @@ def post(ctx, path, input_file):
@click.argument('input_file', type=click.File())
@click.pass_context
def put(ctx, path, input_file):
http_client = ctx.parent.wva.get_http_client()
http_client = get_wva(ctx).get_http_client()
print(http_client.put(path, input_file.read()))


## Vehicle Data Command Group (wva vehicle ...)
@cli.group(short_help="Vehicle Data Commands")
@click.pass_context
def vehicle(ctx):
pass


@vehicle.command(short_help="List available vehicle data items")
@click.option("--value/--no-value", default=False, help="Get the currently value as well")
@click.option('--timestamp/--no-timestamp', default=False, help="Also print the timestamp of the sample")
@click.pass_context
def list(ctx, value, timestamp):
for name, element in sorted(get_wva(ctx).get_vehicle_data_elements().items(), key=lambda (k, v): k):
if value:
try:
curval = element.sample()
except WVAHttpServiceUnavailableError:
print("{} (Unavailable)".format(name))
except WVAError, e:
print("{} (Error: {})".format(name, e))
else:
if timestamp:
print("{} = {} at {}".format(name, curval.value, curval.timestamp.ctime()))
else:
print("{} = {}".format(name, curval.value))
else:
print(name)


@vehicle.command(short_help="Get the current value of a vehicle data element")
@click.argument('element')
@click.option('--timestamp/--no-timestamp', default=False, help="Also print the timestamp of the sample")
@click.option('--repeat', default=1, help="How many times to sample")
@click.option('--delay', default=0.5, help="Time to delay between samples in seconds")
@click.pass_context
def sample(ctx, element, timestamp, repeat, delay):
element = get_wva(ctx).get_vehicle_data_element(element)
for i in xrange(repeat):
curval = element.sample()
if timestamp:
print("{} at {}".format(curval.value, curval.timestamp.ctime()))
else:
print("{}".format(curval.value))

if i + 1 < repeat: # do not delay on last iteration
time.sleep(delay)


def main():
cli(auto_envvar_prefix="WVA")

Expand Down
38 changes: 30 additions & 8 deletions wva/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2015 Digi International Inc. All Rights Reserved.

from wva.http_client import WVAHttpClient
from wva.vehicle import VehicleDataElement


class WVA(object):
Expand Down Expand Up @@ -46,11 +48,31 @@ def get_http_client(self):
"""Get a direct reference to the http client used by this WVA instance"""
return self._http_client

def get_all_config(self):
config = {}
for path in self._http_client.get("config")["config"]:
if "factory_default" in path:
continue # this one doesn't work
data = self._http_client.get(path)
config[path.split("/")[1]] = data
return config
def get_vehicle_data_element(self, name):
"""Return a :class:`VehicleDataElement` with the given name
For example, if I wanted to get information about the speed of a vehicle,
I could do so by doing the following::
speed = wva.get_vehicle_data_element("VehicleSpeed")
print(speed.get_value())
"""
return VehicleDataElement(self._http_client, name)


def get_vehicle_data_elements(self):
"""Get a dictionary mapping names to :class:`VehicleData` instances
This result is based on the results of `GET /ws/vehicle/data` that returns a list
of URIs to each vehicle data element. The value is a handle for accessing
additional information about a particular data element.
:raises WVAError: In the event of a problem retrieving the list of data elements
:returns: A dictionary of element names mapped to :class:`VehicleDataElement` instances.
"""
# Response looks like: { "data": ['vehicle/data/ParkingBrake', ...] }
elements = {}
for uri in self.get_http_client().get("vehicle/data").get("data", []):
name = uri.split("/")[-1]
elements[name] = self.get_vehicle_data_element(name)
return elements
1 change: 0 additions & 1 deletion wva/test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@


class TestWVA(WVATestBase):

def test_construction(self):
wva = WVA("host", "bob", "secrets")
self.assertEqual(wva.hostname, "host")
Expand Down
38 changes: 38 additions & 0 deletions wva/test/test_vehicle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2015 Digi International Inc. All Rights Reserved.

import datetime
from dateutil.tz import tzutc
from wva.test.test_utilities import WVATestBase


class TestVehicleDataElement(WVATestBase):
def test_get_vehicle_data_elements(self):
self.prepare_json_response("GET", "/ws/vehicle/data",
{
'data': [
'vehicle/data/ParkingBrake',
'vehicle/data/VehicleSpeed', # real one has way more...
]
})
els = self.wva.get_vehicle_data_elements()
self.assertEqual(len(els), 2)
self.assertEqual(set(els.keys()), {'ParkingBrake', 'VehicleSpeed'})
self.assertEqual(els["ParkingBrake"].name, "ParkingBrake")
self.assertEqual(els["ParkingBrake"].name, "ParkingBrake")

def test_sample(self):
self.prepare_json_response("GET", "/ws/vehicle/data/VehicleSpeed",
{'VehicleSpeed':
{
'timestamp': '2015-03-20T20:11:10Z',
'value': 170.664856
}
})
el = self.wva.get_vehicle_data_element("VehicleSpeed")
sample = el.sample()
self.assertAlmostEqual(sample.value, 170.664856)
self.assertEqual(sample.timestamp, datetime.datetime(2015, 3, 20, 20, 11, 10, tzinfo=tzutc()))
36 changes: 36 additions & 0 deletions wva/vehicle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2015 Digi International Inc. All Rights Reserved.

from collections import namedtuple
import arrow

VehicleDataSample = namedtuple('VehicleDataSample', ['value', 'timestamp'])


class VehicleDataElement(object):
"""Provides access to a particular vehicle data element"""

def __init__(self, http_client, element_name):
self.name = element_name
self._http_client = http_client

def sample(self):
"""Get the current value of this vehicle data element
The returned value will be a namedtuple with 'value' and
'timestamp' elements. Example::
speed_el = wva.get_vehicle_data_element('VehicleSpeed')
for i in xrange(10):
speed = speed_el.sample()
print("Speed: %0.2f @ %s" % (speed.value, speed.timestamp))
time.sleep(1)
"""
# Response: {'VehicleSpeed': {'timestamp': '2015-03-20T18:00:49Z', 'value': 223.368515}}
data = self._http_client.get("vehicle/data/{}".format(self.name))[self.name]
dt = arrow.get(data["timestamp"]).datetime
value = data["value"]
return VehicleDataSample(value, dt)

0 comments on commit b167bae

Please sign in to comment.