Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
yihong0618 committed Jan 14, 2024
2 parents 4b37099 + bfc9f26 commit ba466d1
Show file tree
Hide file tree
Showing 15 changed files with 446 additions and 92 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/run_data_sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:

env:
# please change to your own config.
RUN_TYPE: pass # support strava/nike/garmin/garmin_cn/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon, Please change the 'pass' it to your own
RUN_TYPE: pass # support strava/nike/garmin/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon, Please change the 'pass' it to your own
ATHLETE: yihong0618
TITLE: Yihong0618 Running
MIN_GRID_DISTANCE: 10 # change min distance here
Expand Down Expand Up @@ -125,6 +125,15 @@ jobs:
# If you only want to sync `type running` add args --only-run, default script is to sync all data (rides and runs).
# python run_page/garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --only-run --is-cn

- name: Run sync Garmin CN to Garmin script
if: env.RUN_TYPE == 'garmin_sync_cn_global'
run: |
# make garimin secret string `python run_page/garmin_sync_cn_global.py ${email} ${password} --is-cn
python run_page/garmin_sync_cn_global.py ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.GARMIN_SECRET_STRING }}
# If you only want to sync `type running` add args --only-run, default script is to sync all data (rides and runs).
# python run_page/garmin_sync_cn_global.py ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.GARMIN_SECRET_STRING }} --only-run


