garmin-collector
This commit is contained in:
parent
a9697956e4
commit
ff789db499
6 changed files with 142 additions and 0 deletions
|
@ -182,6 +182,7 @@
|
||||||
# "matrix-registration-shared-secret".owner = "matrix-synapse";
|
# "matrix-registration-shared-secret".owner = "matrix-synapse";
|
||||||
# "matrix-sliding-sync-secret".owner = "matrix-synapse";
|
# "matrix-sliding-sync-secret".owner = "matrix-synapse";
|
||||||
"autistici-password".owner = "forgejo";
|
"autistici-password".owner = "forgejo";
|
||||||
|
"garmin-collector-environment".owner = "garmin-collector";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
46
modules/garmin-collector/default.nix
Normal file
46
modules/garmin-collector/default.nix
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
fleetFlake,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
users.users.garmin-collector = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "garmin-collector";
|
||||||
|
extraGroups = ["garmin-collector"];
|
||||||
|
home = "/var/lib/garmin-collector";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.garmin-collector = {};
|
||||||
|
|
||||||
|
systemd.services.garmin-collector = {
|
||||||
|
description = "Garmin collector pushing to Prometheus Pushgateway";
|
||||||
|
wantedBy = ["multi-user.target"];
|
||||||
|
environment = {
|
||||||
|
PUSHGATEWAY_ADDRESS = config.services.prometheus.pushgateway.web.listen-address;
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Group = "garmin-collector";
|
||||||
|
User = "garmin-collector";
|
||||||
|
WorkingDirectory = "/var/lib/garmin-collector";
|
||||||
|
ExecStart = ''
|
||||||
|
${lib.getExe fleetFlake.packages.${pkgs.system}.garmin-collector}
|
||||||
|
'';
|
||||||
|
EnvironmentFile = config.age.secrets.garmin-collector-environment.path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers."garmin-collector" = {
|
||||||
|
wantedBy = ["timers.target"];
|
||||||
|
timerConfig = {
|
||||||
|
OnBootSec = "5m";
|
||||||
|
OnUnitActiveSec = "4h";
|
||||||
|
Unit = "garmin-collector.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.persistence."/persist".directories = [
|
||||||
|
"/var/lib/garmin-collector"
|
||||||
|
];
|
||||||
|
}
|
12
packages/garmin-collector/default.nix
Normal file
12
packages/garmin-collector/default.nix
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
writers,
|
||||||
|
python3Packages,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
writers.writePython3Bin "garmin-collector" {
|
||||||
|
libraries = with python3Packages; [
|
||||||
|
prometheus-client
|
||||||
|
garminconnect
|
||||||
|
];
|
||||||
|
flakeIgnore = ["E501"];
|
||||||
|
} (builtins.readFile ./garmin-collector.py)
|
82
packages/garmin-collector/garmin-collector.py
Normal file
82
packages/garmin-collector/garmin-collector.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
# !/usr/bin/env python3
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
from garth.exc import GarthHTTPError
|
||||||
|
|
||||||
|
from garminconnect import (
|
||||||
|
Garmin,
|
||||||
|
GarminConnectAuthenticationError,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
from prometheus_client import CollectorRegistry, push_to_gateway
|
||||||
|
from prometheus_client.core import GaugeMetricFamily
|
||||||
|
|
||||||
|
email = os.getenv("GARMIN_EMAIL")
|
||||||
|
password = os.getenv("GARMIN_PASSWORD")
|
||||||
|
tokenstore = os.getenv("GARMINTOKENS") or "~/.garminconnect"
|
||||||
|
tokenstore_base64 = os.getenv("GARMINTOKENS_BASE64") or "~/.garminconnect_base64"
|
||||||
|
gateway_address = os.getenv("PUSHGATEWAY_ADDRESS")
|
||||||
|
|
||||||
|
today = datetime.date.today()
|
||||||
|
|
||||||
|
|
||||||
|
def init_api(email=email, password=password):
|
||||||
|
"""Initialize Garmin API with your credentials."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(
|
||||||
|
f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
garmin = Garmin()
|
||||||
|
garmin.login(tokenstore)
|
||||||
|
except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError):
|
||||||
|
# Session is expired. You'll need to log in again
|
||||||
|
print(
|
||||||
|
"Login tokens not present, login with your Garmin Connect credentials to generate them.\n"
|
||||||
|
f"They will be stored in '{tokenstore}' for future use.\n"
|
||||||
|
)
|
||||||
|
garmin = Garmin(email=email, password=password, is_cn=False)
|
||||||
|
garmin.login()
|
||||||
|
# Save Oauth1 and Oauth2 token files to directory for next login
|
||||||
|
garmin.garth.dump(tokenstore)
|
||||||
|
print(
|
||||||
|
f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n"
|
||||||
|
)
|
||||||
|
# Encode Oauth1 and Oauth2 tokens to base64 string and safe to file for next login (alternative way)
|
||||||
|
token_base64 = garmin.garth.dumps()
|
||||||
|
dir_path = os.path.expanduser(tokenstore_base64)
|
||||||
|
with open(dir_path, "w") as token_file:
|
||||||
|
token_file.write(token_base64)
|
||||||
|
print(
|
||||||
|
f"Oauth tokens encoded as base64 string and saved to '{dir_path}' file for future use. (second method)\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
return garmin
|
||||||
|
|
||||||
|
|
||||||
|
class GarminCollector:
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.api = init_api()
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
try:
|
||||||
|
body = self.api.get_daily_weigh_ins(today.isoformat())["totalAverage"]
|
||||||
|
metric_gauge = GaugeMetricFamily("body_composition", "Body composition and weight", labels=["metric"])
|
||||||
|
for k in ["weight", "bmi", "bodyFat", "bodyWater", "boneMass", "muscleMass", "physiqueRating", "visceralFat"]:
|
||||||
|
metric_gauge.add_metric([k], body[k])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Something went wrong while fetching body composition data\n{e}")
|
||||||
|
|
||||||
|
yield metric_gauge
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
registry = CollectorRegistry()
|
||||||
|
registry.register(GarminCollector())
|
||||||
|
|
||||||
|
push_to_gateway(gateway_address, job='garmin', registry=registry)
|
BIN
secrets/garmin-collector-environment.age
Normal file
BIN
secrets/garmin-collector-environment.age
Normal file
Binary file not shown.
|
@ -27,6 +27,7 @@ in
|
||||||
"matrix-sliding-sync-secret.age".publicKeys = [ccr-ssh ccr-gpg sisko];
|
"matrix-sliding-sync-secret.age".publicKeys = [ccr-ssh ccr-gpg sisko];
|
||||||
"forgejo-runners-token.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
"forgejo-runners-token.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
||||||
"forgejo-nix-access-tokens.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
"forgejo-nix-access-tokens.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
||||||
|
"garmin-collector-environment.age".publicKeys = [ccr-ssh ccr-gpg sisko];
|
||||||
|
|
||||||
# WireGuard
|
# WireGuard
|
||||||
"picard-wireguard-private-key.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
"picard-wireguard-private-key.age".publicKeys = [ccr-ssh ccr-gpg picard];
|
||||||
|
|
Loading…
Add table
Reference in a new issue