Coder Social home page Coder Social logo

fgebhart / workoutizer Goto Github PK

View Code? Open in Web Editor NEW
60.0 7.0 10.0 4.45 MB

:weight_lifting: Browser based Sport and Workout Organizer :running_woman:

License: MIT License

Dockerfile 0.28% Python 51.34% Shell 0.07% CSS 31.65% JavaScript 3.22% HTML 13.44%
sport python raspberry-pi plots django bokeh leaflet activity garmin

workoutizer's Introduction

Workoutizer

PyPI Python Build Status Coverage Badge Downloads

Workoutizer is a simple web application for organizing your workouts and sports activities. It is designed to work locally on any UNIX-like system running Python.

Track your activities to get an overview of your overall training, similar to platforms like strava or garmin connect - but without uploading all your sensitive health data to some 3rd party cloud.

Features

  • Automatic import of Garmin .fit files and .gpx files
  • Automatic naming of activities based on daytime, sport and geo location
  • Render your activity gps data on different OSM maps
  • Plot your activity specific data e.g. heart rate, pace, temperature, cadence and altitude
  • Integrate laps into both plots and maps
  • Connected plots and map via mouse hovering
  • Find sections with highest speed and max altitude gain using sportgems and highlight on map
  • Add untracked activities manually via the GUI
  • Export activities as .gpx files
  • Add as many different sports as you want

Status

Workoutizer is no longer actively maintained. Use it with caution and feel free to fork it.

Getting Started

Install workoutizer using pip

pip install workoutizer

Initialize workoutizer to provide some demo data and run it:

wkz init --demo
wkz run

See the help description of the CLI with wkz --help and subcommands, e.g.: wkz manage --help.

In case you want to run workoutizer on a Raspberry Pi in your local network, follow the Raspberry Pi setup instructions.

Gallery

Dashboard Sport Page
Activity Page 1/2 Activity Page 2/2

Changelog

See Changelog.

Contributing

Contributions are welcome - check out the Contribution Guidelines.

Thanks

Libraries and other tools used by Workoutizer:

workoutizer's People

Contributors

anyesh avatar dependabot[bot] avatar fabian-gebhart-by avatar fgebhart avatar gotiniens avatar paturiku-p avatar polyvertex avatar roysti10 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

workoutizer's Issues

[BUG] processing demo activity's does not complete

Describe the bug
When using the run_docker.sh script, and initializing the database I get an timeout error. SOmtetimes it seems to be doing nothing, and after pushing ctrl+c it show the timeout error.

To Reproduce
Steps to reproduce the behavior:

  1. run the run_docker.sh script
  2. wkz init --demo
  3. see error:
