Compare commits

...

217 Commits

Author SHA1 Message Date
Ron Klinkien
83e8e52d72 Fixes 2025-02-08 18:18:15 +00:00
Ron
c94104443d Merge pull request #251 from cyberjunky/dependabot/pip/ruff-0.9.5
Bump ruff from 0.9.2 to 0.9.5
2025-02-08 18:39:40 +01:00
Ron
13b2bb64b0 Merge branch 'main' into dependabot/pip/ruff-0.9.5 2025-02-08 18:38:08 +01:00
Ron
d9b99cd9ea Merge pull request #250 from cyberjunky/dependabot/pip/codespell-2.4.1
Bump codespell from 2.3.0 to 2.4.1
2025-02-08 18:37:41 +01:00
Ron
00a15e077a Merge branch 'main' into dependabot/pip/codespell-2.4.1 2025-02-08 18:37:13 +01:00
dependabot[bot]
4a5564bc45 Bump ruff from 0.9.2 to 0.9.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.2 to 0.9.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.2...0.9.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 17:36:34 +00:00
Ron
b35de63aae Merge pull request #245 from cyberjunky/dependabot/pip/ruff-0.9.3
Bump ruff from 0.9.2 to 0.9.3
2025-02-08 18:36:34 +01:00
dependabot[bot]
c52166ded0 Bump codespell from 2.3.0 to 2.4.1
Bumps [codespell](https://github.com/codespell-project/codespell) from 2.3.0 to 2.4.1.
- [Release notes](https://github.com/codespell-project/codespell/releases)
- [Commits](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.1)

---
updated-dependencies:
- dependency-name: codespell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 17:36:28 +00:00
Ron
d6bc6f7f69 Merge pull request #244 from cyberjunky/dependabot/pip/codespell-2.4.0
Bump codespell from 2.3.0 to 2.4.0
2025-02-08 18:36:19 +01:00
Ron
a41b847e45 Merge pull request #243 from cyberjunky/dependabot/pip/pre-commit-4.1.0
Bump pre-commit from 4.0.1 to 4.1.0
2025-02-08 18:35:36 +01:00
Ron
9c42b634e7 Merge pull request #242 from cyberjunky/dependabot/pip/homeassistant-2025.1.4
Bump homeassistant from 2025.1.2 to 2025.1.4
2025-02-08 18:35:24 +01:00
dependabot[bot]
28a95b2ab2 Bump ruff from 0.9.2 to 0.9.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.2...0.9.3)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 06:27:55 +00:00
dependabot[bot]
d1609b5c5b Bump codespell from 2.3.0 to 2.4.0
Bumps [codespell](https://github.com/codespell-project/codespell) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/codespell-project/codespell/releases)
- [Commits](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: codespell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 06:27:45 +00:00
dependabot[bot]
6b5d8fc0b6 Bump pre-commit from 4.0.1 to 4.1.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.0.1 to 4.1.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.0.1...v4.1.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 06:27:43 +00:00
dependabot[bot]
be907979ba Bump homeassistant from 2025.1.2 to 2025.1.4
Bumps [homeassistant](https://github.com/home-assistant/core) from 2025.1.2 to 2025.1.4.
- [Release notes](https://github.com/home-assistant/core/releases)
- [Commits](https://github.com/home-assistant/core/compare/2025.1.2...2025.1.4)

---
updated-dependencies:
- dependency-name: homeassistant
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 06:27:40 +00:00
Ron
d884162c8f Update issue templates 2025-01-22 14:39:28 +01:00
Ron
194f377016 Create .coderabbit.yaml 2025-01-22 14:37:22 +01:00
Ron Klinkien
dfc4b5c723 Added User Points, User Level and Last Activity sensors 2025-01-21 10:52:42 +00:00
Ron Klinkien
06ee9e15b7 Bumped version 2025-01-21 08:12:39 +00:00
Ron Klinkien
164fc53b6d Documented services examples 2025-01-21 08:11:52 +00:00
Ron
2eeb42b4da Merge pull request #236 from cyberjunky/dependabot/pip/ruff-0.9.2
Bump ruff from 0.7.2 to 0.9.2
2025-01-20 19:11:01 +01:00
Ron Klinkien
0cb06c7cc4 Fixed selection of 10 last badges 2025-01-20 18:01:57 +00:00
dependabot[bot]
99fe655919 Bump ruff from 0.7.2 to 0.9.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.2 to 0.9.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.2...0.9.2)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 11:47:00 +00:00
Ron Klinkien
36d6f7720d Many bug fixes and fixes of deprecated code, update of dev environment and dependencies 2025-01-20 10:16:05 +00:00
Ron
34d88ff534 Merge pull request #228 from lukas-hermans/main
Add total sleep duration sensor
2025-01-17 13:08:44 +01:00
Ron
4a3ac50097 Merge pull request #233 from cyberjunky/dependabot/pip/ruff-0.9.2
Bump ruff from 0.8.3 to 0.9.2
2025-01-17 12:39:34 +01:00
dependabot[bot]
16a179ab9b Bump ruff from 0.8.3 to 0.9.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.3 to 0.9.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.8.3...0.9.2)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-17 11:32:38 +00:00
Ron
8269a44345 Merge pull request #224 from cyberjunky/dependabot/pip/pylint-3.3.3
Bump pylint from 3.3.2 to 3.3.3
2025-01-17 12:32:04 +01:00
Ron
3be17f04c4 Merge pull request #223 from cyberjunky/dependabot/pip/mypy-1.14.1
Bump mypy from 1.13.0 to 1.14.1
2025-01-17 12:31:53 +01:00
Ron
46b11708bb Merge pull request #218 from cyberjunky/dependabot/pip/pip-gte-24.1.1-and-lt-24.4
Update pip requirement from <24.2,>=24.1.1 to >=24.1.1,<24.4
2025-01-17 12:31:38 +01:00
Ron
115db56262 Merge pull request #217 from cyberjunky/dependabot/pip/colorlog-6.9.0
Bump colorlog from 6.8.2 to 6.9.0
2025-01-17 12:31:25 +01:00
Ron
792c50e291 Merge pull request #232 from jfparis/badges
Fixes to badge sensor
2025-01-17 12:31:03 +01:00
Jean-François Paris
0f0d9ad8f5 Better icon for badges sensor 2025-01-13 22:31:26 +00:00
Jean-François Paris
2a16ea4727 Fixed 3 sensors with state/Sensor class mixed up 2025-01-13 22:28:27 +00:00
Lukas Hermans
bd567dc0b2 Add total sleep duration 2025-01-07 19:52:13 +01:00
dependabot[bot]
1620038867 Bump pylint from 3.3.2 to 3.3.3
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.3.2...v3.3.3)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 20:03:47 +00:00
dependabot[bot]
9cc0b4ee43 Bump mypy from 1.13.0 to 1.14.1
Bumps [mypy](https://github.com/python/mypy) from 1.13.0 to 1.14.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.13.0...v1.14.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 20:03:01 +00:00
dependabot[bot]
a2ce110dcf Update pip requirement from <24.2,>=24.1.1 to >=24.1.1,<24.4
Updates the requirements on [pip](https://github.com/pypa/pip) to permit the latest version.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/24.1.1...24.3.1)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 20:01:58 +00:00
dependabot[bot]
a2c73638d4 Bump colorlog from 6.8.2 to 6.9.0
Bumps [colorlog](https://github.com/borntyping/python-colorlog) from 6.8.2 to 6.9.0.
- [Release notes](https://github.com/borntyping/python-colorlog/releases)
- [Commits](https://github.com/borntyping/python-colorlog/compare/v6.8.2...v6.9.0)

---
updated-dependencies:
- dependency-name: colorlog
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 20:01:54 +00:00
Ron Klinkien
585242e77c Another hrvdata missing fix. 2024-12-20 12:03:31 +01:00
Ron Klinkien
dbcb5d7103 Fixes related to hrvstatus and body composition data 2024-12-20 10:02:34 +01:00
Ron
3a14a7a108 Update hacs.json 2024-12-17 09:02:43 +01:00
Ron
5f9aeb4914 Merge pull request #210 from cyberjunky/dependabot/pip/vulture-2.14
Bump vulture from 2.11 to 2.14
2024-12-17 08:54:02 +01:00
dependabot[bot]
5934224198 Bump vulture from 2.11 to 2.14
Bumps [vulture](https://github.com/jendrikseipp/vulture) from 2.11 to 2.14.
- [Release notes](https://github.com/jendrikseipp/vulture/releases)
- [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jendrikseipp/vulture/compare/v2.11...v2.14)

---
updated-dependencies:
- dependency-name: vulture
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 07:53:29 +00:00
Ron
fbbbf92728 Merge pull request #213 from cyberjunky/dependabot/pip/ruff-0.8.3
Bump ruff from 0.5.0 to 0.8.3
2024-12-17 08:52:45 +01:00
Ron
cd3190a799 Merge pull request #209 from cyberjunky/dependabot/pip/pre-commit-4.0.1
Bump pre-commit from 3.7.1 to 4.0.1
2024-12-17 08:52:21 +01:00
dependabot[bot]
4015141d23 Bump pre-commit from 3.7.1 to 4.0.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.7.1 to 4.0.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.7.1...v4.0.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 07:51:20 +00:00
dependabot[bot]
91cdd4ad77 Bump ruff from 0.5.0 to 0.8.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.0 to 0.8.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.0...0.8.3)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-17 07:51:17 +00:00
Ron
cdff89f2ca Merge pull request #208 from cyberjunky/dependabot/pip/pylint-3.3.2
Bump pylint from 3.2.5 to 3.3.2
2024-12-17 08:50:17 +01:00
Ron
689df3de94 Merge pull request #206 from cyberjunky/dependabot/pip/mypy-1.13.0
Bump mypy from 1.10.1 to 1.13.0
2024-12-17 08:50:06 +01:00
Ron
8c16842247 Merge pull request #157 from kruemelro/main
Always fetch latest data, added blood pressure service
2024-12-17 08:46:38 +01:00
Ron
fa0e60648f Merge pull request #204 from jfparis/hrv_status
Add a sensor to retreive HRV status when available
2024-12-17 08:40:48 +01:00
Ron
5a8fd38741 Merge pull request #203 from jfparis/measurement_units_v2
Correct states class of several entities
2024-12-17 08:37:10 +01:00
Ron Klinkien
f16f96e5de Pin garth version to 0.4.47 2024-12-12 08:32:30 +01:00
dependabot[bot]
25944ba0fe Bump pylint from 3.2.5 to 3.3.2
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.2.5 to 3.3.2.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.2.5...v3.3.2)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 21:07:37 +00:00
dependabot[bot]
d3fa73c5a1 Bump mypy from 1.10.1 to 1.13.0
Bumps [mypy](https://github.com/python/mypy) from 1.10.1 to 1.13.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.1...v1.13.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 21:07:24 +00:00
Jean-François Paris
252f67ff7f Add a sensor to retreive HRV status when available 2024-11-28 10:50:59 +00:00
Jean-François Paris
b366807571 Gives body indicators related data the 'measurement' class 2024-11-27 21:36:32 +00:00
Jean-François Paris
437aae6d34 Gives respitation related data the 'measurement' class 2024-11-27 21:35:06 +00:00
Jean-François Paris
51ccb196c0 Gives SPO2 related data the 'measurement' class 2024-11-27 21:32:12 +00:00
Jean-François Paris
8de123eff2 Gives stress related data the 'measurement' class 2024-11-27 21:28:53 +00:00
Jean-François Paris
d4e9a4edc6 Gives heart rates related data the 'measurement' class 2024-11-27 21:22:15 +00:00
Ron Klinkien
5a5b42a560 Bumped version of garminconnect 2024-11-27 20:20:57 +01:00
Ron Klinkien
e2deaed42b Dev version, added garmin badges 2024-11-27 10:14:55 +01:00
Ron Klinkien
78b6641506 Updated sk.json 2024-11-27 10:14:17 +01:00
Ron Klinkien
29f0832e8b Updated LICENSE 2024-11-27 10:14:17 +01:00
Ron
07473ef701 Merge pull request #139 from kozerskil/badges
Added badges
2024-11-27 10:13:26 +01:00
Ron
b8156a6a7d Merge pull request #186 from cyberjunky/dependabot/pip/pylint-3.2.5
Bump pylint from 3.0.3 to 3.2.5
2024-11-27 09:41:25 +01:00
Ron
7546a40012 Merge branch 'main' into dependabot/pip/pylint-3.2.5 2024-11-27 09:40:48 +01:00
Ron
e6f7947e2c Merge pull request #187 from cyberjunky/dependabot/pip/ruff-0.5.0
Bump ruff from 0.3.5 to 0.5.0
2024-11-27 09:39:53 +01:00
Ron
dda115539f Merge branch 'main' into dependabot/pip/ruff-0.5.0 2024-11-27 09:39:41 +01:00
Ron
304cd1f227 Merge pull request #188 from cyberjunky/dependabot/pip/mypy-1.10.1
Bump mypy from 1.8.0 to 1.10.1
2024-11-27 09:38:59 +01:00
Ron
6dc64df4d9 Merge branch 'main' into dependabot/pip/mypy-1.10.1 2024-11-27 09:38:23 +01:00
Ron
c3af1cc392 Merge pull request #185 from cyberjunky/dependabot/pip/pip-gte-24.1.1-and-lt-24.2
Update pip requirement from <24.1,>=21.0 to >=24.1.1,<24.2
2024-11-27 09:37:25 +01:00
Ron
4fa9f446de Merge pull request #173 from cyberjunky/dependabot/pip/pre-commit-3.7.1
Bump pre-commit from 3.6.0 to 3.7.1
2024-11-27 09:37:11 +01:00
Ron
9431374a71 Delete .github/workflows/ci.yml 2024-11-27 09:20:29 +01:00
Ron Klinkien
684acf436c Bumped python-garminconnect version to temp fix Garmin's API change 2024-11-27 09:18:47 +01:00
dependabot[bot]
3b54e25db5 Bump mypy from 1.8.0 to 1.10.1
Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.10.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.8.0...v1.10.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 19:10:08 +00:00
dependabot[bot]
73eb0cea73 Bump ruff from 0.3.5 to 0.5.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.5 to 0.5.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.5...0.5.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 19:10:03 +00:00
dependabot[bot]
8099a99077 Bump pylint from 3.0.3 to 3.2.5
Bumps [pylint](https://github.com/pylint-dev/pylint) from 3.0.3 to 3.2.5.
- [Release notes](https://github.com/pylint-dev/pylint/releases)
- [Commits](https://github.com/pylint-dev/pylint/compare/v3.0.3...v3.2.5)

---
updated-dependencies:
- dependency-name: pylint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 19:09:53 +00:00
dependabot[bot]
8647e95e30 Update pip requirement from <24.1,>=21.0 to >=24.1.1,<24.2
Updates the requirements on [pip](https://github.com/pypa/pip) to permit the latest version.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/21.0...24.1.1)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 19:09:49 +00:00
dependabot[bot]
270327d7d8 Bump pre-commit from 3.6.0 to 3.7.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.0 to 3.7.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.7.1)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 19:18:19 +00:00
kruemelro
14c6694239 changed value datatype to int 2024-04-08 08:57:53 +02:00
Ron Klinkien
d42edcabc6 Fixed hacs validation 2024-04-06 12:29:33 +00:00
Ron Klinkien
b827597dab Revert unique id for now, added dev files 2024-04-06 14:13:10 +02:00
Ron
cb16c0198c Merge pull request #126 from cyberjunky/dependabot/pip/vulture-2.11
Bump vulture from 2.10 to 2.11
2024-04-06 10:54:49 +02:00
Ron
d8478aea37 Merge branch 'main' into dependabot/pip/vulture-2.11 2024-04-06 10:54:39 +02:00
Ron
3392a1458a Merge pull request #128 from cyberjunky/dependabot/pip/colorlog-6.8.2
Bump colorlog from 6.8.0 to 6.8.2
2024-04-06 10:54:00 +02:00
Ron
09e10f0da2 Merge pull request #130 from cyberjunky/dependabot/pip/pip-gte-21.0-and-lt-24.1
Update pip requirement from <23.4,>=21.0 to >=21.0,<24.1
2024-04-06 10:53:49 +02:00
Ron
265c0fbb21 Merge branch 'main' into dependabot/pip/pip-gte-21.0-and-lt-24.1 2024-04-06 10:53:41 +02:00
Ron
9c9cf6609c Merge pull request #155 from AnotherGroupChat/main
Bump garminconnect version
2024-04-06 10:52:33 +02:00
Ron
91c35eb486 Merge pull request #152 from cyberjunky/dependabot/pip/pre-commit-3.7.0
Bump pre-commit from 3.6.0 to 3.7.0
2024-04-06 10:51:09 +02:00
Ron
5334378493 Merge pull request #156 from cyberjunky/dependabot/pip/ruff-0.3.5
Bump ruff from 0.1.13 to 0.3.5
2024-04-06 10:50:57 +02:00
kruemelro
3d5200581d try and error ;-) 2024-04-05 10:17:20 +02:00
kruemelro
e4331ecf8c added missing entity_id 2024-04-05 10:05:43 +02:00
kruemelro
47096eabeb added blood_pressure function 2024-04-05 09:43:52 +02:00
kruemelro
09e80e4255 Update manifest.json 2024-04-03 08:04:11 +02:00
kruemelro
62377b5d55 changed body return values
instead of the total average, take the first measurement
2024-04-02 12:16:16 +02:00
kruemelro
9136466115 fixed typo 2024-04-02 11:49:48 +02:00
kruemelro
3bf77a9056 Update manifest.json 2024-04-02 11:40:38 +02:00
kruemelro
83ceb35666 Update garminconnect to 0.2.15 2024-04-02 11:26:08 +02:00
kruemelro
003a4c57ef changed get_body_composition
changed get_body_composition to load data of past week not only today
2024-04-02 11:24:46 +02:00
dependabot[bot]
e99ec5c770 Bump ruff from 0.1.13 to 0.3.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.13 to 0.3.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.13...v0.3.5)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 19:21:08 +00:00
dylan madisetti
38fd1fb631 Bump connect version 2024-03-31 13:05:56 -04:00
dependabot[bot]
9b8f536601 Bump pre-commit from 3.6.0 to 3.7.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.7.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 19:19:34 +00:00
luk
261f41940f Add badges 2024-02-13 13:47:51 +01:00
dependabot[bot]
27551067ba Update pip requirement from <23.4,>=21.0 to >=21.0,<24.1
Updates the requirements on [pip](https://github.com/pypa/pip) to permit the latest version.
- [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/pip/compare/21.0...24.0)

---
updated-dependencies:
- dependency-name: pip
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 19:50:05 +00:00
dependabot[bot]
d5dddd5f73 Bump colorlog from 6.8.0 to 6.8.2
Bumps [colorlog](https://github.com/borntyping/python-colorlog) from 6.8.0 to 6.8.2.
- [Release notes](https://github.com/borntyping/python-colorlog/releases)
- [Commits](https://github.com/borntyping/python-colorlog/compare/v6.8.0...v6.8.2)

---
updated-dependencies:
- dependency-name: colorlog
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 19:37:29 +00:00
dependabot[bot]
4e033b91a5 Bump vulture from 2.10 to 2.11
Bumps [vulture](https://github.com/jendrikseipp/vulture) from 2.10 to 2.11.
- [Release notes](https://github.com/jendrikseipp/vulture/releases)
- [Changelog](https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jendrikseipp/vulture/compare/v2.10...v2.11)

---
updated-dependencies:
- dependency-name: vulture
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 19:28:07 +00:00
Ron
a12306d79d Merge pull request #120 from ronlut/main
Add user- and integration name to sensor names and ids
2024-01-16 13:32:04 +01:00
Ron
6abe6ace3c Merge branch 'main' into main 2024-01-16 13:26:02 +01:00
Ron
5f31a7492d Merge pull request #121 from cyberjunky/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2024-01-16 13:20:50 +01:00
Ron
b8abef2954 Merge pull request #122 from cyberjunky/dependabot/pip/ruff-0.1.13
Bump ruff from 0.1.11 to 0.1.13
2024-01-16 13:20:42 +01:00
Ron
a3096b4c80 Merge pull request #123 from cyberjunky/dependabot/pip/pre-commit-3.6.0
Bump pre-commit from 3.5.0 to 3.6.0
2024-01-16 13:20:33 +01:00
dependabot[bot]
23e7ab94c0 Bump pre-commit from 3.5.0 to 3.6.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.5.0...v3.6.0)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-16 12:18:50 +00:00
dependabot[bot]
7fb469121c Bump ruff from 0.1.11 to 0.1.13
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.11 to 0.1.13.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.11...v0.1.13)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-16 12:18:44 +00:00
dependabot[bot]
d5c88528b1 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-16 12:18:26 +00:00
cyberjunky
7ab5141a6c Added development enviroment 2024-01-16 12:17:17 +00:00
Rony Lutsky
7ad8d099e1 unique_id with domain 2024-01-11 00:02:42 +02:00
Rony Lutsky
9198f469c2 use username for name and entity_id 2024-01-11 00:01:39 +02:00
Ron
7b227663dc Update README.md 2024-01-08 17:42:53 +01:00
Ron
afc9baa4a2 Update services.yaml 2024-01-03 12:01:19 +01:00
Ron
f15658548d Update services.yaml 2024-01-02 19:07:29 +01:00
Ron
e64573c6aa Update manifest.json 2024-01-02 18:51:21 +01:00
Ron
71f24148a2 Update sk.json 2024-01-02 18:47:22 +01:00
Ron
891230ce7a Update sk.json 2024-01-02 18:47:12 +01:00
Ron
c72e7a1879 Update README.md 2024-01-02 18:28:02 +01:00
Ron
65720791c2 Create hassfest.yaml 2024-01-02 13:57:02 +01:00
Ron
1029e21e23 Merge pull request #116 from alexives/add_body_composition_service
Add service for body composition
2024-01-02 12:33:56 +01:00
Ron
ac923b7517 Update services.yaml 2024-01-02 12:31:50 +01:00
Ron
cf5d39fb56 Update sensor.py 2024-01-02 12:31:16 +01:00
Ron
77dbbc9f47 Update manifest.json 2024-01-02 12:30:43 +01:00
Ron
2326be7455 Update const.py 2024-01-02 12:30:21 +01:00
Ron
bfb720ac5d Update __init__.py 2024-01-02 12:29:46 +01:00
Alex Ives
d20c9bedb2 Add service for body composition
Relates to https://github.com/cyberjunky/home-assistant-garmin_connect/issues/74
2023-12-30 15:42:19 -06:00
Ron
5e6f7ff6e1 Create FUNDING.yml 2023-12-26 20:26:50 +01:00
Ron
9d90c366d9 Merge pull request #113 from misa1515/patch-3
Update sk.json
2023-12-23 17:54:49 +01:00
Ron
891ddbce27 Bumped python-garminconnect version 2023-12-22 13:28:59 +01:00
misa1515
dc345c4d53 Update sk.json 2023-11-21 13:55:51 +01:00
Ron Klinkien
89222f0299 Small textual changes 2023-09-15 13:02:43 +02:00
Ron Klinkien
18304be84a Bumped python-garminconnect package to 0.2.3 to fix stability issues 2023-09-15 13:01:34 +02:00
Ron
ffd478cc60 Fixed typo 2023-07-12 13:23:13 +02:00
Ron
4e6b9310a1 Merge pull request #89 from jfparis/sleep_score
Pull sleepscore from Garmin interface
2023-07-12 12:58:23 +02:00
Jean-François Paris
e9fba7abbe Pull sleepscore from Garmin interface 2023-06-23 23:34:24 +01:00
Ron
17fa2bcc63 Update version 2023-05-22 10:17:21 +02:00
Ron
b26f36c41f Merge pull request #72 from ViPeR5000/patch-2
Update translation
2023-05-22 10:14:53 +02:00
Ron
845cce3bda Merge pull request #49 from ray0711/gear_sensor
Added gear sensors & gear service
2023-05-22 10:14:12 +02:00
Ron
15800b00d5 Merge pull request #80 from gorzelak/main
Adding last activities sensor
2023-05-22 10:11:49 +02:00
Ron
c9e9740137 Merge pull request #79 from pailloM/main
Remove pytz dependency in sensor.py
2023-05-22 10:10:23 +02:00
Krzysztof Gorzelak
b7f2a6e653 Adding last activities sensor 2023-05-05 10:09:46 +02:00
Paillomams
be150124b7 remove pytz dependency 2023-04-23 00:46:24 -04:00
Ron
28876a8709 Update manifest.json 2023-04-19 08:23:38 +02:00
ray0711
287a9a04c3 Merge branch 'main' of https://github.com/cyberjunky/home-assistant-garmin_connect into gear_sensor 2023-04-16 10:15:05 +02:00
Ron
de51403c43 Update manifest.json 2023-04-05 17:39:31 +02:00
Ron
36e5b7c6b8 Add support for China 2023-04-05 17:39:16 +02:00
Ron
9a6aea06c4 Delete .github/workflows directory 2023-03-28 12:18:31 +02:00
Ron
bc1ff88af2 Update hassfest.yml 2023-03-28 12:07:57 +02:00
Ron
48d738d1dc Update manifest.json 2023-03-28 11:44:38 +02:00
Rui Melo
d57b8e0223 update
fix word
2023-03-03 11:25:43 +00:00
Ron
0688f89be1 Fix manifest validation errors 2023-03-03 10:08:37 +01:00
Ron
985468d7a2 Update manifest.json 2023-03-03 09:52:56 +01:00
Ron
ff0ccb7e99 Update manifest.json 2023-03-03 09:52:34 +01:00
Ron
f59b41e7c4 Fixed errors for weight and stress sensors 2023-03-03 09:52:23 +01:00
Ron
0074b94723 Fixed syntax error 2023-02-03 09:14:27 +01:00
Ron
ee777314f9 Update manifest.json 2023-02-02 18:13:12 +01:00
Ron
58f5dd49db Merge pull request #62 from misa1515/patch-2
Create sk.json
2023-02-02 18:12:30 +01:00
Ron
7b66c4b7be Update manifest.json 2023-02-02 18:11:13 +01:00
Ron
1aa8cfb78f Use async_forward_entry_setups instead of async_setup_platforms 2023-02-02 18:10:58 +01:00
misa1515
a29be3127e Create sk.json
Created new translation to slovak language
2023-01-20 21:13:04 +01:00
Ron
ef91580157 Updated version 2023-01-10 09:05:17 +01:00
Ron
1c3e5a4385 Merge pull request #61 from arniebarni/main
Use local time zone for alarms
2023-01-10 08:33:02 +01:00
arniebarni
b6aebe1637 use local time zone for alarms 2023-01-09 19:47:11 +01:00
Ron
9cf15616ab Updated version 2023-01-09 13:37:22 +01:00
Ron
911ca663ab Fixed timestamp related sensors 2023-01-09 13:37:05 +01:00
Ron
645d3842bd Bumped version 2023-01-07 11:51:04 +01:00
Ron
04aa1e7093 Added SensorStateClass per sensor 2023-01-07 11:50:32 +01:00
Ron
f33c2803bc Add SensorStateClass setting per sensor 2023-01-07 11:50:02 +01:00
Ron
d118638429 Corrected units/device type for some body related sensors 2023-01-06 12:50:22 +01:00
Ron
3103929f5f Updated version and bumped garminconnect pkg version 2023-01-06 12:49:27 +01:00
Ron
842355d273 Added debug logging 2023-01-06 12:48:23 +01:00
Ron
e2728b6bc2 Update manifest.json 2023-01-04 20:08:21 +01:00
Ron
b75133a2d4 Fixed error with stress qualfier 2023-01-04 20:08:07 +01:00
Ron
ecbed70aee Update manifest.json 2023-01-04 19:43:06 +01:00
Ron
a761bc00a3 Update sensor.py 2023-01-04 19:42:46 +01:00
Ron
517bd5af5d Converted alarm time value to datetime 2023-01-04 19:42:20 +01:00
Ron
dc4e7301d8 Update manifest.json 2023-01-04 19:04:57 +01:00
Ron
e7ab9ef561 Update sensor.py 2023-01-04 19:04:04 +01:00
Ron
5438cd2d59 Updated version 2023-01-04 15:04:00 +01:00
Ron
4113550397 Implemented SensorDeviceClass 2023-01-04 15:03:23 +01:00
Ron
4b53fdb162 Implemented SensorDeviceClass 2023-01-04 15:02:47 +01:00
Raimund Huber
fa48f9f4e2 List default for actitivies on sensor, bugfixes 2022-12-08 20:10:58 +01:00
Raimund Huber
4f4d0b8425 Gear service to update default gear per activity on garmin connect 2022-12-06 22:02:51 +01:00
Raimund Huber
8760bc0a78 unreleased python-garminconnect version 2022-12-04 12:35:28 +01:00
Raimund Huber
8efd365163 Fixup exception for non-numeric sensor values e.g. stress_qualifier = UNKNOWN 2022-12-04 12:34:47 +01:00
Raimund Huber
cc6e0af4e5 add basic gear sensors 2022-12-04 12:03:10 +01:00
Ron Klinkien
6a1b5240d7 Update README.md 2022-05-24 21:19:04 +02:00
Ron Klinkien
5155e90c8a Corrected cal to kcal for Netto Galorie Goal 2022-05-24 20:04:55 +02:00
Ron Klinkien
1844b50877 Fixed unit of measurement legacy code 2022-05-24 20:01:16 +02:00
Ron Klinkien
0241207443 Merge pull request #37 from wrt54g/main
Update HACS URL
2022-05-03 08:38:36 +02:00
Sven
eaec42124b Update HACS URL 2022-05-03 08:08:11 +02:00
Ron Klinkien
8dea9bfa4b Return correct results 2022-04-22 11:44:40 +02:00
Ron Klinkien
f13f59ae46 Don't use timestamp for nextalarm sensor 2022-04-22 09:43:30 +02:00
Ron Klinkien
e958d2af89 Remove title of step user 2022-04-11 11:08:18 +02:00
Ron Klinkien
8beb401e41 Remove title of step user 2022-04-11 11:07:38 +02:00
Ron Klinkien
4cd2826643 Update README.md 2022-01-05 10:23:20 +01:00
Ron Klinkien
6a5a7fbd34 Added statistics to sensors
Updated sensor code to newer standard
2021-12-30 11:34:38 +01:00
Ron Klinkien
30fe26751c Update manifest.json 2021-12-29 21:55:46 +01:00
Ron Klinkien
05adddd8e5 Update config_flow.py 2021-12-29 21:54:35 +01:00
Ron Klinkien
3fd9388170 Update __init__.py 2021-12-29 21:54:16 +01:00
Ron Klinkien
dd7cfc021f Update manifest.json 2021-12-29 13:09:07 +01:00
Ron Klinkien
154190f9e9 Update hacs.json 2021-12-24 16:41:58 +01:00
Ron Klinkien
e2d3a61c0e Merge pull request #22 from obbers/main
Remvove country from hacs.json
2021-12-24 16:41:21 +01:00
Ron Klinkien
47717f019c Bumped garminconnect-ha 2021-12-24 13:38:00 +01:00
Ron Klinkien
03f576207f Bumped garminconnect-ha to 0.1.16
Renamed bodyMass to boneMass
2021-12-24 12:37:16 +01:00
Gary Sinclair
db334c0761 Merge branch 'cyberjunky:main' into main 2021-12-23 14:35:08 -06:00
Ron Klinkien
78f5266a5f Bumped garminconnect-ha to 1.0.15 2021-12-23 20:55:53 +01:00
Gary Sinclair
7ce7bd2277 Remove Country from hacs.json 2021-12-08 08:40:03 -06:00
39 changed files with 1952 additions and 246 deletions

22
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,22 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json # Schema for CodeRabbit configurations
language: "en-US"
early_access: true
reviews:
profile: "assertive"
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
path_filters:
- "!tests/**/cassettes/**"
path_instructions:
- path: "tests/**"
instructions: |
- test functions shouldn't have a return type hint
- it's ok to use `assert` instead of `pytest.assume()`
chat:
auto_reply: true

62
.devcontainer.json Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "cyberjunky/home-assistant-garmin_connect",
"image": "mcr.microsoft.com/devcontainers/python:1-3.12",
"postCreateCommand": "scripts/setup",
"forwardPorts": [
8123
],
"portsAttributes": {
"8123": {
"label": "Home Assistant"
},
"0-8122": {
"label": "Auto-Forwarded - Other",
"onAutoForward": "ignore"
},
"8124-999999": {
"label": "Auto-Forwarded - Other",
"onAutoForward": "ignore"
}
},
"customizations": {
"extensions": [
"charliermarsh.ruff",
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance",
"GitHub.copilot"
],
"vscode": {
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.formatting.provider": "ruff",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.rulers": [
100
],
"editor.codeActionsOnSave": {
"source.fixAll": "always",
"source.organizeImports": "always"
},
"files.trimTrailingWhitespace": true
},
"extensions": [
"GitHub.copilot",
"github.vscode-pull-request-github",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-vscode.makefile-tools",
"ryanluker.vscode-coverage-gutters"
]
}
},
"remoteUser": "vscode",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/rust:1": {}
}
}

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [cyberjunky] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

26
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
labels:
- "pr: dependency-update"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
directory: "/"
labels:
- "pr: dependency-update"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10
- package-ecosystem: pip
directory: "/"
labels:
- "pr: dependency-update"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

88
.github/pre-commit-config.yaml vendored Normal file
View File

@@ -0,0 +1,88 @@
repos:
- repo: local
hooks:
- id: codespell
name: Check code for common misspellings
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: codespell
args:
- --quiet-level=2
- --ignore-words-list=hass,ba,fo
- --skip=tests/fixtures/*,custom_components/garmin_connect/translations/*
- id: isort
name: Sort imports
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: isort
- id: pyupgrade
name: Run pyupgrade
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: pyupgrade
files: ^.*.py$
args:
- "--py39-plus"
- id: ruff-check
name: Run ruff check
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: ruff
args:
- check
files: ^((action|custom_components|script|tests)/.+)?[^/]+\.py$
- id: ruff-format
name: Run ruff format
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: ruff
args:
- format
files: ^((action|custom_components|script)/.+)?[^/]+\.py$
- id: check-executables-have-shebangs
name: Check that executables have shebangs
language: system
types: [text, executable]
entry: check-executables-have-shebangs
stages: [commit-msg, post-commit, manual]
- id: check-json
name: Check JSON files
language: system
types: [json]
stages: [commit-msg, post-commit, manual]
entry: check-json
- id: requirements-txt-fixer
name: Check requirements files
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: requirements-txt-fixer
files: ^requirements_.*.txt$
- id: check-ast
name: Check Python AST
language: system
types: [python]
stages: [commit-msg, post-commit, manual]
entry: check-ast
- id: mixed-line-ending
name: Check line nedings
language: system
types: [text]
stages: [commit-msg, post-commit, manual]
entry: mixed-line-ending
args:
- --fix=lf

25
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
changelog:
categories:
- title: '💥 Breaking changes'
labels:
- 'Breaking Change'
- title: '🛎️ Experimental'
labels:
- 'Experimental'
- title: '✨ New features'
labels:
- 'pr: new-feature'
- title: '⚡ Enhancements'
labels:
- 'pr: enhancement'
- title: '♻️ Refactor'
labels:
- 'pr: refactor'
- title: '🐛 Bug Fixes'
labels:
- 'pr: bugfix'

View File

@@ -1,4 +1,3 @@
name: Validate with hassfest
on:
@@ -11,5 +10,5 @@ jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- uses: "actions/checkout@v4"
- uses: home-assistant/actions/hassfest@master

7
.gitignore vendored
View File

@@ -1,3 +1,10 @@
# Translation files
custom_components/garmin_connect/translations
!custom_components/garmin_connect/translations/en.json
# Home Assistant configuration
config
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

89
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,89 @@
ci:
skip:
- mypy
- pylint
default_language_version:
python: python3.11
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
hooks:
- id: pyupgrade
args: [--py310-plus]
- repo: https://github.com/psf/black
rev: 23.12.1
hooks:
- id: black
args:
- --safe
- --quiet
<<: &python-files-with-tests
files: ^((custom_components|tests)/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-docstrings==1.6.0
- pydocstyle==6.1.1
<<: &python-files
files: ^(custom_components/.+)?[^/]+\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.6
hooks:
- id: bandit
args:
- --quiet
- --format=custom
- --configfile=bandit.yaml
<<: *python-files-with-tests
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: detect-private-key
- id: no-commit-to-branch
- id: requirements-txt-fixer
- id: mixed-line-ending
args:
- --fix=lf
stages: [manual]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
additional_dependencies:
- prettier@2.7.1
- prettier-plugin-sort-json@0.0.3
exclude_types:
- python
exclude: manifest\.json$
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.33.0
hooks:
- id: yamllint
- repo: local
hooks:
# Run mypy through our wrapper script in order to get the possible
# pyenv and/or virtualenv activated; it may not have been e.g. if
# committing from a GUI tool that was not launched from an activated
# shell.
- id: mypy
name: Check with mypy
entry: scripts/run-in-env.sh mypy
language: script
types: [python]
<<: *python-files
- id: pylint
name: Check with pylint
entry: scripts/run-in-env.sh pylint
language: script
types: [python]
<<: *python-files

28
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"configurations": [
{
"justMyCode": false,
"name": "Python: Attach Local",
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"request": "attach",
"type": "debugpy"
},
{
"name": "Python: Attach Remote",
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/usr/src/homeassistant"
}
],
"request": "attach",
"type": "debugpy"
}
],
"version": "0.2.0"
}

29
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
"tasks": [
{
"command": "scripts/develop",
"label": "Run Home Assistant",
"problemMatcher": [],
"type": "shell"
},
{
"command": "scripts/setup",
"label": "Upgrade environment",
"problemMatcher": [],
"type": "shell"
},
{
"command": "scripts/test",
"label": "Run tests",
"problemMatcher": [],
"type": "shell"
},
{
"command": "scripts/lint",
"label": "Run lint checks",
"problemMatcher": [],
"type": "shell"
}
],
"version": "2.0.0"
}

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Ron Klinkien
Copyright (c) 2021-2025 Ron Klinkien
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,16 +1,19 @@
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/cyberjunkynl/)
# Garmin Connect
The Garmin Connect integration allows you to expose data from Garmin Connect to Home Assistant.
NOTE: This integration doesn't support 2FA on Garmin Connect yet (support is coming), so if you have enabled it -and want to keep it- this integration doesn't work, it will try to login repeatedly and generate lots of 2FA codes via email.
The change of adding support for it is unlikely since the Garmin Connect API is closed source, and will not be open for open-sourced projects.
## Installation
### HACS - Recommended
- Have [HACS](https://hacs.xyz) installed, this will allow you to easily manage and track updates.
- Add https://github.com/cyberjunky/home-assistant-garmin_connect to custom repositories in HACS
- Inside HACS click 'Explore & download repositories'
- Search for 'Garmin Connect'.
- Click Install below the found integration.
- Click on found integration.
- Click Download this repository with HACS.
- Restart Home-Assistant.
- Follow configuration steps below.
@@ -46,7 +49,6 @@ Daily Step Goal
Total KiloCalories
Active KiloCalories
BMR KiloCalories
Consumed KiloCalories
Burned KiloCalories
Total Distance Mtr
Active Time
@@ -54,6 +56,7 @@ Sedentary Time
Sleeping Time
Awake Duration
Sleep Duration
Total Sleep Duration
Floors Ascended
Floors Descended
Floors Ascended Goal
@@ -77,11 +80,19 @@ Body Battery Most Recent
Average SPO2
Lowest SPO2
Latest SPO2
Next Alarm Time
Total Sleep Duration
HRV Status
Gear Sensors
```
Disabled by default:
```text
Badges
User Points
User Level
Consumed KiloCalories
Remaining KiloCalories
Net Remaining KiloCalories
Net Calorie Goal
@@ -116,22 +127,96 @@ Latest Respiration Update
Highest Respiration
Lowest Respiration
Latest Respiration
Weight
BMI
Body Fat
Body Water
Body Mass
Bone Mass
Muscle Mass
Physique Rating
Visceral Fat
Metabolic Age
Last Activities
Last Activity
```
## Screenshots
![screenshot](https://github.com/cyberjunky/home-assistant-garmin_connect/blob/main/screenshots/garmin_connect.png?raw=true "Screenshot Garmin Connect")
## Tips and Tricks
### Set up an automation using the garmin_connect.add_body_composition service
Useful if you want to pass your weight from another (incompatible) device to Garmin Connect. Garmin Connect does not calculate your BMI when you enter your weight manually so it needs to be passed along for now.
```
alias: uiSendWeightToGarminConnect
description: ""
trigger:
- platform: state
entity_id:
- sensor.weight
condition:
- condition: and
conditions:
- condition: numeric_state
entity_id: sensor.weight
above: 75
- condition: numeric_state
entity_id: sensor.weight
below: 88
action:
- service: garmin_connect.add_body_composition
data:
entity_id: sensor.weight
weight: "{{trigger.to_state.state}}"
timestamp: "{{ as_timestamp(now()) | timestamp_local}}"
bmi: >-
{{ (trigger.to_state.state | float(0) / 1.86**2 )| round(1, default=0)
}}
mode: single
```
### Examples on how to test actions from HA GUI
#### Add Body Composition
```
action: garmin_connect.add_body_composition
data:
entity_id: sensor.weight
weight: 87
bmi: 25.5
bone_mass: 4.8
```
See the action template for other available values to add
NOTE: You need to enable the Weight entity
#### Set Active Gear
```
action: garmin_connect.set_active_gear
data:
entity_id: sensor.adidas
activity_type: running
setting: set as default
```
#### Add Blood Pressure
```
action: garmin_connect.add_blood_pressure
data:
entity_id: sensor.min_heart_rate
systolic: 120
diastolic: 80
pulse: 60
timestamp: 2025-1-21T07:34:00.000Z
notes: Measured with Beurer BC54
```
## Debugging
Add the relevant lines below to the `configuration.yaml`:

0
constraints.txt Normal file
View File

View File

@@ -1,21 +1,32 @@
"""The Garmin Connect integration."""
from datetime import date
import logging
from garminconnect_ha import (
import asyncio
from collections.abc import Awaitable
from datetime import datetime, timedelta
import logging
from zoneinfo import ZoneInfo
from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
GarminConnectTooManyRequestsError,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
import requests
from .const import DATA_COORDINATOR, DEFAULT_UPDATE_INTERVAL, DOMAIN
from .const import (
DATA_COORDINATOR,
DAY_TO_NUMBER,
DEFAULT_UPDATE_INTERVAL,
DOMAIN,
LEVEL_POINTS,
Gear,
)
_LOGGER = logging.getLogger(__name__)
@@ -33,11 +44,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {
DATA_COORDINATOR: coordinator,
}
hass.data[DOMAIN][entry.entry_id] = {DATA_COORDINATOR: coordinator}
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@@ -57,46 +66,121 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
"""Initialize the Garmin Connect hub."""
self.entry = entry
self.hass = hass
self.in_china = False
self._api = Garmin(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD])
self.country = self.hass.config.country
if self.country == "CN":
self.in_china = True
_LOGGER.debug("Country: %s", self.country)
super().__init__(
hass, _LOGGER, name=DOMAIN, update_interval=DEFAULT_UPDATE_INTERVAL
)
self.time_zone = self.hass.config.time_zone
_LOGGER.debug("Time zone: %s", self.time_zone)
self.api = Garmin(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD], self.in_china)
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=DEFAULT_UPDATE_INTERVAL)
async def async_login(self) -> bool:
"""Login to Garmin Connect."""
try:
await self.hass.async_add_executor_job(self._api.login)
await self.hass.async_add_executor_job(self.api.login)
except (
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
) as err:
_LOGGER.error("Error occurred during Garmin Connect login request: %s", err)
return False
except (GarminConnectConnectionError) as err:
_LOGGER.error(
"Connection error occurred during Garmin Connect login request: %s", err
)
except GarminConnectConnectionError as err:
_LOGGER.error("Connection error occurred during Garmin Connect login request: %s", err)
raise ConfigEntryNotReady from err
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Unknown error occurred during Garmin Connect login request"
)
_LOGGER.exception("Unknown error occurred during Garmin Connect login request")
return False
return True
async def _async_update_data(self) -> dict:
"""Fetch data from Garmin Connect."""
summary = {}
body = {}
alarms = {}
gear = {}
gear_stats = {}
gear_defaults = {}
activity_types = {}
last_activities = []
sleep_data = {}
sleep_score = None
sleep_time_seconds = None
hrv_data = {}
hrv_status = {"status": "unknown"}
next_alarms = []
today = datetime.now(ZoneInfo(self.time_zone)).date()
try:
# User summary
summary = await self.hass.async_add_executor_job(
self._api.get_user_summary, date.today().isoformat()
self.api.get_user_summary, today.isoformat()
)
_LOGGER.debug("Summary data fetched: %s", summary)
# Body composition
body = await self.hass.async_add_executor_job(
self._api.get_body_composition, date.today().isoformat()
self.api.get_body_composition, today.isoformat()
)
alarms = await self.hass.async_add_executor_job(self._api.get_device_alarms)
_LOGGER.debug("Body data fetched: %s", body)
# Last activities
last_activities = await self.hass.async_add_executor_job(
self.api.get_activities_by_date,
(today - timedelta(days=7)).isoformat(),
(today + timedelta(days=1)).isoformat(),
)
_LOGGER.debug("Activities data fetched: %s", last_activities)
summary["lastActivities"] = last_activities
summary["lastActivity"] = last_activities[0] if last_activities else {}
# Badges
badges = await self.hass.async_add_executor_job(self.api.get_earned_badges)
_LOGGER.debug("Badges data fetched: %s", badges)
summary["badges"] = badges
# Calculate user points and user level
user_points = 0
for badge in badges:
user_points += badge["badgePoints"] * badge["badgeEarnedNumber"]
summary["userPoints"] = user_points
user_level = 0
for level, points in LEVEL_POINTS.items():
if user_points >= points:
user_level = level
summary["userLevel"] = user_level
# Alarms
alarms = await self.hass.async_add_executor_job(self.api.get_device_alarms)
_LOGGER.debug("Alarms data fetched: %s", alarms)
next_alarms = calculate_next_active_alarms(alarms, self.time_zone)
# Activity types
activity_types = await self.hass.async_add_executor_job(self.api.get_activity_types)
_LOGGER.debug("Activity types data fetched: %s", activity_types)
# Sleep data
sleep_data = await self.hass.async_add_executor_job(
self.api.get_sleep_data, today.isoformat()
)
_LOGGER.debug("Sleep data fetched: %s", sleep_data)
# HRV data
hrv_data = await self.hass.async_add_executor_job(
self.api.get_hrv_data, today.isoformat()
)
_LOGGER.debug("HRV data fetched: %s", hrv_data)
except (
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
@@ -105,10 +189,119 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
_LOGGER.debug("Trying to relogin to Garmin Connect")
if not await self.async_login():
raise UpdateFailed(error) from error
return {}
# Gear data
try:
gear = await self.hass.async_add_executor_job(
self.api.get_gear, summary[Gear.USERPROFILE_ID]
)
_LOGGER.debug("Gear data fetched: %s", gear)
gear_defaults = await self.hass.async_add_executor_job(
self.api.get_gear_defaults, summary[Gear.USERPROFILE_ID]
)
_LOGGER.debug("Gear defaults data fetched: %s", gear_defaults)
except (KeyError, TypeError, ValueError, ConnectionError) as err:
_LOGGER.debug("Error while fetching Gear data: %s", err)
# Gear stats data
try:
tasks: list[Awaitable] = [
self.hass.async_add_executor_job(self.api.get_gear_stats, gear_item[Gear.UUID])
for gear_item in gear
]
gear_stats = await asyncio.gather(*tasks)
_LOGGER.debug("Gear stats data fetched: %s", gear_stats)
except (
KeyError,
TypeError,
ValueError,
ConnectionError,
requests.exceptions.HTTPError,
) as err:
_LOGGER.debug("Error while fetching Gear stats data: %s", err)
# Sleep score data
try:
sleep_score = sleep_data["dailySleepDTO"]["sleepScores"]["overall"]["value"]
_LOGGER.debug("Sleep score data: %s", sleep_score)
except KeyError:
_LOGGER.debug("Sleep score data is not available")
# Sleep time seconds data
try:
sleep_time_seconds = sleep_data["dailySleepDTO"]["sleepTimeSeconds"]
_LOGGER.debug("Sleep time seconds data: %s", sleep_time_seconds)
except KeyError:
_LOGGER.debug("Sleep time seconds data is not available")
# HRV data
try:
if hrv_data and "hrvSummary" in hrv_data:
hrv_status = hrv_data["hrvSummary"]
_LOGGER.debug("HRV summary: %s", hrv_status)
except KeyError:
_LOGGER.debug("HRV data is not available")
return {
**summary,
**body["totalAverage"],
"nextAlarm": alarms,
"nextAlarm": next_alarms,
"gear": gear,
"gearStats": gear_stats,
"activityTypes": activity_types,
"gearDefaults": gear_defaults,
"sleepScore": sleep_score,
"sleepTimeSeconds": sleep_time_seconds,
"hrvStatus": hrv_status,
}
def calculate_next_active_alarms(alarms, time_zone):
"""
Calculate garmin next active alarms from settings.
Alarms are sorted by time.
Example of alarms data:
Alarms data fetched: [{'alarmMode': 'OFF', 'alarmTime': 1233, 'alarmDays': ['ONCE'], 'alarmSound': 'TONE_AND_VIBRATION', 'alarmId': 1737308355, 'changeState': 'UNCHANGED', 'backlight': 'ON', 'enabled': None, 'alarmMessage': None, 'alarmImageId': None, 'alarmIcon': None, 'alarmType': None}]
"""
active_alarms = []
now = datetime.now(ZoneInfo(time_zone))
_LOGGER.debug("Now: %s, Alarms: %s", now, alarms)
for alarm_setting in alarms:
if alarm_setting["alarmMode"] != "ON":
continue
for day in alarm_setting["alarmDays"]:
alarm_time = alarm_setting["alarmTime"]
_LOGGER.debug("Alarm time: %s, Alarm day: %s", alarm_time, day)
if day == "ONCE":
midnight = datetime.combine(
now.date(), datetime.min.time(), tzinfo=ZoneInfo(time_zone)
)
alarm = midnight + timedelta(minutes=alarm_time)
_LOGGER.debug("Midnight: %s, Alarm: %s", midnight, alarm_time)
# If the alarm time is in the past, move it to the next day
if alarm < now:
alarm += timedelta(days=1)
else:
start_of_week = datetime.combine(
now.date() - timedelta(days=now.date().isoweekday() % 7),
datetime.min.time(),
tzinfo=ZoneInfo(time_zone),
)
days_to_add = DAY_TO_NUMBER[day] % 7
alarm = start_of_week + timedelta(minutes=alarm_time, days=days_to_add)
_LOGGER.debug("Start of week: %s, Alarm: %s", start_of_week, alarm)
# If the alarm time is in the past, move it to the next week
if alarm < now:
alarm += timedelta(days=7)
active_alarms.append(alarm.isoformat())
return sorted(active_alarms) if active_alarms else None

View File

@@ -1,49 +0,0 @@
"""Utility method for converting Garmin Connect alarms to python datetime."""
from datetime import date, datetime, timedelta
import logging
_LOGGER = logging.getLogger(__name__)
DAY_TO_NUMBER = {
"Mo": 1,
"M": 1,
"Tu": 2,
"We": 3,
"W": 3,
"Th": 4,
"Fr": 5,
"F": 5,
"Sa": 6,
"Su": 7,
}
def calculate_next_active_alarms(alarms):
"""
Calculate garmin next active alarms from settings.
Alarms are sorted by time
"""
active_alarms = []
for alarm_setting in alarms:
if alarm_setting["alarmMode"] != "ON":
continue
for day in alarm_setting["alarmDays"]:
alarm_time = alarm_setting["alarmTime"]
if day == "ONCE":
midnight = datetime.combine(date.today(), datetime.min.time())
alarm = midnight + timedelta(minutes=alarm_time)
if alarm < datetime.now():
alarm += timedelta(days=1)
else:
start_of_week = datetime.combine(
date.today() - timedelta(days=datetime.today().isoweekday() % 7),
datetime.min.time(),
)
days_to_add = DAY_TO_NUMBER[day] % 7
alarm = start_of_week + timedelta(minutes=alarm_time, days=days_to_add)
if alarm < datetime.now():
alarm += timedelta(days=7)
active_alarms.append(alarm.isoformat())
return sorted(active_alarms) if active_alarms else None

View File

@@ -1,16 +1,16 @@
"""Config flow for Garmin Connect integration."""
import logging
from garminconnect_ha import (
from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
GarminConnectTooManyRequestsError,
)
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_ID, CONF_PASSWORD, CONF_USERNAME
import voluptuous as vol
from .const import DOMAIN

View File

@@ -1,33 +1,83 @@
"""Constants for the Garmin Connect integration."""
from datetime import timedelta
from homeassistant.const import (
DEVICE_CLASS_TIMESTAMP,
LENGTH_METERS,
MASS_KILOGRAMS,
PERCENTAGE,
TIME_MINUTES,
TIME_YEARS,
)
from datetime import timedelta
from typing import NamedTuple
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.const import PERCENTAGE, UnitOfLength, UnitOfMass, UnitOfTime
DOMAIN = "garmin_connect"
ATTRIBUTION = "connect.garmin.com"
DATA_COORDINATOR = "coordinator"
DEFAULT_UPDATE_INTERVAL = timedelta(minutes=5)
DAY_TO_NUMBER = {
"Mo": 1,
"M": 1,
"Tu": 2,
"We": 3,
"W": 3,
"Th": 4,
"Fr": 5,
"F": 5,
"Sa": 6,
"Su": 7,
}
LEVEL_POINTS = {
1: 0,
2: 20,
3: 60,
4: 140,
5: 300,
6: 620,
7: 1260,
8: 2540,
9: 5100,
10: 10220,
}
GARMIN_ENTITY_LIST = {
"totalSteps": ["Total Steps", "steps", "mdi:walk", None, True],
"dailyStepGoal": ["Daily Step Goal", "steps", "mdi:walk", None, True],
"totalKilocalories": ["Total KiloCalories", "kcal", "mdi:food", None, True],
"activeKilocalories": ["Active KiloCalories", "kcal", "mdi:food", None, True],
"bmrKilocalories": ["BMR KiloCalories", "kcal", "mdi:food", None, True],
"consumedKilocalories": ["Consumed KiloCalories", "kcal", "mdi:food", None, False],
"burnedKilocalories": ["Burned KiloCalories", "kcal", "mdi:food", None, True],
"totalSteps": ["Total Steps", "steps", "mdi:walk", None, SensorStateClass.TOTAL, True],
"dailyStepGoal": ["Daily Step Goal", "steps", "mdi:walk", None, SensorStateClass.TOTAL, True],
"totalKilocalories": [
"Total KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
True,
],
"activeKilocalories": [
"Active KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
True,
],
"bmrKilocalories": ["BMR KiloCalories", "kcal", "mdi:food", None, SensorStateClass.TOTAL, True],
"consumedKilocalories": [
"Consumed KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
False,
],
"burnedKilocalories": [
"Burned KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
True,
],
"remainingKilocalories": [
"Remaining KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
False,
],
"netRemainingKilocalories": [
@@ -35,36 +85,48 @@ GARMIN_ENTITY_LIST = {
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
False,
],
"netCalorieGoal": ["Net Calorie Goal", "cal", "mdi:food", None, False],
"netCalorieGoal": ["Net Calorie Goal", "kcal", "mdi:food", None, SensorStateClass.TOTAL, False],
"totalDistanceMeters": [
"Total Distance Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:walk",
None,
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
True,
],
"wellnessStartTimeLocal": [
"Wellness Start Time",
None,
"mdi:clock",
DEVICE_CLASS_TIMESTAMP,
SensorDeviceClass.TIMESTAMP,
None,
False,
],
"wellnessEndTimeLocal": [
"Wellness End Time",
None,
"mdi:clock",
DEVICE_CLASS_TIMESTAMP,
SensorDeviceClass.TIMESTAMP,
None,
False,
],
"wellnessDescription": [
"Wellness Description",
"",
"mdi:clock",
None,
SensorStateClass.TOTAL,
False,
],
"wellnessDescription": ["Wellness Description", "", "mdi:clock", None, False],
"wellnessDistanceMeters": [
"Wellness Distance Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:walk",
None,
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
False,
],
"wellnessActiveKilocalories": [
@@ -72,66 +134,151 @@ GARMIN_ENTITY_LIST = {
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
False,
],
"wellnessKilocalories": [
"Wellness KiloCalories",
"kcal",
"mdi:food",
None,
SensorStateClass.TOTAL,
False,
],
"wellnessKilocalories": ["Wellness KiloCalories", "kcal", "mdi:food", None, False],
"highlyActiveSeconds": [
"Highly Active Time",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:fire",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"activeSeconds": ["Active Time", TIME_MINUTES, "mdi:fire", None, True],
"sedentarySeconds": ["Sedentary Time", TIME_MINUTES, "mdi:seat", None, True],
"sleepingSeconds": ["Sleeping Time", TIME_MINUTES, "mdi:sleep", None, True],
"activeSeconds": [
"Active Time",
UnitOfTime.MINUTES,
"mdi:fire",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"sedentarySeconds": [
"Sedentary Time",
UnitOfTime.MINUTES,
"mdi:seat",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"sleepingSeconds": [
"Sleeping Time",
UnitOfTime.MINUTES,
"mdi:sleep",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"measurableAwakeDuration": [
"Awake Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:sleep",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"measurableAsleepDuration": [
"Sleep Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:sleep",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"floorsAscendedInMeters": [
"Floors Ascended Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:stairs",
None,
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
False,
],
"floorsDescendedInMeters": [
"Floors Descended Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:stairs",
None,
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
False,
],
"floorsAscended": ["Floors Ascended", "floors", "mdi:stairs", None, True],
"floorsDescended": ["Floors Descended", "floors", "mdi:stairs", None, True],
"floorsAscended": [
"Floors Ascended",
"floors",
"mdi:stairs",
None,
SensorStateClass.TOTAL,
True,
],
"floorsDescended": [
"Floors Descended",
"floors",
"mdi:stairs",
None,
SensorStateClass.TOTAL,
True,
],
"userFloorsAscendedGoal": [
"Floors Ascended Goal",
"floors",
"mdi:stairs",
None,
SensorStateClass.TOTAL,
True,
],
"minHeartRate": ["Min Heart Rate", "bpm", "mdi:heart-pulse", None, True],
"maxHeartRate": ["Max Heart Rate", "bpm", "mdi:heart-pulse", None, True],
"restingHeartRate": ["Resting Heart Rate", "bpm", "mdi:heart-pulse", None, True],
"minAvgHeartRate": ["Min Avg Heart Rate", "bpm", "mdi:heart-pulse", None, False],
"maxAvgHeartRate": ["Max Avg Heart Rate", "bpm", "mdi:heart-pulse", None, False],
"minHeartRate": [
"Min Heart Rate",
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
True,
],
"maxHeartRate": [
"Max Heart Rate",
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
True,
],
"restingHeartRate": [
"Resting Heart Rate",
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
True,
],
"minAvgHeartRate": [
"Min Avg Heart Rate",
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
False,
],
"maxAvgHeartRate": [
"Max Avg Heart Rate",
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
False,
],
"abnormalHeartRateAlertsCount": [
"Abnormal HR Counts",
None,
"mdi:heart-pulse",
None,
SensorStateClass.TOTAL,
False,
],
"lastSevenDaysAvgRestingHeartRate": [
@@ -139,59 +286,88 @@ GARMIN_ENTITY_LIST = {
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.MEASUREMENT,
False,
],
"averageStressLevel": ["Avg Stress Level", "lvl", "mdi:flash-alert", None, True],
"maxStressLevel": ["Max Stress Level", "lvl", "mdi:flash-alert", None, True],
"stressQualifier": ["Stress Qualifier", None, "mdi:flash-alert", None, False],
"stressDuration": ["Stress Duration", TIME_MINUTES, "mdi:flash-alert", None, False],
"restStressDuration": [
"Rest Stress Duration",
TIME_MINUTES,
"averageStressLevel": [
"Avg Stress Level",
"lvl",
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
True,
],
"maxStressLevel": [
"Max Stress Level",
"lvl",
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
True,
],
"stressQualifier": ["Stress Qualifier", None, "mdi:flash-alert", None, None, False],
"stressDuration": [
"Stress Duration",
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"restStressDuration": [
"Rest Stress Duration",
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"activityStressDuration": [
"Activity Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"uncategorizedStressDuration": [
"Uncat. Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"totalStressDuration": [
"Total Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"lowStressDuration": [
"Low Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"mediumStressDuration": [
"Medium Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"highStressDuration": [
"High Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
True,
],
"stressPercentage": [
@@ -199,6 +375,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"restStressPercentage": [
@@ -206,6 +383,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"activityStressPercentage": [
@@ -213,6 +391,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"uncategorizedStressPercentage": [
@@ -220,6 +399,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"lowStressPercentage": [
@@ -227,6 +407,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"mediumStressPercentage": [
@@ -234,6 +415,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"highStressPercentage": [
@@ -241,27 +423,31 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.MEASUREMENT,
False,
],
"moderateIntensityMinutes": [
"Moderate Intensity",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"vigorousIntensityMinutes": [
"Vigorous Intensity",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:run-fast",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"intensityMinutesGoal": [
"Intensity Goal",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:run-fast",
None,
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"bodyBatteryChargedValue": [
@@ -269,6 +455,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:battery-charging-100",
None,
SensorStateClass.TOTAL,
True,
],
"bodyBatteryDrainedValue": [
@@ -276,6 +463,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:battery-alert-variant-outline",
None,
SensorStateClass.TOTAL,
True,
],
"bodyBatteryHighestValue": [
@@ -283,6 +471,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:battery-heart",
None,
SensorStateClass.TOTAL,
True,
],
"bodyBatteryLowestValue": [
@@ -290,6 +479,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:battery-heart-outline",
None,
SensorStateClass.TOTAL,
True,
],
"bodyBatteryMostRecentValue": [
@@ -297,16 +487,39 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:battery-positive",
None,
SensorStateClass.TOTAL,
True,
],
"averageSpo2": [
"Average SPO2",
PERCENTAGE,
"mdi:diabetes",
None,
SensorStateClass.MEASUREMENT,
True,
],
"lowestSpo2": [
"Lowest SPO2",
PERCENTAGE,
"mdi:diabetes",
None,
SensorStateClass.MEASUREMENT,
True,
],
"latestSpo2": [
"Latest SPO2",
PERCENTAGE,
"mdi:diabetes",
None,
SensorStateClass.MEASUREMENT,
True,
],
"averageSpo2": ["Average SPO2", PERCENTAGE, "mdi:diabetes", None, True],
"lowestSpo2": ["Lowest SPO2", PERCENTAGE, "mdi:diabetes", None, True],
"latestSpo2": ["Latest SPO2", PERCENTAGE, "mdi:diabetes", None, True],
"latestSpo2ReadingTimeLocal": [
"Latest SPO2 Time",
None,
"mdi:diabetes",
DEVICE_CLASS_TIMESTAMP,
SensorDeviceClass.TIMESTAMP,
None,
False,
],
"averageMonitoringEnvironmentAltitude": [
@@ -314,6 +527,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:image-filter-hdr",
None,
SensorStateClass.TOTAL,
False,
],
"highestRespirationValue": [
@@ -321,6 +535,7 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.MEASUREMENT,
False,
],
"lowestRespirationValue": [
@@ -328,6 +543,7 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.MEASUREMENT,
False,
],
"latestRespirationValue": [
@@ -335,23 +551,135 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.MEASUREMENT,
False,
],
"latestRespirationTimeGMT": [
"Latest Respiration Update",
None,
"mdi:progress-clock",
DEVICE_CLASS_TIMESTAMP,
SensorDeviceClass.TIMESTAMP,
None,
False,
],
"weight": ["Weight", MASS_KILOGRAMS, "mdi:weight-kilogram", None, False],
"bmi": ["BMI", "bmi", "mdi:food", None, False],
"bodyFat": ["Body Fat", PERCENTAGE, "mdi:food", None, False],
"bodyWater": ["Body Water", PERCENTAGE, "mdi:water-percent", None, False],
"bodyMass": ["Body Mass", MASS_KILOGRAMS, "mdi:food", None, False],
"muscleMass": ["Muscle Mass", MASS_KILOGRAMS, "mdi:dumbbell", None, False],
"physiqueRating": ["Physique Rating", None, "mdi:numeric", None, False],
"visceralFat": ["Visceral Fat", PERCENTAGE, "mdi:food", None, False],
"metabolicAge": ["Metabolic Age", TIME_YEARS, "mdi:calendar-heart", None, False],
"nextAlarm": ["Next Alarm Time", None, "mdi:alarm", DEVICE_CLASS_TIMESTAMP, True],
"weight": [
"Weight",
UnitOfMass.KILOGRAMS,
"mdi:weight-kilogram",
SensorDeviceClass.WEIGHT,
SensorStateClass.MEASUREMENT,
False,
],
"bmi": ["BMI", "bmi", "mdi:food", None, SensorStateClass.MEASUREMENT, False],
"bodyFat": ["Body Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.MEASUREMENT, False],
"bodyWater": [
"Body Water",
PERCENTAGE,
"mdi:water-percent",
None,
SensorStateClass.MEASUREMENT,
False,
],
"boneMass": [
"Bone Mass",
UnitOfMass.KILOGRAMS,
"mdi:bone",
SensorDeviceClass.WEIGHT,
SensorStateClass.MEASUREMENT,
False,
],
"muscleMass": [
"Muscle Mass",
UnitOfMass.KILOGRAMS,
"mdi:dumbbell",
SensorDeviceClass.WEIGHT,
SensorStateClass.MEASUREMENT,
False,
],
"physiqueRating": [
"Physique Rating",
None,
"mdi:numeric",
None,
SensorStateClass.MEASUREMENT,
False,
],
"visceralFat": [
"Visceral Fat",
PERCENTAGE,
"mdi:food",
None,
SensorStateClass.MEASUREMENT,
False,
],
"metabolicAge": [
"Metabolic Age",
UnitOfTime.YEARS,
"mdi:calendar-heart",
None,
SensorStateClass.MEASUREMENT,
False,
],
"nextAlarm": ["Next Alarm Time", None, "mdi:alarm", SensorDeviceClass.TIMESTAMP, None, True],
"lastActivities": ["Last Activities", None, "mdi:numeric", None, SensorStateClass.TOTAL, False],
"lastActivity": ["Last Activity", None, "mdi:walk", None, None, False],
"badges": ["Badges", None, "mdi:medal", None, SensorStateClass.TOTAL, False],
"userPoints": ["User Points", None, "mdi:counter", None, SensorStateClass.TOTAL, False],
"userLevel": [
"User Level",
None,
"mdi:star-four-points-circle",
None,
SensorStateClass.TOTAL,
False,
],
"sleepScore": [
"Sleep Score",
None,
"mdi:sleep",
None,
SensorStateClass.MEASUREMENT,
True,
],
"sleepTimeSeconds": [
"Total Sleep Duration",
UnitOfTime.MINUTES,
"mdi:sleep",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
True,
],
"hrvStatus": [
"HRV Status",
None,
"mdi:heart-pulse",
None,
None,
True,
],
}
GEAR_ICONS = {
"Shoes": "mdi:shoe-sneaker",
"Bike": "mdi:bike",
"Other": "mdi:basketball",
"Golf Clubs": "mdi:golf",
}
class ServiceSetting(NamedTuple):
"""Options for the service settings, see services.yaml"""
ONLY_THIS_AS_DEFAULT = "set this as default, unset others"
DEFAULT = "set as default"
UNSET_DEFAULT = "unset default"
class Gear(NamedTuple):
"""Options for the gear settings, see services.yaml"""
UUID = "uuid"
TYPE_KEY = "typeKey"
TYPE_ID = "typeId"
USERPROFILE_ID = "userProfileId"
ACTIVITY_TYPE_PK = "activityTypePk"

View File

@@ -1,11 +1,12 @@
{
"domain": "garmin_connect",
"name": "Garmin Connect",
"documentation": "https://github.com/cyberjunky/home-assistant-garmin_connect",
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
"requirements": ["garminconnect_ha==0.1.13"],
"codeowners": ["@cyberjunky"],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/cyberjunky/home-assistant-garmin_connect",
"iot_class": "cloud_polling",
"version": "0.1.1"
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
"requirements": ["garminconnect>=0.2.24"],
"version": "0.2.30"
}

View File

@@ -1,51 +1,59 @@
"""Platform for Garmin Connect integration."""
from __future__ import annotations
import datetime
import logging
from numbers import Number
from zoneinfo import ZoneInfo
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID, DEVICE_CLASS_TIMESTAMP
from homeassistant.const import ATTR_ENTITY_ID, CONF_ID, UnitOfLength
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import IntegrationError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
import voluptuous as vol
from .alarm_util import calculate_next_active_alarms
from .const import (
ATTRIBUTION,
DATA_COORDINATOR,
DOMAIN as GARMIN_DOMAIN,
GARMIN_ENTITY_LIST,
GEAR_ICONS,
Gear,
ServiceSetting,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
) -> None:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities) -> None:
"""Set up Garmin Connect sensor based on a config entry."""
coordinator: DataUpdateCoordinator = hass.data[GARMIN_DOMAIN][entry.entry_id][
DATA_COORDINATOR
]
coordinator: DataUpdateCoordinator = hass.data[GARMIN_DOMAIN][entry.entry_id][DATA_COORDINATOR]
unique_id = entry.data[CONF_ID]
entities = []
for (
sensor_type,
(name, unit, icon, device_class, enabled_by_default),
(name, unit, icon, device_class, state_class, enabled_by_default),
) in GARMIN_ENTITY_LIST.items():
_LOGGER.debug(
"Registering entity: %s, %s, %s, %s, %s, %s",
"Registering entity: %s, %s, %s, %s, %s, %s, %s",
sensor_type,
name,
unit,
icon,
device_class,
state_class,
enabled_by_default,
)
entities.append(
@@ -57,11 +65,93 @@ async def async_setup_entry(
unit,
icon,
device_class,
state_class,
enabled_by_default,
)
)
if "gear" in coordinator.data:
for gear_item in coordinator.data["gear"]:
name = gear_item["displayName"]
sensor_type = gear_item["gearTypeName"]
uuid = gear_item[Gear.UUID]
unit = UnitOfLength.KILOMETERS
icon = GEAR_ICONS.get(sensor_type, "mdi:shoe-print")
device_class = SensorDeviceClass.DISTANCE
state_class = SensorStateClass.TOTAL
enabled_by_default = True
_LOGGER.debug(
"Registering entity: %s, %s, %s, %s, %s, %s, %s, %s",
sensor_type,
name,
unit,
icon,
uuid,
device_class,
state_class,
enabled_by_default,
)
entities.append(
GarminConnectGearSensor(
coordinator,
unique_id,
sensor_type,
name,
unit,
icon,
uuid,
device_class,
state_class,
enabled_by_default,
)
)
async_add_entities(entities)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
"set_active_gear",
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Required("activity_type"): str,
vol.Required("setting"): str,
},
"set_active_gear",
)
platform.async_register_entity_service(
"add_body_composition",
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Optional("timestamp"): str,
vol.Required("weight"): vol.Coerce(float),
vol.Optional("percent_fat"): vol.Coerce(float),
vol.Optional("percent_hydration"): vol.Coerce(float),
vol.Optional("visceral_fat_mass"): vol.Coerce(float),
vol.Optional("bone_mass"): vol.Coerce(float),
vol.Optional("muscle_mass"): vol.Coerce(float),
vol.Optional("basal_met"): vol.Coerce(float),
vol.Optional("active_met"): vol.Coerce(float),
vol.Optional("physique_rating"): vol.Coerce(float),
vol.Optional("metabolic_age"): vol.Coerce(float),
vol.Optional("visceral_fat_rating"): vol.Coerce(float),
vol.Optional("bmi"): vol.Coerce(float),
},
"add_body_composition",
)
platform.async_register_entity_service(
"add_blood_pressure",
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Optional("timestamp"): str,
vol.Required("systolic"): int,
vol.Required("diastolic"): int,
vol.Required("pulse"): int,
vol.Optional("notes"): str,
},
"add_blood_pressure",
)
class GarminConnectSensor(CoordinatorEntity, SensorEntity):
@@ -76,6 +166,7 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
unit,
icon,
device_class,
state_class,
enabled_default: bool = True,
):
"""Initialize a Garmin Connect sensor."""
@@ -83,56 +174,60 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
self._unique_id = unique_id
self._type = sensor_type
self._name = name
self._unit = unit
self._icon = icon
self._device_class = device_class
self._state_class = state_class
self._enabled_default = enabled_default
@property
def name(self):
"""Return the name of the sensor."""
return self._name
self._attr_name = name
self._attr_device_class = self._device_class
self._attr_icon = icon
self._attr_native_unit_of_measurement = unit
self._attr_unique_id = f"{self._unique_id}_{self._type}"
self._attr_state_class = state_class
@property
def icon(self):
"""Return the icon to use in the frontend."""
return self._icon
@property
def state(self):
def native_value(self):
"""Return the state of the sensor."""
if not self.coordinator.data or not self.coordinator.data[self._type]:
if not self.coordinator.data:
return None
value = self.coordinator.data[self._type]
if "Duration" in self._type or "Seconds" in self._type:
value = value // 60
value = self.coordinator.data.get(self._type)
if value is None:
return None
if self._type == "lastActivities" or self._type == "badges":
value = len(self.coordinator.data[self._type])
if self._type == "lastActivity":
value = self.coordinator.data[self._type]["activityName"]
elif self._type == "hrvStatus":
value = self.coordinator.data[self._type]["status"].capitalize()
elif "Duration" in self._type or "Seconds" in self._type:
value = round(value // 60, 2)
elif "Mass" in self._type or self._type == "weight":
value = value / 1000
value = round(value / 1000, 2)
elif self._type == "nextAlarm":
active_alarms = calculate_next_active_alarms(
self.coordinator.data[self._type]
)
active_alarms = self.coordinator.data[self._type]
_LOGGER.debug("Active alarms: %s", active_alarms)
if active_alarms:
_LOGGER.debug("Next alarm: %s", active_alarms[0])
value = active_alarms[0]
else:
value = None
if self._device_class == DEVICE_CLASS_TIMESTAMP:
return value
elif self._type == "stressQualifier":
value = value.capitalize()
return round(value, 2)
@property
def unique_id(self) -> str:
"""Return the unique ID for this sensor."""
return f"{self._unique_id}_{self._type}"
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit
if self._device_class == SensorDeviceClass.TIMESTAMP:
if value:
value = datetime.datetime.fromisoformat(value).replace(
tzinfo=ZoneInfo(self.coordinator.time_zone)
)
return round(value, 2) if isinstance(value, Number) else value
@property
def extra_state_attributes(self):
@@ -141,14 +236,30 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
return {}
attributes = {
"source": self.coordinator.data["source"],
"last_synced": self.coordinator.data["lastSyncTimestampGMT"],
ATTR_ATTRIBUTION: ATTRIBUTION,
}
# Only add the last 5 activities for performance reasons
if self._type == "lastActivities":
activities = self.coordinator.data.get(self._type, [])
sorted_activities = sorted(activities, key=lambda x: x["activityId"])
attributes["last_activities"] = sorted_activities[-5:]
if self._type == "lastActivity":
attributes = {**attributes, **self.coordinator.data[self._type]}
# Only add the last 10 badges for performance reasons
if self._type == "badges":
badges = self.coordinator.data.get(self._type, [])
sorted_badges = sorted(badges, key=lambda x: x["badgeEarnedDate"])
attributes["badges"] = sorted_badges[-10:]
if self._type == "nextAlarm":
attributes["next_alarms"] = calculate_next_active_alarms(
self.coordinator.data[self._type]
)
attributes["next_alarms"] = self.coordinator.data[self._type]
if self._type == "hrvStatus":
attributes = {**attributes, **self.coordinator.data[self._type]}
del attributes["status"]
return attributes
@@ -169,13 +280,230 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
super().available
and self.coordinator.data
and self._type in self.coordinator.data
return super().available and self.coordinator.data and self._type in self.coordinator.data
async def add_body_composition(self, **kwargs):
"""Handle the service call to add body composition."""
weight = kwargs.get("weight")
timestamp = kwargs.get("timestamp")
percent_fat = kwargs.get("percent_fat")
percent_hydration = kwargs.get("percent_hydration")
visceral_fat_mass = kwargs.get("visceral_fat_mass")
bone_mass = kwargs.get("bone_mass")
muscle_mass = kwargs.get("muscle_mass")
basal_met = kwargs.get("basal_met")
active_met = kwargs.get("active_met")
physique_rating = kwargs.get("physique_rating")
metabolic_age = kwargs.get("metabolic_age")
visceral_fat_rating = kwargs.get("visceral_fat_rating")
bmi = kwargs.get("bmi")
"""Check for login."""
if not await self.coordinator.async_login():
raise IntegrationError("Failed to login to Garmin Connect, unable to update")
"""Record a weigh in/body composition."""
await self.hass.async_add_executor_job(
self.coordinator.api.add_body_composition,
timestamp,
weight,
percent_fat,
percent_hydration,
visceral_fat_mass,
bone_mass,
muscle_mass,
basal_met,
active_met,
physique_rating,
metabolic_age,
visceral_fat_rating,
bmi,
)
async def add_blood_pressure(self, **kwargs):
"""Handle the service call to add blood pressure."""
timestamp = kwargs.get("timestamp")
systolic = kwargs.get("systolic")
diastolic = kwargs.get("diastolic")
pulse = kwargs.get("pulse")
notes = kwargs.get("notes")
"""Check for login."""
if not await self.coordinator.async_login():
raise IntegrationError("Failed to login to Garmin Connect, unable to update")
"""Record a blood pressure measurement."""
await self.hass.async_add_executor_job(
self.coordinator.api.set_blood_pressure, systolic, diastolic, pulse, timestamp, notes
)
class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Garmin Connect Gear Sensor."""
def __init__(
self,
coordinator,
unique_id,
sensor_type,
name,
unit,
icon,
uuid,
device_class: None,
state_class: None,
enabled_default: bool = True,
):
"""Initialize a Garmin Connect Gear sensor."""
super().__init__(coordinator)
self._unique_id = unique_id
self._type = sensor_type
self._device_class = device_class
self._state_class = state_class
self._enabled_default = enabled_default
self._uuid = uuid
self._attr_name = name
self._attr_device_class = self._device_class
self._attr_icon = icon
self._attr_native_unit_of_measurement = unit
self._attr_unique_id = f"{self._unique_id}_{self._uuid}"
self._attr_state_class = self._state_class
@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class
def uuid(self):
"""Return the entity uuid"""
return self._uuid
@property
def native_value(self):
"""Return the state of the sensor."""
if not self.coordinator.data or not self._stats():
return None
value = self._stats()["totalDistance"]
return round(value / 1000, 2)
@property
def extra_state_attributes(self):
"""Return attributes for sensor."""
gear = self._gear()
stats = self._stats()
gear_defaults = self._gear_defaults()
activity_types = self.coordinator.data["activityTypes"]
default_for_activity = self._activity_names_for_gear_defaults(gear_defaults, activity_types)
if not self.coordinator.data or not gear or not stats:
return {}
attributes = {
"last_synced": self.coordinator.data["lastSyncTimestampGMT"],
"total_activities": stats["totalActivities"],
"create_date": stats["createDate"],
"update_date": stats["updateDate"],
"date_begin": gear["dateBegin"],
"date_end": gear["dateEnd"],
"gear_make_name": gear["gearMakeName"],
"gear_model_name": gear["gearModelName"],
"gear_status_name": gear["gearStatusName"],
"custom_make_model": gear["customMakeModel"],
"maximum_meters": gear["maximumMeters"],
}
attributes["default_for_activity"] = (
", ".join(default_for_activity) if default_for_activity else "None"
)
return attributes
def _activity_names_for_gear_defaults(self, gear_defaults, activity_types):
"""Get activity names for gear defaults."""
activity_type_ids = [d["activityTypePk"] for d in gear_defaults]
return [a["typeKey"] for a in activity_types if a["typeId"] in activity_type_ids]
@property
def device_info(self) -> DeviceInfo:
"""Return device information."""
return {
"identifiers": {(GARMIN_DOMAIN, self._unique_id)},
"name": "Garmin Connect",
"manufacturer": "Garmin Connect",
}
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return self._enabled_default
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self.coordinator.data and self._gear()
def _stats(self):
"""Get gear statistics from garmin"""
for gear_stats_item in self.coordinator.data["gearStats"]:
if gear_stats_item[Gear.UUID] == self._uuid:
return gear_stats_item
def _gear(self):
"""Get gear from garmin"""
for gear_item in self.coordinator.data["gear"]:
if gear_item[Gear.UUID] == self._uuid:
return gear_item
def _gear_defaults(self):
"""Get gear defaults"""
return list(
filter(
lambda d: d[Gear.UUID] == self.uuid and d["defaultGear"] is True,
self.coordinator.data["gearDefaults"],
)
)
async def set_active_gear(self, **kwargs):
"""Handle the service call to set active gear."""
activity_type = kwargs.get("activity_type")
setting = kwargs.get("setting")
"""Check for login."""
if not await self.coordinator.async_login():
raise IntegrationError("Failed to login to Garmin Connect, unable to update")
"""Update Garmin Gear settings."""
activity_type_id = next(
filter(
lambda a: a[Gear.TYPE_KEY] == activity_type,
self.coordinator.data["activityTypes"],
)
)[Gear.TYPE_ID]
if setting != ServiceSetting.ONLY_THIS_AS_DEFAULT:
await self.hass.async_add_executor_job(
self.coordinator.api.set_gear_default,
activity_type_id,
self._uuid,
setting == ServiceSetting.DEFAULT,
)
else:
old_default_state = await self.hass.async_add_executor_job(
self.coordinator.api.get_gear_defaults, self.coordinator.data[Gear.USERPROFILE_ID]
)
to_deactivate = list(
filter(
lambda o: o[Gear.ACTIVITY_TYPE_PK] == activity_type_id
and o[Gear.UUID] != self._uuid,
old_default_state,
)
)
for active_gear in to_deactivate:
await self.hass.async_add_executor_job(
self.coordinator.api.set_gear_default,
activity_type_id,
active_gear[Gear.UUID],
False,
)
await self.hass.async_add_executor_job(
self.coordinator.api.set_gear_default, activity_type_id, self._uuid, True
)

View File

@@ -0,0 +1,154 @@
set_active_gear:
name: Set active gear for activity
description: Set active gear for activity.
fields:
entity_id:
name: entity
description: entity
required: true
selector:
entity:
integration: garmin_connect
device_class: garmin_gear
activity_type:
required: true
name: activity type
description: garmin activity type
example: running
default: running
selector:
select:
options:
- running
- cycling
- hiking
- other
- walking
- swimming
setting:
required: true
name: setting
description: gear setting to apply
default: set this as default, unset others
selector:
select:
options:
- set this as default, unset others
- set as default
- unset default
add_body_composition:
name: Adds updated body composition metrics
description: Adds updated body composition metrics.
fields:
entity_id:
name: entity
description: entity
required: true
selector:
entity:
integration: garmin_connect
device_class: weight
weight:
required: true
name: Weight
description: Weight in KG
example: 82.3
timestamp:
required: false
name: Timestamp
description: Datetime string of when the measurements were recorded. Defaults to now.
example: 2023-12-30T07:34:00
bmi:
required: false
name: BMI (Body Mass Index)
description: Body mass index is based on weight and height.
example: 24.7
percent_fat:
required: false
name: Percent Fat
description: Percent body fat
example: 23.6
percent_hydration:
required: false
name: Percent Hydration
description: Percent body hydration
example: 51.2
visceral_fat_mass:
required: false
name: Visceral Fat Mass
description: Estimated mass of visceral fat in KG
example: 45.3
bone_mass:
required: false
name: Bone Mass
description: Estimated mass of bones in KG
example: 10.1
muscle_mass:
required: false
name: Muscle Mass
description: Estimated mass of muscle in KG
example: 15.2
basal_met:
required: false
name: Basel Metabolism
description: Basel metabolism
example: 1900
active_met:
required: false
name: Active Metabolism
description: Active metabolism
example: 840
physique_rating:
required: false
name: Physique Rating
description: Physique Rating
example: 28
metabolic_age:
required: false
name: Metabolic Age
description: Metabolic Age
example: 37
visceral_fat_rating:
required: false
name: Visceral Fat Rating
description: Visceral Fat Rating
example: 10
add_blood_pressure:
name: Adds updated blood pressure metrics
description: Adds updated blood pressure metrics.
fields:
entity_id:
name: entity
description: entity
required: true
selector:
entity:
integration: garmin_connect
device_class: min_heart_rate
systolic:
required: true
name: Systolic
description: Systolic value
example: 120
diastolic:
required: true
name: Diastolic
description: Diastolic value
example: 80
pulse:
required: true
name: Pulse
description: Pulse
example: 60
timestamp:
required: false
name: Timestamp
description: Datetime string of when the measurements were recorded. Defaults to now.
example: 2023-12-30T07:34:00
notes:
required: false
name: Notes
description: Add notes to the measurement
example: 'Measured with Beurer BC54'

View File

@@ -15,8 +15,7 @@
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"description": "Enter your credentials.",
"title": "Garmin Connect"
"description": "Enter your credentials."
}
}
}

View File

@@ -15,9 +15,8 @@
"password": "Password",
"username": "Username"
},
"description": "Enter your credentials.",
"title": "Garmin Connect"
"description": "Enter your credentials."
}
}
}
}
}

View File

@@ -6,7 +6,7 @@
"error": {
"cannot_connect": "Falha na liga\u00e7\u00e3o",
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida",
"unknown": "Erro inesperado"
"unknown": "Erro desconhecido"
},
"step": {
"user": {
@@ -19,4 +19,4 @@
}
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"config": {
"abort": {
"already_configured": "Účet je už nakonfigurovaný"
},
"error": {
"cannot_connect": "Nepodarilo sa pripojiť",
"invalid_auth": "Neplatné overenie",
"too_many_requests": "Príliš veľa žiadostí, skúste to neskôr.",
"unknown": "Neočakávaná chyba"
},
"step": {
"user": {
"data": {
"password": "Heslo",
"username": "Užívateľské meno"
},
"description": "Zadajte svoje poverovacie údaje.",
"title": "Garmin Connect"
}
}
}
}

View File

@@ -1,7 +1,6 @@
{
"name": "Garmin Connect",
"country": "NL",
"render_readme": true,
"domains": ["sensor"]
}
"homeassistant": "2024.4.1",
"hacs": "1.34.0"
}

119
pyproject.toml Normal file
View File

@@ -0,0 +1,119 @@
[tool.isort]
# https://github.com/PyCQA/isort/wiki/isort-Settings
profile = "black"
# will group `import x` and `from x import` of the same module.
force_sort_within_sections = true
known_first_party = ["custom_components"]
combine_as_imports = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
addopts = "-rxf -l --cov=./ --cov-report=xml"
filterwarnings = [
"ignore::DeprecationWarning",
"ignore:It is recommended to use web.AppKey instances for keys",
]
log_format = "%(asctime)s.%(msecs)03d %(levelname)-8s %(threadName)s %(name)s:%(filename)s:%(lineno)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
norecursedirs = [".git"]
[tool.ruff]
fix = true
line-length = 100
show-fixes = true
target-version = "py312"
[tool.lint]
select = ["ALL"]
ignore = [
"ANN001",
"ANN002",
"ANN003",
"ANN101",
"ANN201",
"ANN202",
"ANN204",
"ANN205",
"ANN401",
"ARG001",
"ARG002",
"ARG005",
"ASYNC110",
"BLE001",
"C901",
"COM812",
"D100",
"D101",
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D205",
"D213",
"D400",
"D401",
"D415",
"E501",
"E713",
"EM101",
"EM102",
"F401",
"FBT001",
"FBT002",
"FBT003",
"I001",
"INP001",
"ISC001",
"N812",
"N818",
"PERF401",
"PERF402",
"PIE804",
"PLR0912",
"PLR0913",
"PLR0915",
"PLR1714",
"PLR1722",
"PLR2004",
"PLR5501",
"PTH103",
"PTH107",
"PTH110",
"PTH113",
"PTH123",
"RET502",
"RET503",
"RET504",
"RET507",
"RSE102",
"RUF005",
"RUF012",
"RUF013",
"S105",
"S110",
"SIM102",
"SIM103",
"SIM105",
"SIM108",
"SIM110",
"SIM114",
"SIM117",
"SLF001",
"TCH001",
"TCH002",
"TCH003",
"TID252",
"TRY003",
"TRY201",
"TRY300",
"TRY301",
"TRY400",
"TRY401",
"UP040",
]
fixable = ["ALL"]
unfixable = []

2
requirements_base.txt Normal file
View File

@@ -0,0 +1,2 @@
colorlog==6.9.0
setuptools==75.8.0

View File

@@ -0,0 +1 @@
homeassistant==2025.1.4

8
requirements_lint.txt Normal file
View File

@@ -0,0 +1,8 @@
--requirement requirements_base.txt
codespell==2.4.1
isort==5.13.2
pre-commit==4.1.0
pre-commit-hooks==5.0.0
pyupgrade==3.19.1
ruff==0.9.5
vulture==2.14

23
scripts/develop Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
if [ ! -f "${PWD}/config/configuration.yaml" ]; then
mkdir -p "${PWD}/config"
hass --config "${PWD}/config" --script ensure_config
echo "Updating default configuration."
echo "
logger:
default: info
logs:
custom_components.garmin_connect: debug
" >> "${PWD}/config/configuration.yaml"
fi
# Set the python path to include our custom_components directory
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
# Start Home Assistant
hass --config "${PWD}/config" --debug

7
scripts/install/core Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../.."
bash scripts/install/pip_packages --requirement requirements_core_min.txt

7
scripts/install/core_dev Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/../.."
bash scripts/install/pip_packages "git+https://github.com/home-assistant/core.git@dev"

10
scripts/install/pip_packages Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
python3 -m pip \
install \
--upgrade \
--disable-pip-version-check \
--constraint constraints.txt \
"${@}"

10
scripts/lint Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
pre-commit install-hooks --config .github/pre-commit-config.yaml;
pre-commit run --hook-stage manual --all-files --config .github/pre-commit-config.yaml;
vulture . --min-confidence 75 --ignore-names policy

12
scripts/setup Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
scripts/install/pip_packages "pip<23.2,>=21.3.1"
scripts/install/pip_packages setuptools wheel
scripts/install/pip_packages --requirement requirements_lint.txt
scripts/install/core
pre-commit install --config .github/pre-commit-config.yaml