- name: Run sync Only GPX script
if: env.RUN_TYPE == 'only_gpx'
run: |
Expand Down
43 changes: 43 additions & 0 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。
- **[GPX](#gpx)**
- **[TCX](#tcx)**
- **[FIT](#fit)**
- **[佳明国内同步国际](#Garmin-CN-to-Garmin)**
- **[Tcx+Strava(upload all tcx data to strava)](#tcx_to_strava)**
- **[Gpx+Strava(upload all tcx data to strava)](#gpx_to_strava)**
- **[Nike+Strava(Using NRC Run, Strava backup data)](#nikestrava)**
Expand Down Expand Up @@ -231,6 +232,11 @@ siteMetadata: {
const USE_DASH_LINE = true;
// styling: 透明度:[0, 1]
const LINE_OPACITY = 0.4;
// styling: 开启隐私模式(不显示地图仅显示轨迹): 设置为 `true`
// 注意:此配置仅影响页面显示,数据保护请参考下方的 "隐私保护"
const PRIVACY_MODE = false;
// styling: 默认关灯: 设置为 `false`, 仅在隐私模式关闭时生效(`PRIVACY_MODE` = false)
const LIGHTS_ON = true;
```

> 隐私保护:设置下面环境变量:
Expand Down Expand Up @@ -570,6 +576,43 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxx --is-cn --only-run

</details>

### Garmin-CN to Garmin

<details>
<summary> 同步佳明 CN 数据到 佳明国际区</summary>

<br>

- 如果你只想同步 `type running` 使用参数 --only-run
**The Python version must be >=3.10**

#### 获取佳明 CN 的密钥

在终端中输入以下命令

```bash
python3(python) run_page/get_garmin_secret.py ${your email} ${your password} --is-cn
```

#### 获取佳明全球的密钥

在终端中输入以下命令

```bash
python3(python) run_page/get_garmin_secret.py ${your email} ${your password}
```

#### 同步 佳明 CN 到 佳明全球

在终端中输入以下命令

```bash
python3(python) run_page/garmin_sync_cn_global.py ${garmin_cn_secret_string} ${garmin_secret_string}
```

</details>


### Nike Run Club

<details>
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ English | [简体中文](https://github.com/yihong0618/running_page/blob/master/
- **[GPX](#gpx)**
- **[TCX](#tcx)**
- **[FIT](#fit)**
- **[Garmin-CN_to_Garmin(Sync Garmin-CN activities to Garmin Global)](#garmin-cn-to-garmin)**
- **[Nike_to_Strava(Using NRC Run, Strava backup data)](#nike_to_strava)**
- **[Tcx_to_Strava(upload all tcx data to strava)](#tcx_to_strava)**
- **[Gpx_to_Strava(upload all gpx data to strava)](#gpx_to_strava)**
Expand Down Expand Up @@ -214,6 +215,11 @@ siteMetadata: {
const USE_DASH_LINE = true;
// styling: route line opacity: [0, 1]
const LINE_OPACITY = 0.4;
// styling: set to `true` if you want to display only the routes without showing the map
// Note: This config only affects the page display; please refer to "privacy protection" below for data protection
const PRIVACY_MODE = false;
// styling: set to `false` if you want to make light off as default, only effect when `PRIVACY_MODE` = false
const LIGHTS_ON = true;
```

- To use Google Analytics, you need to modify the configuration in the `src/utils/const.ts` file.
Expand Down Expand Up @@ -379,6 +385,45 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxxxxxx(secret_string) --is-cn -

</details>

### Garmin-CN to Garmin

<details>
<summary> Sync your <code>Garmin-CN</code> data to <code>Garmin</code></summary>

<br>

- If you only want to sync `type running` add args --only-run
**The Python version must be >=3.10**

#### Get Garmin CN Secret

Enter the following command in the terminal

```bash
# to get secret_string
python3(python) run_page/get_garmin_secret.py ${your email} ${your password} --is-cn
```

#### Get Garmin Secret

Enter the following command in the terminal

```bash
# to get secret_string
python3(python) run_page/get_garmin_secret.py ${your email} ${your password}
```

#### Sync Garmin CN to Garmin

Enter the following command in the terminal

```bash
# to sync garmin-cn to garmin-global
python3(python) run_page/garmin_sync_cn_global.py ${garmin_cn_secret_string} ${garmin_secret_string}
```

</details>

### Nike Run Club

<details>
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tenacity
numpy
tzlocal
fit-tool
garmin-fit-sdk
haversine==2.8.0
garth
pycryptodome
Expand Down
1 change: 1 addition & 0 deletions run_page/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
SQL_FILE = os.path.join(parent, "run_page", "data.db")
JSON_FILE = os.path.join(parent, "src", "static", "activities.json")
SYNCED_FILE = os.path.join(parent, "imported.json")
SYNCED_ACTIVITY_FILE = os.path.join(parent, "synced_activity.json")

# TODO: Move into nike_sync NRC THINGS

Expand Down
44 changes: 43 additions & 1 deletion run_page/garmin_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ async def download_activity(self, activity_id, file_type="gpx"):
response.raise_for_status()
return response.read()

async def upload_activities_original(self, datas, use_fake_garmin_device=False):
async def upload_activities_original_from_strava(
self, datas, use_fake_garmin_device=False
):
print(
"start upload activities to garmin!, use_fake_garmin_device:",
use_fake_garmin_device,
Expand Down Expand Up @@ -152,6 +154,38 @@ async def upload_activities_original(self, datas, use_fake_garmin_device=False):
print("garmin upload failed: ", e)
await self.req.aclose()

async def upload_activity_from_file(self, file):
print("Uploading " + str(file))
f = open(file, "rb")

file_body = BytesIO(f.read())
files = {"file": (file, file_body)}

try:
res = await self.req.post(
self.upload_url, files=files, headers=self.headers
)
f.close()
except Exception as e:
print(str(e))
# just pass for now
return
try:
resp = res.json()["detailedImportResult"]
print("garmin upload success: ", resp)
except Exception as e:
print("garmin upload failed: ", e)

async def upload_activities_files(self, files):
print("start upload activities to garmin!")

await gather_with_concurrency(
10,
[self.upload_activity_from_file(file=f) for f in files],
)

await self.req.aclose()


class GarminConnectHttpError(Exception):
def __init__(self, status):
Expand Down Expand Up @@ -206,6 +240,11 @@ async def download_garmin_data(client, activity_id, file_type="gpx"):
os.path.join(folder, f"{activity_id}_ACTIVITY.fit"),
os.path.join(folder, f"{activity_id}.fit"),
)
elif file_info.filename.endswith(".gpx"):
os.rename(
os.path.join(folder, f"{activity_id}_ACTIVITY.gpx"),
os.path.join(FOLDER_DICT["gpx"], f"{activity_id}.gpx"),
)
else:
os.remove(os.path.join(folder, file_info.filename))
os.remove(file_path)
Expand Down Expand Up @@ -323,4 +362,7 @@ async def download_new_activities(
)
)
loop.run_until_complete(future)
# fit may contain gpx(maybe upload by user)
if file_type == "fit":
make_activities_file(SQL_FILE, FOLDER_DICT["gpx"], JSON_FILE, file_suffix="gpx")
make_activities_file(SQL_FILE, folder, JSON_FILE, file_suffix=file_type)
105 changes: 105 additions & 0 deletions run_page/garmin_sync_cn_global.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Python 3 API wrapper for Garmin Connect to get your statistics.
Copy most code from https://github.com/cyberjunky/python-garminconnect
"""

import argparse
import asyncio
import logging
import os
import sys
import time
import traceback
import zipfile
from io import BytesIO

import aiofiles
import cloudscraper
import garth
import httpx
from config import FIT_FOLDER, GPX_FOLDER, JSON_FILE, SQL_FILE, config
from garmin_device_adaptor import wrap_device_info
from garmin_sync import Garmin, get_downloaded_ids
from garmin_sync import download_new_activities, gather_with_concurrency
from synced_data_file_logger import load_synced_activity_list, save_synced_activity_list
from utils import make_activities_file

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"cn_secret_string", nargs="?", help="secret_string fro get_garmin_secret.py"
)
parser.add_argument(
"global_secret_string", nargs="?", help="secret_string fro get_garmin_secret.py"
)
parser.add_argument(
"--only-run",
dest="only_run",
action="store_true",
help="if is only for running",
)

options = parser.parse_args()
secret_string_cn = options.cn_secret_string
secret_string_global = options.global_secret_string
auth_domain = "CN"
is_only_running = options.only_run
if secret_string_cn is None or secret_string_global is None:
print("Missing argument nor valid configuration file")
sys.exit(1)

# Step 1:
# Sync all activities from Garmin CN to Garmin Global in FIT format
# If the activity is manually imported with a GPX, the GPX file will be synced

# load synced activity list
synced_activity = load_synced_activity_list()

folder = FIT_FOLDER
# make gpx or tcx dir
if not os.path.exists(folder):
os.mkdir(folder)

loop = asyncio.get_event_loop()
future = asyncio.ensure_future(
download_new_activities(
secret_string_cn,
auth_domain,
synced_activity,
is_only_running,
folder,
"fit",
)
)
loop.run_until_complete(future)
new_ids = future.result()

to_upload_files = []
for i in new_ids:
if os.path.exists(os.path.join(FIT_FOLDER, f"{i}.fit")):
# upload fit files
to_upload_files.append(os.path.join(FIT_FOLDER, f"{i}.fit"))
elif os.path.exists(os.path.join(GPX_FOLDER, f"{i}.gpx")):
# upload gpx files which are manually uploaded to garmin connect
to_upload_files.append(os.path.join(GPX_FOLDER, f"{i}.gpx"))

print("Files to sync:" + " ".join(to_upload_files))
garmin_global_client = Garmin(
secret_string_global,
config("sync", "garmin", "authentication_domain"),
is_only_running,
)
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(
garmin_global_client.upload_activities_files(to_upload_files)
)
loop.run_until_complete(future)

# Save synced activity list for speeding up
synced_activity.extend(new_ids)
save_synced_activity_list(synced_activity)

# Step 2:
# Generate track from fit/gpx file
make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE, file_suffix="gpx")
make_activities_file(SQL_FILE, FIT_FOLDER, JSON_FILE, file_suffix="fit")
Loading

1 comment on commit ba466d1

@vercel
Copy link

@vercel vercel bot commented on ba466d1 Jan 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

running-page – ./

running-page-yihong0618.vercel.app
running-page.vercel.app
running-page-git-master-yihong0618.vercel.app

Please sign in to comment.