Traceback (most recent call last):
  File "/tmp/venv/lib/python3.8/site-packages/distributed/comm/core.py", line 285, in connect
    comm = await asyncio.wait_for(
  File "/usr/lib/python3.8/asyncio/tasks.py", line 490, in wait_for
    raise exceptions.TimeoutError()
asyncio.exceptions.TimeoutError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/tmp/venv/bin/wkz", line 33, in <module>
    sys.exit(load_entry_point('workoutizer', 'console_scripts', 'wkz')())
  File "/tmp/venv/lib/python3.8/site-packages/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/tmp/venv/lib/python3.8/site-packages/click/core.py", line 1062, in main
    rv = self.invoke(ctx)
  File "/tmp/venv/lib/python3.8/site-packages/click/core.py", line 1668, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/tmp/venv/lib/python3.8/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/tmp/venv/lib/python3.8/site-packages/click/core.py", line 763, in invoke
    return __callback(*args, **kwargs)
  File "/workspaces/workoutizer/workoutizer/cli.py", line 40, in init
    _init(import_demo_activities=demo)
  File "/workspaces/workoutizer/workoutizer/cli.py", line 163, in _init
    run_importer__dask(models, importing_demo_data=True)
  File "/workspaces/workoutizer/wkz/file_importer.py", line 389, in run_importer__dask
    sse.send(msg, "blue", "DEBUG")
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/spec.py", line 454, in __exit__
    super().__exit__(typ, value, traceback)
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/cluster.py", line 419, in __exit__
    return self.sync(self.__aexit__, typ, value, traceback)
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/cluster.py", line 192, in sync
    return sync(self.loop, func, *args, **kwargs)
  File "/tmp/venv/lib/python3.8/site-packages/distributed/utils.py", line 354, in sync
    raise exc.with_traceback(tb)
  File "/tmp/venv/lib/python3.8/site-packages/distributed/utils.py", line 337, in f
    result[0] = yield future
  File "/tmp/venv/lib/python3.8/site-packages/tornado/gen.py", line 762, in run
    value = future.result()
  File "/tmp/venv/lib/python3.8/site-packages/tornado/ioloop.py", line 741, in _run_callback
    ret = callback()
  File "/tmp/venv/lib/python3.8/site-packages/tornado/ioloop.py", line 765, in _discard_future_result
    future.result()
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/cluster.py", line 428, in __aexit__
    await f
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/spec.py", line 426, in _close
    await self._correct_state()
  File "/tmp/venv/lib/python3.8/site-packages/distributed/deploy/spec.py", line 341, in _correct_state_internal
    await self.scheduler_comm.retire_workers(workers=list(to_close))
  File "/tmp/venv/lib/python3.8/site-packages/distributed/core.py", line 791, in send_recv_from_rpc
    comm = await self.live_comm()
  File "/tmp/venv/lib/python3.8/site-packages/distributed/core.py", line 749, in live_comm
    comm = await connect(
  File "/tmp/venv/lib/python3.8/site-packages/distributed/comm/core.py", line 309, in connect
    raise IOError(
OSError: Timed out trying to connect to inproc://10.1.0.2/13/1 after 10 s

Additional context
It also happens when running the tests with `pytest tests/db_tests/test_cli.py, if I edit the tests file and remove the import tests all works well.

investigate the use of watchdog for monitoring trace dir for new files

Because of several issues when running the FileImporter in a separate process in background using multiprocessing the #44 PR removed the long running process. With these changes the FileImporter does no longer have a infinite while loop and only runs a single time (when triggered).

Currently only a newly connected device (garmin watch) will trigger the FileImporter when the device gets mounted. This leads to the problem, that new gpx files which are saved to the trace directory won't get imported.

In order to fix this it might be helpful to investigate watchdog and find out whether it could be used to watch the trace dir and trigger the FileImporter once new files are added.

Note: It should be verified, that the watchdog process is also stopped when running the cli command wkz stop to prevent dangling processes.

TST: add support for chrome

Currently only Firefox is supported. To additionally support Chrome, the following tasks should be completed:

  • install chrome driver to docker image (similar to how it is done for geckodriver)
  • extend webdriver fixture to use both Firefox and Chrome
  • ensure all tests in wizer/tests/integration_tests/browser_tests pass locally
  • ensure all tests in wizer/tests/integration_tests/browser_tests pass in CI

ENH: Provide feedback on progress of "Reimport" in UI

When reimporting activity files from the settings page by clicking "Reimport Files" the user has no idea about what happens in the background. Providing feedback on whether some files are reimported or no files at all have been found, would improve the user experience.

fix failing integration tests

Due to the migration from gitlab ci to github actions, some tests still fail and have been excluded for now. Fix these.

[ENH] Make workoutizer Windows compatible

To allow more people to use workoutizer, it would be great if it would be compatible with windows.

Help on this front is appreciated, since I'm not having direct access to a windows machine. Feel free to open up individual issues for specific problems or discuss general things within this issue.

[BUG] Rounding errors cause wkz.gis.geo distance calculation error.

Describe the bug
As I myself am not a particularly fast uphill rider, the two coordinates of a few consecutive points of an activity of mine (GPX file attached) were in such a close proximity, causing the rounding errors in function calculate_distance_between_points (@wkz.gis.geo:30) to sum the mathematical formula beyond 1 and cause function acos() (@wkz.gis.geo:33) to fail (Raising ValueError: math domain error).

distance = acos(

To Reproduce
Steps to reproduce the behavior:

  1. Download attached file and rename it to .GPX
  2. Upload it to local WKZ instance and wait for it to start processing
  3. The bug appears

Environment:

  • OS: Ubuntu 20.04.4 LTS
  • Browser Firefox 99.0
  • Workoutizer Version 0.25.0

Additional context
Attached sample GPX file as TXT: BMC.txt

Suggested workaround/fix
The following code is my "bodge" / "fix". I highly appreciate somebody having a second look and share their thought.
TLDR: The math formula is wrapped in max(0,min(FORMULA,1))

    # Original code:
#    distance = acos(
#        sin(_to_rad(coordinate_1[0])) * sin(_to_rad(coordinate_2[0]))
#        + cos(_to_rad(coordinate_1[0])) * cos(_to_rad(coordinate_2[0])) * cos(_to_rad(coordinate_1[1] - coordinate_2[1]))
#    )

    distance = acos(
      max(0,
        min(
          sin(_to_rad(coordinate_1[0])) * sin(_to_rad(coordinate_2[0])) 
            + cos(_to_rad(coordinate_1[0])) * cos(_to_rad(coordinate_2[0])) 
            * cos(_to_rad(coordinate_1[1] - coordinate_2[1]))
        ,1)
      )
    )

    # Back to original code:
    # multiply by earth radius (nominal "zero tide" equatorial) in centimeter
    return distance * 6378100

Edit
Hats down to the author for providing such a functional platform/portal. I was already making my own variant of this and found this one by accident, browsing for a gpx parsing pip package.

BUG: Error in filter when e.g. avg_speed is not available in fit file

Turns out some garmin models do not provide the overall average speed attribute in their fit file. The activity file is imported without any problem, but it leads to the following error when opening the activity in the browser:

03-10 06:44:50 - ERROR - django.request - Internal Server Error: /activity/20
Traceback (most recent call last):
  File "/tmp/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/tmp/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/tmp/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "/tmp/venv/lib/python3.8/site-packages/django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "/workspaces/workoutizer/wizer/activity_views.py", line 47, in get
    return render(request, self.template_name, {**context, **activity_context})
...
  File "/workspaces/workoutizer/wizer/templatetags/filters.py", line 44, in m_per_s_to_km_per_h
    return round(float(m_per_s) * 3.6, 1)
TypeError: float() argument must be a string or a number, not 'NoneType'
03-10 06:44:50 - ERROR - django.server - "GET /activity/20 HTTP/1.1" 500 476088

This was initially reported in #90.

  • This could be fixed by determining the avg (min and max) speed from the speed_list, which the fit parser has available as pandas series.

  • However, an additional save guard mechanism for simply not displaying attributes which are None would be good, since it is not foreseeable which attributes are always available and which not.

TST: Test results not consistent

Describe the bug
After merging #174 running the tests/db_tests/test_api.py::test_mount_device__success test does not produce consistent results. When running the tests with the -n 1 parameter the tests fail, the tests suceed when running with -n 2 or an higher number of threads.

To Reproduce
Steps to reproduce the behavior:

  1. start docker container with ./run_docker.sh
  2. Start test with pytest tests/db_tests/test_api.py -n 2 --no-header -v
  3. see All tests succeed
  4. start tests with pytest tests/db_tests/test_api.py -n 1 --no-header -v
  5. See the tests/db_tests/test_api.py::test_mount_device__success test fail

Aditionall context
The problem is two-fold:

  1. The code intruduced with #174 was not tested correctly, so I changed the test to account for the new _find_device_type function. You can find the PR for this in #176. I consider this fixed with that PR.
  2. The real problem is that this issue was not identified by the automatic tests. I have no clue why. A solution would be running the tests with 1 thread and with multiple threads, but that would still be an workaround imho.

[BUG] 920xt not mounting automatically

Describe the bug
Garmin Device 920xt is not mounted automatically after following Readme.md. Failing with an gio error
To Reproduce
Steps to reproduce the behavior:

  1. Follow Readme to install on Raspberry pi
  2. Connect 920XT
  3. See error:
06-22 21:48:50 - DEBUG - wkz.file_helper.fit_collector - trying to mount device...
06-22 21:48:50 - DEBUG - wkz.file_helper.fit_collector - found Garmin device in: Bus 001 Device 006: ID 091e:26e5 Garmin International 
gio: /dev/bus/usb/001/006: No volume for device file
06-22 21:48:50 - WARNING - wkz.file_helper.fit_collector - could not mount device: Command '['gio', 'mount', '-d', '/dev/bus/usb/001/006']' returned non-zero exit status 2.
Traceback (most recent call last):
  File "/home/pi/venv/lib/python3.7/site-packages/wkz/file_helper/fit_collector.py", line 92, in try_to_mount_device
    mount_output = _mount_device_using_gio(bus, dev)
  File "/home/pi/venv/lib/python3.7/site-packages/wkz/file_helper/fit_collector.py", line 112, in _mount_device_using_gio
    return subprocess.check_output(["gio", "mount", "-d", f"/dev/bus/usb/{bus}/{dev}"]).decode("utf-8")
  File "/usr/lib/python3.7/subprocess.py", line 395, in check_output
    **kwargs).stdout
  File "/usr/lib/python3.7/subprocess.py", line 487, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['gio', 'mount', '-d', '/dev/bus/usb/001/006']' returned non-zero exit status 2.
06-22 21:48:50 - ERROR - wkz.api - could not mount device, no valid mount path available - got: None
06-22 21:48:50 - ERROR - django.request - Internal Server Error: /mount-device/
06-22 21:48:50 - ERROR - django.channels.server - HTTP POST /mount-device/ 500 [0.12, 192.168.2.146:40772]

Additional context
I found a mention of the same problem in issue #90 , But I can not find the solution. I have the feeling it is because your device is mounted as an MTP device, and mine should be mounted as an block device.

Following the https://github.com/fgebhart/workoutizer/blob/main/setup/other/debugging.md I found the following info:

pi@raspberrypi:~ $ gio mount -d /dev/bus/usb/001/007
gio: /dev/bus/usb/001/007: No volume for device file
pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ udevadm info --name=/dev/bus/usb/001/007 --query=property
DEVPATH=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3
DEVNAME=/dev/bus/usb/001/007
DEVTYPE=usb_device
DRIVER=usb
PRODUCT=91e/26e5/509
TYPE=0/0/0
BUSNUM=001
DEVNUM=007
MAJOR=189
MINOR=6
SUBSYSTEM=usb
USEC_INITIALIZED=4529355716
ID_VENDOR=091e
ID_VENDOR_ENC=091e
ID_VENDOR_ID=091e
ID_MODEL=26e5
ID_MODEL_ENC=26e5
ID_MODEL_ID=26e5
ID_REVISION=0509
ID_SERIAL=091e_26e5
ID_BUS=usb
ID_USB_INTERFACES=:080650:
ID_VENDOR_FROM_DATABASE=Garmin International
pi@raspberrypi:~ $ cat /proc/partitions 
major minor  #blocks  name

   1        0       4096 ram0
   1        1       4096 ram1
   1        2       4096 ram2
   1        3       4096 ram3
   1        4       4096 ram4
   1        5       4096 ram5
   1        6       4096 ram6
   1        7       4096 ram7
   1        8       4096 ram8
   1        9       4096 ram9
   1       10       4096 ram10
   1       11       4096 ram11
   1       12       4096 ram12
   1       13       4096 ram13
   1       14       4096 ram14
   1       15       4096 ram15
 179        0   31226880 mmcblk0
 179        1     262144 mmcblk0p1
 179        2   30960640 mmcblk0p2
   8        0      11238 sda

My own debugging also resulted in some info:

pi@raspberrypi:~ $ gio mount --list --detail
Drive(0): USDU1
  Type: GProxyDrive (GProxyVolumeMonitorUDisks2)
  ids:
   unix-device: '/dev/mmcblk0'
  themed icons:  [drive-removable-media-flash-sd]  [drive-removable-media-flash]  [drive-removable-media]  [drive-removable]  [drive]  [drive-removable-media-flash-sd-symbolic]  [drive-removable-media-flash-symbolic]  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]
  symbolic themed icons:  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]  [drive-removable-media]  [drive-removable]  [drive]
  is_removable=1
  is_media_removable=1
  has_media=1
  is_media_check_automatic=1
  can_poll_for_media=0
  can_eject=0
  can_start=0
  can_stop=0
  start_stop_type=shutdown
  sort_key=00coldplug/12removable/mmcblk0
Drive(1): Garmin FR920 FLASH
  Type: GProxyDrive (GProxyVolumeMonitorUDisks2)
  ids:
   unix-device: '/dev/sda'
  themed icons:  [drive-removable-media-usb]  [drive-removable-media]  [drive-removable]  [drive]  [drive-removable-media-usb-symbolic]  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]
  symbolic themed icons:  [drive-removable-media-usb-symbolic]  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]  [drive-removable-media-usb]  [drive-removable-media]  [drive-removable]  [drive]
  is_removable=1
  is_media_removable=1
  has_media=1
  is_media_check_automatic=1
  can_poll_for_media=0
  can_eject=1
  can_start=0
  can_stop=0
  start_stop_type=shutdown
  sort_key=01hotplug/1624392348454994
  Volume(0): GARMIN
    Type: GProxyVolume (GProxyVolumeMonitorUDisks2)
    ids:
     class: 'device'
     unix-device: '/dev/sda'
     label: 'GARMIN'
    themed icons:  [drive-removable-media-usb]  [drive-removable-media]  [drive-removable]  [drive]  [drive-removable-media-usb-symbolic]  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]
    symbolic themed icons:  [drive-removable-media-usb-symbolic]  [drive-removable-media-symbolic]  [drive-removable-symbolic]  [drive-symbolic]  [drive-removable-media-usb]  [drive-removable-media]  [drive-removable]  [drive]
    can_mount=1
    can_eject=1
    should_automount=0
    sort_key=gvfs.time_detected_usec.1624392348562305

[BUG] Incorrect if statement causes block devices to never be mounted

Describe the bug
A bit embarrassing but an incorrect if statement in the fit_collector._find_device_type function causes Block devices to never be mounted

To Reproduce
Steps to reproduce the behavior:

  1. Run latest version
  2. connect block device
  3. see error:
07-10 11:03:59 - DEBUG - wkz.file_helper.fit_collector - trying to mount device...
07-10 11:03:59 - DEBUG - wkz.file_helper.fit_collector - found Garmin device in: Bus 001 Device 003: ID 091e:26e5 Garmin International 
07-10 11:03:59 - DEBUG - wkz.file_helper.fit_collector - Looking up type of device
07-10 11:03:59 - DEBUG - wkz.file_helper.fit_collector - Device is block device
07-10 11:03:59 - ERROR - wkz.api - could not mount device: cannot unpack non-iterable NoneType object
Traceback (most recent call last):
  File "/home/pi/venv/lib/python3.7/site-packages/wkz/api.py", line 18, in mount_device_endpoint
    mount_path = try_to_mount_device()
  File "/home/pi/venv/lib/python3.7/site-packages/wkz/file_helper/fit_collector.py", line 93, in try_to_mount_device
    (type, path) = _find_device_type(bus, dev)
TypeError: cannot unpack non-iterable NoneType object
07-10 11:03:59 - ERROR - django.request - Internal Server Error: /mount-device/
07-10 11:03:59 - ERROR - django.channels.server - HTTP POST /mount-device/ 500 [0.26, 192.168.2.182:52808]

FRONTEND: Add Datetime Picker for adding / editing Acitivites

Add some fancy datetime picker. Could be either a js widget or a django tool, similar to what is in place for the color picker.

For reference:

rendering sport icon on map fails in case lap has no coordinates

It is possible to set a lap marker on garmin devices even if the device does not have enough gps signal for determining the position. This leads to lap entries without any lat or lon values. In this case activity_map.html fails with

Uncaught ReferenceError: None is not defined

This could be solved by avoiding to pass laps without coordinate data to the view.

FRONTEND: Collapse button in sidebar does not properly adapt its position when zooming in/out

When zooming in / out the position of the "collapse" button in the (left) sidebar is not adjusting its position properly. Its position should be fixed to the bottom of the page. See the attached images:

Here the position of the collapse button is correct:
Screenshot 2020-12-23 at 10 06 51

When zooming out, the position of the button moves up instead of staying at the bottom of the page:
Screenshot 2020-12-23 at 10 07 07

When refreshing the page in the new zoom level, the position of the collapse button again correct. However, it would be nice to have the position fixed and adapt responsive properly when zooming in and out.

[BUG] Readme in setup dir does not install latest version of workoutizer

Describe the bug
The readme in the setup dir says you should use Raspian 10 (buster). Raspian 10 comes with python3.7.3 installed. And a higher version is not in the repositories.

Since version 0.7.1 and later workoutizer requires python version >=3.8, So 0.7.0 will be the newest version installed

To Reproduce
Steps to reproduce the behavior:

  1. Install a clean Raspian on an supported raspberry pi
  2. run pip install -vvv workoutizer
  3. Assert that the version being installed is 0.7.0
    Link requires a different Python (3.7.3 not in: '>=3.8'): https://files.pythonhosted.org/packages/72/a2/ab6355491543ac1254dc3a93061a4c5bae70a100c2245084d18fbcb1bbb1/workoutizer-0.7.1-py3-none-any.whl#sha256=188e0c97d243a5c64be15309f033d529d49831bc1d481a78dab3ce35c56ebbe5 (from https://pypi.org/simple/workoutizer/) (requires-python:>=3.8)

Expected behavior
Install latest version of workoutizer

Additional context
You can probably install an newer version of python via other repositories.

Make the auto-import more flexible

Right now it's hardcoded to look in a /Activity subdirectory in the specified Garmin folder. I import my files from other devices (Wahoo) using other mechanisms, and I'm not using a /Activity folder. It would be nice if it could be optional to NOT look in that folder.

[BUG] Block device not settled yet when mount triggerd by wkz_mount.service

Describe the bug
When plugging in my Garmin device, I get the following error:

Jul 25 21:01:28 raspberrypi wkz[560]: 07-25 22:01:28 - ERROR - wkz.api - could not mount device: cannot unpack non-iterable NoneType object
Jul 25 21:01:28 raspberrypi wkz[560]: Traceback (most recent call last):
Jul 25 21:01:28 raspberrypi wkz[560]:   File "/home/pi/venv/lib/python3.7/site-packages/wkz/api.py", line 18, in mount_device_endpoint
Jul 25 21:01:28 raspberrypi wkz[560]:     mount_path = try_to_mount_device()
Jul 25 21:01:28 raspberrypi wkz[560]:   File "/home/pi/venv/lib/python3.7/site-packages/wkz/file_helper/fit_collector.py", line 93, in try_to_mount_device
Jul 25 21:01:28 raspberrypi wkz[560]:     (type, path) = _find_device_type(bus, dev)
Jul 25 21:01:28 raspberrypi wkz[560]: TypeError: cannot unpack non-iterable NoneType object
Jul 25 21:01:28 raspberrypi wkz[560]: 07-25 22:01:28 - ERROR - django.request - Internal Server Error: /mount-device/

Although this error should be handled better(I will take care of this), this is also caused by my block device not ready yet to be mounted when the wkz_mount.service is triggered by udev.

After some troubleshooting I check the output of the udevadm monitor command

KERNEL[877.854533] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1 (usb)
KERNEL[877.858957] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0 (usb)
KERNEL[877.859569] add      /devices/virtual/workqueue/scsi_tmf_0 (workqueue)
KERNEL[877.862380] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0 (scsi)
KERNEL[877.862451] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/scsi_host/host0 (scsi_host)
KERNEL[877.862533] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0 (usb)
KERNEL[877.862648] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1 (usb)
UDEV  [877.865922] add      /devices/virtual/workqueue/scsi_tmf_0 (workqueue)
UDEV  [877.883068] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1 (usb)
UDEV  [877.884524] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0 (usb)
UDEV  [877.888102] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0 (scsi)
UDEV  [877.891724] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/scsi_host/host0 (scsi_host)
UDEV  [877.895082] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0 (usb)
UDEV  [877.907580] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1 (usb)
KERNEL[878.872297] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0 (scsi)
KERNEL[878.872376] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)
KERNEL[878.872429] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
KERNEL[878.872504] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
KERNEL[878.872579] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
KERNEL[878.872806] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
UDEV  [878.876385] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0 (scsi)
KERNEL[878.876480] change   /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)
UDEV  [878.880500] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)
UDEV  [878.885594] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0 (scsi_device)
UDEV  [878.887239] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_generic/sg0 (scsi_generic)
UDEV  [878.888806] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0 (scsi_disk)
UDEV  [878.890731] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0 (bsg)
UDEV  [878.894677] change   /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)
KERNEL[878.930324] add      /devices/virtual/bdi/8:0 (bdi)
UDEV  [878.931658] add      /devices/virtual/bdi/8:0 (bdi)
KERNEL[879.007785] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
KERNEL[879.010554] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)
UDEV  [886.931941] add      /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0/block/sda (block)
UDEV  [886.936138] bind     /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/host0/target0:0:0/0:0:0:0 (scsi)

You see that first the USB subsystem (the last field between parenthesis) is triggered, after a while SCSI subsystem and the last subsystem is the block subsystem.

So I changed the udev rule to trigger on the block subsystem instead of the USB subsystem like:

ACTION=="add", SUBSYSTEM=="block", ATTRS{idVendor}=="091e", ATTRS{idProduct}=="26e5", TAG+="systemd", ENV{SYSTEMD_WANTS}="wkz_mount"

This works!

But this change could break for your device, So before I create an pull request with this change I would like to ask you for the output of udevadm monitor when plugging in your device.

Make path to activities inside garmin device configurable

Different devices do save their activity files on different paths on their file system. Thus it would be good if the path would not be hard coded (like it currently is with self.activity_path = "/Primary/GARMIN/Activity/") but configurable.

[BUG] Altitude is shown incorrectly

Describe the bug
image
The altitude in the above graph is incorrect, this exercise was around 35m not 270m+, Although the profile seems correct.

Additional context
I see this on a 910XT and a 920XT, with fitdump you see this:

2513. record
 * altitude: 2726
 * enhanced_altitude: 45.200000000000045 [m]

Where enhanced_altitude looks like the correct altitude, the devices both have an barometer, but I dont know if it is relevant for this issue.

When checking the demo fit file 2020-08-31-17-41-11.fit I think I see the same issue:

2596. record
 * altitude: 2728
 * enhanced_altitude: 45.60000000000002 [m]

You can also see that altitude is wrong when using the Topo view of the map, because that view renders height line on the map. Although the looks to be an small discrepancy between those lines and the enhanced_altitude

In the FIT SDK I could not (yet) find an description how to handle altitude.

Trouble getting workoutizer to work

Hey! ๐Ÿ‘‹

Let me start by saying that I love the looks of your project! It looks really great and I would love to get this up-and-running for my device.

However, the setup for this project does not seem to be as simple as the README.md says. In fact, I have not been able to get it running using the package provided in pip.

I cloned the repo to investigate the problems and it seems to roughly boil down to these two:

  1. My Garmin device (a Garmin Venu SQ) does not store the activities inside the folder that is hardcoded in the application (/Primary/GARMIN/Activity). Instead it is stored at /Garmin/Activity. It would be nice if that value is configurable.
  2. Automatic imports do not seem to be working. Even after changing the path above to be correct, the codebase seems to make assumptions about the filesystem of the device. In my case, the Garmin watch gets automatically mounted at /run/media/bintzandt/GARMIN. This would make the mounting step in mount-device unnecessary.

I managed to get it somewhat working by changing the following things:

  1. The path at which the activity folder is stored
  2. Creating a new route for copying the Fit files from my device. I created an /update/ route which does the same as mount-device except for mounting the device ๐Ÿ˜›.

After that, I got workoutizer to load the files. However, error did still occur:

  1. The m_per_s_to_km_per_h filter crashed because m_per_s was not defined in the file, causing float() to run on NoneType.

I have added an additional check to make sure that that code only runs when m_per_s is defined.

These changes allow me to run workoutizer locally. Since I only have one workout file, I am unsure if these fixes are very stable.

Besides that, I am not a Python developer so I have probably created a lot of other error ๐Ÿ˜…

Anyways, I just wanted to let you know what is up. Hopefully, you (we?) will be able to resolve the issues.

Again: thanks a lot for creating this! I look forward to use it.

parse fit files record-wise

Parse fit files record-wise and include NaN for not available attributes to ensure same length of attributes from the early beginning. This will fix many related issues down-stream.

Add import/copy command to workoutizer

As first signaled in #90, the current flow of workoutizer expects that the device is unmounted before the files are copied. In some cases the OS automatically mounts the device (or the user uses a different way to mount the device).

In order to let the user profit from the automatic import process, it would be nice to add a command / route to workoutizer that only copies the files from the device_path + activity_folder_path.

This gives the user the flexibility to use a different mounting strategy in combination with either manually running wkz import or automatically triggering the command (maybe a cron job or something similar).

I think this issue would "fix" point 2 and 3 of #90 (comment).

migrate sport_naming_map to db model

instead of hard coding the name mapping dict, it would be great for the sake of ux to have the mapping be configurable using the settings page and django model

[BUG] run_docker.sh does not forward container port

Describe the bug
The default run_docker script does not provide for forwarding the port the webserver is listening to. WHen manually adding -p 8000:8000 to run_dokcer.sh . You still wont see the web-interface because the webserver is specifically listening on 127.0.0.1 which is not used for forwarding.

To Reproduce
Steps to reproduce the behavior:

  1. Run run_docker.sh
  2. Type wkz init
  3. Type wkz run
  4. In your browser go to http://localhost:8000
  5. See an Connection Refused Error

Expected behavior
After starting the docker image, and browsing to http://localhost:8000 with my browser I would expect to see the workoutizer web interface

Environment:

  • OS: Docker 19.03 on Ubuntu 21.04
  • Browser: Chrome
  • Workoutizer Version latest from git

Additional context
In my Forked repo I have an fix for this problem: gotiniens@191b7bf

This fixes the problem for me, but this can break your workflow.

introduce automatic naming of activities

This could consist of the following:

  • depending on the time of the activity name it e.g. Morning, Afternoon, Evening, ...
  • Use the geo reference to name the activity according to the nearby region/city

[ENH] Officially support Python 3.7 and run on Raspberry Pi (ARM)

Since a convenient way of operating workoutizer in a local network is running it on a Raspberry Pi, it would be great if Python3.7 would be fully supported.

This includes:

  • a successful Python3.7 CI pipeline
  • sportgems needs to be cross compiles for Python3.7 on armv7 architecture - done
  • CI pipeline to test the installation of workoutizer on arm

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.