bicycle_statistics: Transformed to file system event driven service
Signed-off-by: Thomas Klaehn <thomas.klaehn@u-blox.com>
This commit is contained in:
parent
492c67bec3
commit
b4d4d844fd
@ -1,6 +1,12 @@
|
||||
import argparse
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from gpx2html import Gpx2Html
|
||||
from input_observer import InputObserver
|
||||
|
||||
def parse_args():
|
||||
'''Shell argument parser.'''
|
||||
@ -9,10 +15,48 @@ def parse_args():
|
||||
parser.add_argument('outfolder', help='Specify the out folder.')
|
||||
return parser.parse_args()
|
||||
|
||||
class myLoop(threading.Thread):
|
||||
def __init__(self, infolder, outfolder):
|
||||
super(myLoop, self).__init__()
|
||||
self.run_condition = True
|
||||
self.infolder = infolder
|
||||
self.outfolder = outfolder
|
||||
self.observer = Observer()
|
||||
self.in_obs = InputObserver(patterns=["*.gpx"])
|
||||
|
||||
def run(self):
|
||||
gpx2html = Gpx2Html(self.infolder, self.outfolder)
|
||||
gpx2html.process()
|
||||
|
||||
self.observer.schedule(self.in_obs, self.infolder)
|
||||
self.observer.start()
|
||||
while self.run_condition is True:
|
||||
new_file = self.in_obs.get_new_file()
|
||||
if new_file:
|
||||
gpx2html.process()
|
||||
print "processed"
|
||||
|
||||
def stop(self):
|
||||
self.run_condition = False
|
||||
self.in_obs.stop()
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
gpx2html = Gpx2Html(args.infolder, args.outfolder)
|
||||
gpx2html.process()
|
||||
|
||||
my_loop = myLoop(args.infolder, args.outfolder)
|
||||
my_loop.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
my_loop.stop()
|
||||
my_loop.join()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
@ -47,99 +47,37 @@ class Gpx2Html(object):
|
||||
|
||||
def __init__(self, infolder, outfolder):
|
||||
self.infolder = infolder
|
||||
self.outfolder = outfolder
|
||||
self.outfolder = os.path.abspath(outfolder)
|
||||
self.distance_diag_file = 'distance.png'
|
||||
self.distance_diag_abs = os.path.join(self.outfolder, self.distance_diag_file)
|
||||
self.avg_spd_diag_file = 'avg_spd.png'
|
||||
self.avg_spd_diag_abs = os.path.join(self.outfolder, self.avg_spd_diag_file)
|
||||
self.last_n_days_diag_file = None
|
||||
self.html_file = os.path.join(self.outfolder, 'index.html')
|
||||
self.years_distance = list()
|
||||
self.years_avg_spd = list()
|
||||
self.years = list()
|
||||
|
||||
|
||||
def process(self):
|
||||
self.tracks = gpx_parser.Tracks(self.infolder)
|
||||
years_distance = list()
|
||||
years_avg_spd = list()
|
||||
years = list()
|
||||
for year in range(2017, datetime.datetime.now().year + 1):
|
||||
month_avg_spd = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
month_distance = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
month_duration = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
for month in range(1, 13):
|
||||
if month == 12:
|
||||
max_day = (datetime.date(year + 1, 1, 1) - datetime.timedelta(days=1)).day
|
||||
else:
|
||||
max_day = (datetime.date(year, month + 1, 1) - datetime.timedelta(days=1)).day
|
||||
for day in range(1, max_day + 1):
|
||||
date_tracks = self.tracks.get(year, month, day)
|
||||
for track in date_tracks:
|
||||
month_distance[month] += (track.distance / 1000) # km
|
||||
month_duration[month] += track.duration.total_seconds() / 3600 # h
|
||||
for i in range(1, 13):
|
||||
if month_duration[i] > 0:
|
||||
month_avg_spd[i] = month_distance[i] / month_duration[i]
|
||||
years_distance.append(month_distance.values())
|
||||
years_avg_spd.append(month_avg_spd.values())
|
||||
years.append(str(year))
|
||||
def plot_distance_diagram(self):
|
||||
plot_bar_chart(self.years, self.MONTH_LABELS, self.years_distance,
|
||||
'Distance', 'Month', 'km', self.distance_diag_abs)
|
||||
|
||||
out_folder = os.path.abspath(self.outfolder)
|
||||
|
||||
dst_file_name = 'distance.png'
|
||||
plot_bar_chart(years, self.MONTH_LABELS, years_distance, 'Distance', 'Month', 'km', os.path.join(out_folder, dst_file_name))
|
||||
def plot_avg_spd_diagram(self):
|
||||
plot_bar_chart(self.years, self.MONTH_LABELS, self.years_avg_spd,
|
||||
'Average Speed', 'Month', 'km/h', self.avg_spd_diag_abs)
|
||||
|
||||
avg_file_name = 'avg_spd.png'
|
||||
plot_bar_chart(years, self.MONTH_LABELS, years_avg_spd, 'Average Speed', 'Month', 'km/h', os.path.join(out_folder, avg_file_name))
|
||||
|
||||
# last n days
|
||||
n = 14
|
||||
end_date = datetime.date.today()
|
||||
start_date = end_date - datetime.timedelta(days=n)
|
||||
dates = pd.date_range(start_date, end_date)
|
||||
def plot_last_n_days(self, day_count):
|
||||
plot_bar_chart(["Distance", "Average speed"], self.date_distance.keys(),
|
||||
[self.date_distance.values(), self.date_avg_spd.values()],
|
||||
'Last {} days'.format(day_count), 'Date', 'km, km/h',
|
||||
os.path.join(self.outfolder, self.last_n_days_diag_file), 90)
|
||||
|
||||
date_distance = dict()
|
||||
date_duration = dict()
|
||||
date_avg_spd = dict()
|
||||
|
||||
for date in dates:
|
||||
date_str = "{0:04d}-{1:02d}-{2:02d}".format(date.year, date.month, date.day)
|
||||
date_tracks = self.tracks.get(date.year, date.month, date.day)
|
||||
for track in date_tracks:
|
||||
try:
|
||||
current_dist = date_distance[date_str]
|
||||
current_duration = date_duration[date_str]
|
||||
except KeyError:
|
||||
current_dist = 0
|
||||
current_duration = 0
|
||||
current_dist += track.distance / 1000
|
||||
date_distance.update({date_str:current_dist})
|
||||
current_duration += track.duration.total_seconds() / 3600
|
||||
date_duration.update({date_str:current_duration})
|
||||
# check for empty dates
|
||||
try:
|
||||
current_dist = date_distance[date_str]
|
||||
current_duration = date_duration[date_str]
|
||||
except KeyError:
|
||||
date_distance.update({date_str:0})
|
||||
date_duration.update({date_str:0})
|
||||
|
||||
date_duration = collections.OrderedDict(sorted(date_duration.items()))
|
||||
|
||||
for key, value in date_duration.items():
|
||||
if value == 0:
|
||||
date_avg_spd.update({key:0})
|
||||
else:
|
||||
avg_spd = date_distance[key] / value
|
||||
date_avg_spd.update({key:avg_spd})
|
||||
|
||||
date_avg_spd = collections.OrderedDict(sorted(date_avg_spd.items()))
|
||||
date_distance = collections.OrderedDict(sorted(date_distance.items()))
|
||||
|
||||
dst_n_file_name = "distance_last_{}_days.png".format(n)
|
||||
plot_bar_chart(["Distance", "Average speed"],
|
||||
date_distance.keys(),
|
||||
[date_distance.values(), date_avg_spd.values()],
|
||||
'Last {} days'.format(n),
|
||||
'Date',
|
||||
'km, km/h',
|
||||
os.path.join(out_folder, dst_n_file_name),
|
||||
90)
|
||||
|
||||
html_file = os.path.join(out_folder, 'index.html')
|
||||
with open(html_file, 'w') as handle:
|
||||
def write_html_file(self):
|
||||
with open(self.html_file, 'w') as handle:
|
||||
handle.write('<!DOCTYPE html>\n')
|
||||
handle.write('<html>\n')
|
||||
handle.write('<head>\n')
|
||||
@ -161,24 +99,102 @@ class Gpx2Html(object):
|
||||
|
||||
handle.write('<table>\n')
|
||||
handle.write('<tr>\n')
|
||||
for year in years:
|
||||
for year in self.years:
|
||||
handle.write('<th>{}</th>\n'.format(year))
|
||||
handle.write('</tr>\n')
|
||||
|
||||
handle.write('<tr>\n')
|
||||
for i in range(len(years_distance)):
|
||||
handle.write('<td>{} km</td>\n'.format(round(sum(years_distance[i]), 1)))
|
||||
for i in range(len(self.years_distance)):
|
||||
handle.write('<td>{} km</td>\n'.format(round(sum(self.years_distance[i]), 1)))
|
||||
handle.write('</tr>\n')
|
||||
handle.write('</table>\n')
|
||||
|
||||
handle.write('</p>\n')
|
||||
|
||||
handle.write('<p>\n')
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(dst_file_name))
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(avg_file_name))
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(dst_n_file_name))
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(self.distance_diag_file))
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(self.avg_spd_diag_file))
|
||||
handle.write('<IMG SRC="{}" ALT="Distance">\n'.format(self.last_n_days_diag_file))
|
||||
handle.write('</p>\n')
|
||||
|
||||
handle.write('</body>\n')
|
||||
handle.write('<center>\n')
|
||||
handle.write('</html>\n')
|
||||
|
||||
|
||||
def process(self):
|
||||
self.years[:] = []
|
||||
self.years_avg_spd[:] = []
|
||||
self.years_distance[:] = []
|
||||
self.tracks = gpx_parser.Tracks(self.infolder)
|
||||
for year in range(2017, datetime.datetime.now().year + 1):
|
||||
month_avg_spd = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
month_distance = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
month_duration = {1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0}
|
||||
for month in range(1, 13):
|
||||
if month == 12:
|
||||
max_day = (datetime.date(year + 1, 1, 1) - datetime.timedelta(days=1)).day
|
||||
else:
|
||||
max_day = (datetime.date(year, month + 1, 1) - datetime.timedelta(days=1)).day
|
||||
for day in range(1, max_day + 1):
|
||||
date_tracks = self.tracks.get(year, month, day)
|
||||
for track in date_tracks:
|
||||
month_distance[month] += (track.distance / 1000) # km
|
||||
month_duration[month] += track.duration.total_seconds() / 3600 # h
|
||||
for i in range(1, 13):
|
||||
if month_duration[i] > 0:
|
||||
month_avg_spd[i] = month_distance[i] / month_duration[i]
|
||||
self.years_distance.append(month_distance.values())
|
||||
self.years_avg_spd.append(month_avg_spd.values())
|
||||
self.years.append(str(year))
|
||||
|
||||
self.plot_distance_diagram()
|
||||
self.plot_avg_spd_diagram()
|
||||
|
||||
# last n days
|
||||
n = 14
|
||||
end_date = datetime.date.today()
|
||||
start_date = end_date - datetime.timedelta(days=n)
|
||||
dates = pd.date_range(start_date, end_date)
|
||||
|
||||
self.date_distance = dict()
|
||||
date_duration = dict()
|
||||
self.date_avg_spd = dict()
|
||||
|
||||
for date in dates:
|
||||
date_str = "{0:04d}-{1:02d}-{2:02d}".format(date.year, date.month, date.day)
|
||||
date_tracks = self.tracks.get(date.year, date.month, date.day)
|
||||
for track in date_tracks:
|
||||
try:
|
||||
current_dist = self.date_distance[date_str]
|
||||
current_duration = date_duration[date_str]
|
||||
except KeyError:
|
||||
current_dist = 0
|
||||
current_duration = 0
|
||||
current_dist += track.distance / 1000
|
||||
self.date_distance.update({date_str:current_dist})
|
||||
current_duration += track.duration.total_seconds() / 3600
|
||||
date_duration.update({date_str:current_duration})
|
||||
# check for empty dates
|
||||
try:
|
||||
current_dist = self.date_distance[date_str]
|
||||
current_duration = date_duration[date_str]
|
||||
except KeyError:
|
||||
self.date_distance.update({date_str:0})
|
||||
date_duration.update({date_str:0})
|
||||
|
||||
date_duration = collections.OrderedDict(sorted(date_duration.items()))
|
||||
|
||||
for key, value in date_duration.items():
|
||||
if value == 0:
|
||||
self.date_avg_spd.update({key:0})
|
||||
else:
|
||||
avg_spd = self.date_distance[key] / value
|
||||
self.date_avg_spd.update({key:avg_spd})
|
||||
|
||||
self.date_avg_spd = collections.OrderedDict(sorted(self.date_avg_spd.items()))
|
||||
self.date_distance = collections.OrderedDict(sorted(self.date_distance.items()))
|
||||
|
||||
self.last_n_days_diag_file = "distance_last_{}_days.png".format(n)
|
||||
self.plot_last_n_days(n)
|
||||
self.write_html_file()
|
||||
|
31
input_observer/__init__.py
Normal file
31
input_observer/__init__.py
Normal file
@ -0,0 +1,31 @@
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
|
||||
import threading
|
||||
|
||||
class InputObserver(PatternMatchingEventHandler):
|
||||
def __init__(self, patterns=None, ignore_patterns=None,
|
||||
ignore_directories=False, case_sensitive=False):
|
||||
super(InputObserver, self).__init__(patterns, ignore_patterns,
|
||||
ignore_directories, case_sensitive)
|
||||
|
||||
self.lock = threading.Lock()
|
||||
self.lock.acquire()
|
||||
self.new_filename = None
|
||||
self.run_condition = True
|
||||
|
||||
|
||||
def on_created(self, event):
|
||||
self.new_filename = event.src_path
|
||||
self.lock.release()
|
||||
|
||||
|
||||
def get_new_file(self):
|
||||
self.lock.acquire() # don't release - will be released with next on_created
|
||||
if self.run_condition == True:
|
||||
return self.new_filename
|
||||
else:
|
||||
return None
|
||||
|
||||
def stop(self):
|
||||
self.run_condition = False
|
||||
self.lock.release()
|
2
setup.py
2
setup.py
@ -10,7 +10,7 @@ NAME = 'bicycle-statistics'
|
||||
VERSION = '0.1.0'
|
||||
AUTHOR = 'Thomas Klaehn'
|
||||
EMAIL = 'tkl@blackfinn.de'
|
||||
PACKAGES = [NAME, 'gpx_parser', 'gpx2html']
|
||||
PACKAGES = ['bicycle_statistics', 'gpx_parser', 'gpx2html', 'input_observer']
|
||||
SCRIPTS = ['example-gpx-parser', 'bicycle-stat']
|
||||
|
||||
DAEMON_START_SCRIPT = os.path.join("/lib/systemd/system", "bicycle-stat.service")
|
||||
|
Loading…
Reference in New Issue
Block a user