Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a4b8e7d
Fix issue with pypi reading rst as markdown.
pjt0620 Sep 23, 2019
074d10f
Merge pull request #115 from openxc/pypi_fix
pjt0620 Sep 23, 2019
36b98fb
implemented signal frequency
SamaVinod Oct 21, 2019
f799ad5
Small bug fixes
Oct 21, 2019
8f75cfb
Removed dir import
Oct 21, 2019
8fda317
Added import for fatal_error
Oct 21, 2019
c4f7181
Merge remote-tracking branch 'origin/next' into Signalfrequency
Oct 21, 2019
fcd4e4c
fix the unit test
SamaVinod Oct 22, 2019
b739207
fix curley braces in json
SamaVinod Oct 23, 2019
9f1117f
added handlers in name
SamaVinod Oct 23, 2019
e77a42c
Merge pull request #117 from openxc/Signalfrequency
cwbaldwin Oct 23, 2019
f7bcaa1
Added initial code for web dashboard
pjt0620 Nov 14, 2019
8f4313e
Fix missing template folder on install
pjt0620 Nov 14, 2019
a503bea
Port dashboard tool to JS
Nov 18, 2019
31047b7
Merge branch 'webdashtool' of https://github.com/openxc/openxc-python…
Nov 18, 2019
d8327b0
Firmware Error Handling
SamaVinod Nov 18, 2019
8375a34
fix the unit test
SamaVinod Nov 18, 2019
50f40c0
Update .travis.yml
pjt0620 Nov 18, 2019
3a91938
Update .travis.yml
pjt0620 Nov 18, 2019
e6cbee2
Update .travis.yml
pjt0620 Nov 18, 2019
b9c0006
Update .travis.yml
pjt0620 Nov 18, 2019
087ab0e
Update .travis.yml
pjt0620 Nov 18, 2019
9fb3a6d
Update .travis.yml
pjt0620 Nov 18, 2019
165f5b6
Update .travis.yml
pjt0620 Nov 18, 2019
7433b8d
Update .travis.yml
pjt0620 Nov 18, 2019
05069e3
Update .travis.yml
pjt0620 Nov 18, 2019
fae9015
Update .travis.yml
pjt0620 Nov 18, 2019
087baf2
Update .travis.yml
pjt0620 Nov 18, 2019
d560a8f
Dashboard displays simple vehicle messages
Nov 18, 2019
af84cd5
Merge pull request #119 from openxc/webdashtool
mstocke Nov 18, 2019
5b75501
Update .travis.yml
pjt0620 Nov 18, 2019
91f235a
Update .travis.yml
pjt0620 Nov 18, 2019
a95a8cb
Update README.rst
pjt0620 Nov 18, 2019
78fc058
Update version.py
pjt0620 Nov 18, 2019
dc89215
Update README.rst
pjt0620 Nov 18, 2019
7607817
Update .travis.yml
pjt0620 Nov 18, 2019
78ea76d
Update .travis.yml
pjt0620 Nov 18, 2019
9df3406
Merge branch 'next' into TravisPypi
pjt0620 Nov 18, 2019
4a08fe8
Merge pull request #120 from openxc/TravisPypi
mstocke Nov 18, 2019
db1e751
fix the duplicate error
SamaVinod Nov 18, 2019
f6062b8
removed uncommented code
SamaVinod Nov 19, 2019
fa366a6
Merge branch 'next' into Errorhandling
pjt0620 Nov 19, 2019
e92d79a
Merge pull request #118 from openxc/Errorhandling
pjt0620 Nov 19, 2019
600c2f1
Update CHANGELOG.rst
pjt0620 Nov 25, 2019
6d15b80
Update README.rst
pjt0620 Nov 25, 2019
f5445f4
Update version.py
pjt0620 Nov 25, 2019
eecef8c
Update index.rst
pjt0620 Nov 25, 2019
1720a53
Merge pull request #121 from openxc/1.1.0
pjt0620 Nov 25, 2019
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
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ script: python setup.py test
after_success:
- coverage run --source=openxc setup.py test
- coveralls
deploy:
provider: pypi
server: https://pypi.org/legacy/
user: "__token__"
password: $PYPI_PASSWORD
on:
branch: master
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
OpenXC Python Library Changelog
===============================

v1.1.0
----------
* Feature: Generate firmware now checks for duplicate entries in json and for improperly used keys and unrecognised attributes.
* Feature: openxc-dashboad has been updated from a command line tool to a web server and webpage.
* Fix: Requiring windows curses on non windows system
* Fix: Various Python 3 bugs

v1.0.0
----------
* Remove python 2.7 support
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ include CONTRIBUTING.rst
include LICENSE
include openxc/generator/signals.cpp.footer
include openxc/generator/signals.cpp.header
include openxc/tools/templates/dashboard.html
17 changes: 9 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
OpenXC for Python
===============================================

.. image:: /docs/_static/logo.png
.. image:: https://github.com/openxc/openxc-python/raw/master/docs/_static/logo.png

:Version: 1.0.0
:Version: 1.1.0
:Web: http://openxcplatform.com
:Download: http://pypi.python.org/pypi/openxc/
:Documentation: http://python.openxcplatform.com
Expand All @@ -31,15 +31,16 @@ number of command-line tools for connecting to the CAN translator and
manipulating previously recorded vehicle data.

To package run "setup.py sdist bdist_wheel"
to push to pypi run "python -m twine upload dist/*"
to push to pypi run "python -m twine upload dist/\*"
Version files:
CHANGELOG.rst
README.rst
openxc/version.py
docs/index.rst

- CHANGELOG.rst
- README.rst
- openxc/version.py
- docs/index.rst

License
=======
========

Copyright (c) 2012-2017 Ford Motor Company

Expand Down
4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
OpenXC for Python
===================

.. image:: /_static/logo.png
.. image:: https://github.com/openxc/openxc-python/raw/master/docs/_static/logo.png

:Version: 1.0.0
:Version: 1.1.0
:Web: http://openxcplatform.com
:Download: http://pypi.python.org/pypi/openxc/
:Documentation: http://python.openxcplatform.com
Expand Down
17 changes: 17 additions & 0 deletions openxc/generator/structures.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import operator
import math
from collections import defaultdict
from openxc.utils import fatal_error

import logging

Expand Down Expand Up @@ -102,6 +103,14 @@ def id(self, value):
def merge_message(self, data):
self.bus_name = self.bus_name or data.get('bus', None)

message_attributes = dir(self)
message_attributes = [a.replace('bus_name', 'bus') for a in message_attributes]
data_attributes = list(data.keys())
extra_attributes = set(data_attributes) - set(message_attributes)

if extra_attributes:
fatal_error('ERROR: Message %s has unrecognized attributes: %s' % (data.get('id'), ', '.join(extra_attributes)))

if getattr(self, 'message_set'):
self.bus = self.message_set.lookup_bus(name=self.bus_name)
if not self.bus.valid():
Expand Down Expand Up @@ -353,6 +362,14 @@ def merge_signal(self, data):
"%s is deprecated and has no effect " % self.generic_name +
" - see the replacement, max_frequency")

signal_attributes = dir(self)
data_attributes = list(data.keys())
extra_attributes = set(data_attributes) - set(signal_attributes)

if extra_attributes:
fatal_error('ERROR: Signal %s in %s has unrecognized attributes: %s' % (self.name, self.message.name, ', '.join(extra_attributes)))


@property
def decoder(self):
decoder = getattr(self, '_decoder', None)
Expand Down
259 changes: 48 additions & 211 deletions openxc/tools/dashboard.py
Original file line number Diff line number Diff line change
@@ -1,211 +1,48 @@
""" This module contains the methods for the ``openxc-dashboard`` command line
program.

`main` is executed when ``openxc-dashboard`` is run, and all other callables in
this module are internal only.
"""



import argparse
import curses
import math
from datetime import datetime
from threading import Lock

from .common import device_options, configure_logging, select_device
from openxc.vehicle import Vehicle
from openxc.measurements import EventedMeasurement, Measurement

try:
str
except NameError:
# Python 3
str = str = str


# timedelta.total_seconds() is only in 2.7, so we backport it here for 2.6
def total_seconds(delta):
return (delta.microseconds + (delta.seconds
+ delta.days * 24 * 3600) * 10**6) / 10**6


# Thanks, SO: http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size
def sizeof_fmt(num):
for unit in ['bytes', 'KB', 'MB', 'GB', 'TB']:
if num < 1024.0:
return "%3.1f%s" % (num, unit)
num /= 1024.0


class DataPoint(object):
AVERAGE_FREQUENCY_ALPHA = 0.1

def __init__(self, measurement_type):
self.event = ''
self.current_data = None
self.events = {}
self.messages_received = 0
self.measurement_type = measurement_type
self.min = None
self.max = None
self.last_update_time = None
self.average_time_since_update = None

def update(self, measurement):
self.messages_received += 1
self.current_data = measurement

if self.last_update_time is not None:
time_since_update = total_seconds(datetime.now() - self.last_update_time)
if self.average_time_since_update is None:
self.average_time_since_update = time_since_update
else:
self.average_time_since_update = ((self.AVERAGE_FREQUENCY_ALPHA *
time_since_update) + (1 - self.AVERAGE_FREQUENCY_ALPHA) *
self.average_time_since_update)
self.last_update_time = datetime.now()

if getattr(self.current_data.value, 'unit', None) == self.current_data.unit:
if self.min is None or self.current_data.value < self.min:
self.min = self.current_data.value
elif self.max is None or self.current_data.value > self.max:
self.max = self.current_data.value

if isinstance(measurement, EventedMeasurement):
if measurement.valid_state():
self.events[measurement.value] = measurement.event

def percentage(self):
# TODO man, this is getting really ugly to handle all of the different
# types
percent = None
if hasattr(self.measurement_type, 'valid_range'):
percent = self.current_data.percentage_within_range()
elif (getattr(self, 'min', None) is not None and
getattr(self, 'max', None) is not None) and self.min != self.max:
percent = (((self.current_data.value - self.min) / float(self.max -
self.min)) * 100).num
return percent

def print_to_window(self, window, row, started_time):
width = window.getmaxyx()[1]
if self.current_data is not None:
if len(self.events) == 0:
value = str(self.current_data)
else:
result = ""
for item, value in enumerate(self.measurement_type.states):
# TODO missing keys here?
result += "%s: %s " % (value, self.events.get(value, "?"))
value = result

value_color = curses.color_pair(2)
window.addstr(row, 0, "{:>22} %s".format(value), value_color)
window.addstr(row, 23, self.current_data.name)

if width > 60:
message_count_color = curses.color_pair(3)
window.addstr(row, 50, "Msgs: " + str(self.messages_received),
message_count_color)

if width > 75 and self.average_time_since_update > 0:
window.addstr(row, 61, "Freq. (Hz): %d" %
math.ceil((1 / self.average_time_since_update)))


class Dashboard(object):
def __init__(self, window, vehicle):
self.window = window
self.elements = {}
self.scroll_position = 0
self.screen_lock = Lock()
vehicle.listen(Measurement, self.receive)

self.started_time = datetime.now()
self.messages_received = 0

curses.use_default_colors()
curses.init_pair(1, curses.COLOR_RED, -1)
curses.init_pair(2, curses.COLOR_GREEN, -1)
curses.init_pair(3, curses.COLOR_YELLOW, -1)

def receive(self, measurement, **kwargs):
if self.messages_received == 0:
self.started_time = datetime.now()
self.messages_received += 1

if measurement.name not in self.elements:
self.elements[measurement.name] = DataPoint(measurement.__class__)
self.elements[measurement.name].update(measurement)
self._redraw()

def _redraw(self):
self.screen_lock.acquire()
self.window.erase()
max_rows = self.window.getmaxyx()[0] - 4
for row, element in enumerate(sorted(list(self.elements.values()),
key=lambda elt: elt.current_data.name)[self.scroll_position:]):
if row > max_rows:
break
element.print_to_window(self.window, row, self.started_time)
self.window.addstr(max_rows + 1, 0,
"Message count: %d (%d corrupted)" % (self.messages_received,
self.source.corrupted_messages), curses.A_REVERSE)
self.window.addstr(max_rows + 2, 0,
"Total received: %s" %
sizeof_fmt(self.source.bytes_received),
curses.A_REVERSE)
self.window.addstr(max_rows + 3, 0, "Data Rate: %s" %
sizeof_fmt(self.source.bytes_received /
(total_seconds(datetime.now() - self.started_time)
+ 0.1)),
curses.A_REVERSE)
self.window.refresh()
self.screen_lock.release()

def scroll_down(self, lines):
self.screen_lock.acquire()
self.scroll_position = min(self.window.getmaxyx()[1],
self.scroll_position + lines)
self.screen_lock.release()

def scroll_up(self, lines):
self.screen_lock.acquire()
self.scroll_position = max(0, self.scroll_position - lines)
self.screen_lock.release()


def run_dashboard(window, source_class, source_kwargs):
vehicle = Vehicle()
dashboard = Dashboard(window, vehicle)
dashboard.source = source_class(**source_kwargs)
vehicle.add_source(dashboard.source)

window.scrollok(True)
while True:
c = window.getch()
if c == curses.KEY_DOWN:
dashboard.scroll_down(1)
elif c == curses.KEY_UP:
dashboard.scroll_up(1)
elif c == curses.KEY_NPAGE:
dashboard.scroll_down(25)
elif c == curses.KEY_PPAGE:
dashboard.scroll_up(25)



def parse_options():
parser = argparse.ArgumentParser(
description="View a real-time dashboard of all OpenXC measurements",
parents=[device_options()])
arguments = parser.parse_args()
return arguments


def main():
configure_logging()
arguments = parse_options()
source_class, source_kwargs = select_device(arguments)
curses.wrapper(run_dashboard, source_class, source_kwargs)
""" This module contains the methods for the ``openxc-dashboard`` command line
program.

`main` is executed when ``openxc-dashboard`` is run, and all other callables in
this module are internal only.
"""

import argparse
import threading
import logging
from flask import Flask
from flask import render_template
from flask_socketio import SocketIO

from openxc.interface import UsbVehicleInterface

log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
app = Flask(__name__)
socketio = SocketIO(app)

def vehicle_data_thread():
vi = UsbVehicleInterface(callback=send_data)
vi.start()

def send_data(data, **kwargs):
socketio.emit('vehicle data', data, broadcast=True)

@app.route("/")
def dashboard_static():
try:
vehicle_data_thread()
except:
pass
return render_template('dashboard.html')

def parse_options():
parser = argparse.ArgumentParser(
description="View a real-time dashboard of all OpenXC measurements",
parents=[device_options()])
arguments = parser.parse_args()
return arguments


def main():
socketio.start_background_task(vehicle_data_thread)
print("View the dashboard at http://127.0.0.1:5000")
app.run()
12 changes: 12 additions & 0 deletions openxc/tools/static/css/dashboard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
caption {
font-size: 1.5em;
}

table, th, td {
text-align: left;
border-spacing: 8px 1px;
}

.metric {
text-align: right;
}
Loading