Compare commits

...

127 Commits

Author SHA1 Message Date
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
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
33 changed files with 1286 additions and 87 deletions

58
.devcontainer.json Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "cyberjunky/home-assistant-garmin_connect",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye",
"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": [
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
],
"vscode": {
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"files.eol": "\n",
"editor.tabSize": 4,
"python.pythonPath": "/usr/local/python/bin/python",
"python.analysis.autoSearchPaths": false,
"python.linting.pylintArgs": [
"--disable",
"import-error"
],
"python.formatting.provider": "black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
},
"extensions": [
"github.vscode-pull-request-github",
"ms-python.python",
"ms-python.vscode-pylance",
"ms-vscode.makefile-tools",
"ryanluker.vscode-coverage-gutters"
]
}
},
"remoteUser": "vscode",
"features": {
"rust": "latest"
}
}

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']

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

@@ -0,0 +1,15 @@
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
ignore:
# Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json
- dependency-name: "homeassistant"

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

@@ -0,0 +1,50 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
hooks:
- id: pyupgrade
stages: [manual]
args:
- "--py39-plus"
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black
stages: [manual]
args:
- --safe
- --quiet
files: ^((custom_components|script|tests)/.+)?[^/]+\.py$
- repo: https://github.com/codespell-project/codespell
rev: v2.1.0
hooks:
- id: codespell
stages: [manual]
args:
- --quiet-level=2
- --ignore-words-list=hass,ba,fo
- --exclude-file=custom_components/hacs/utils/default.repositories
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-executables-have-shebangs
stages: [manual]
- id: check-json
stages: [manual]
- id: requirements-txt-fixer
stages: [manual]
- id: check-ast
stages: [manual]
- id: mixed-line-ending
stages: [manual]
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

12
.gitignore vendored
View File

@@ -1,3 +1,15 @@
# misc
.vscode
outputdata
settings.json
# 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

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"jsonRecursiveSort": true
}

48
.ruff.toml Normal file
View File

@@ -0,0 +1,48 @@
# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml
target-version = "py310"
select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"C", # complexity
"D", # docstrings
"E", # pycodestyle
"F", # pyflakes/autoflake
"ICN001", # import concentions; {name} should be imported as {asname}
"PGH004", # Use specific rule codes when using noqa
"PLC0414", # Useless import alias. Import alias does not rename original package.
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T20", # flake8-print
"TRY004", # Prefer TypeError exception for invalid type
"RUF006", # Store a reference to the return value of asyncio.create_task
"UP", # pyupgrade
"W", # pycodestyle
]
ignore = [
"D202", # No blank lines allowed after function docstring
"D203", # 1 blank line required before class docstring
"D213", # Multi-line docstring summary should start at the second line
"D404", # First word of the docstring should not be This
"D406", # Section name should end with a newline
"D407", # Section name underlining
"D411", # Missing blank line before section
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
]
[flake8-pytest-style]
fixture-parentheses = false
[pyupgrade]
keep-runtime-typing = true
[mccabe]
max-complexity = 25

59
.yamlllint Normal file
View File

@@ -0,0 +1,59 @@
rules:
braces:
level: error
min-spaces-inside: 0
max-spaces-inside: 1
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
brackets:
level: error
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons:
level: error
max-spaces-before: 0
max-spaces-after: 1
commas:
level: error
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments:
level: error
require-starting-space: true
min-spaces-from-content: 2
comments-indentation:
level: error
document-end:
level: error
present: false
document-start:
level: error
present: false
empty-lines:
level: error
max: 1
max-start: 0
max-end: 1
hyphens:
level: error
max-spaces-after: 1
indentation:
level: error
spaces: 2
indent-sequences: true
check-multi-line-strings: false
key-duplicates:
level: error
line-length: disable
new-line-at-end-of-file:
level: error
new-lines:
level: error
type: unix
trailing-spaces:
level: error
truthy:
disable

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Ron Klinkien
Copyright (c) 2021-2024 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

@@ -3,7 +3,7 @@
# 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, 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.
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
@@ -135,6 +135,39 @@ Metabolic Age
![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.my_weight
condition:
- condition: and
conditions:
- condition: numeric_state
entity_id: sensor.my_weight
above: 75
- condition: numeric_state
entity_id: sensor.my_weight
below: 88
action:
- service: garmin_connect.add_body_composition
data:
entity_id: sensor.garmin_connect_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
```
## Debugging
Add the relevant lines below to the `configuration.yaml`:

20
bandit.yaml Normal file
View File

@@ -0,0 +1,20 @@
# https://bandit.readthedocs.io/en/latest/config.html
tests:
- B103
- B108
- B306
- B307
- B313
- B314
- B315
- B316
- B317
- B318
- B319
- B320
- B601
- B602
- B604
- B608
- B609

View File

@@ -1,6 +1,9 @@
"""The Garmin Connect integration."""
from datetime import date
from datetime import timedelta
import logging
import asyncio
from collections.abc import Awaitable
from garminconnect import (
Garmin,
@@ -12,10 +15,16 @@ from garminconnect import (
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.exceptions import ConfigEntryNotReady, IntegrationError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DATA_COORDINATOR, DEFAULT_UPDATE_INTERVAL, DOMAIN
from .const import (
DATA_COORDINATOR,
DEFAULT_UPDATE_INTERVAL,
DOMAIN,
GEAR,
SERVICE_SETTING,
)
_LOGGER = logging.getLogger(__name__)
@@ -33,9 +42,7 @@ 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}
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -57,8 +64,13 @@ 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])
country = self.hass.config.country
if country == "CN":
self.in_china = True
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
@@ -89,17 +101,58 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
async def _async_update_data(self) -> dict:
"""Fetch data from Garmin Connect."""
summary = {}
body = {}
activites = {}
alarms = {}
gear = {}
gear_stats = {}
gear_defaults = {}
activity_types = {}
sleep_data = {}
sleep_score = None
hrv_data = {}
hrvStatus = None
try:
summary = await self.hass.async_add_executor_job(
self._api.get_user_summary, date.today().isoformat()
)
_LOGGER.debug(summary)
_LOGGER.debug(f"Summary data: {summary}")
body = await self.hass.async_add_executor_job(
self._api.get_body_composition, date.today().isoformat()
self._api.get_body_composition, (date.today()-timedelta(days=7)).isoformat(), date.today().isoformat()
)
_LOGGER.debug(body)
_LOGGER.debug(f"Body data: {body}")
activities = await self.hass.async_add_executor_job(
self._api.get_activities_by_date, (date.today()-timedelta(days=7)).isoformat(), (date.today()+timedelta(days=1)).isoformat()
)
_LOGGER.debug(f"Activities data: {activities}")
summary['lastActivities'] = activities
badges = await self.hass.async_add_executor_job(
self._api.get_earned_badges
)
_LOGGER.debug(f"Badges data: {badges}")
summary['badges'] = badges
alarms = await self.hass.async_add_executor_job(self._api.get_device_alarms)
_LOGGER.debug(alarms)
_LOGGER.debug(f"Alarms data: {alarms}")
activity_types = await self.hass.async_add_executor_job(
self._api.get_activity_types
)
_LOGGER.debug(f"Activity types data: {activity_types}")
sleep_data = await self.hass.async_add_executor_job(
self._api.get_sleep_data, date.today().isoformat())
_LOGGER.debug(f"Sleep data: {sleep_data}")
hrv_data = await self.hass.async_add_executor_job(
self._api.get_hrv_data, date.today().isoformat())
_LOGGER.debug(f"hrv data: {hrv_data}")
except (
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
@@ -110,8 +163,132 @@ class GarminConnectDataUpdateCoordinator(DataUpdateCoordinator):
raise UpdateFailed(error) from error
return {}
try:
gear = await self.hass.async_add_executor_job(
self._api.get_gear, summary[GEAR.USERPROFILE_ID]
)
_LOGGER.debug(f"Gear data: {gear}")
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(f"Gear stats data: {gear_stats}")
gear_defaults = await self.hass.async_add_executor_job(
self._api.get_gear_defaults, summary[GEAR.USERPROFILE_ID]
)
_LOGGER.debug(f"Gear defaults data: {gear_defaults}")
except:
_LOGGER.debug("Gear data is not available")
try:
sleep_score = sleep_data["dailySleepDTO"]["sleepScores"]["overall"]["value"]
_LOGGER.debug(f"Sleep score data: {sleep_score}")
except KeyError:
_LOGGER.debug("Sleep score data is not available")
try:
hrvStatus = hrv_data["hrvSummary"]
_LOGGER.debug(f"HRV status: {hrvStatus} ")
except KeyError:
_LOGGER.debug("HRV data is not available")
return {
**summary,
**body["totalAverage"],
**body["dateWeightList"][0],
"nextAlarm": alarms,
"gear": gear,
"gear_stats": gear_stats,
"activity_types": activity_types,
"gear_defaults": gear_defaults,
"sleepScore": sleep_score,
"hrvStatus": hrvStatus,
}
async def set_active_gear(self, entity, service_data):
"""Update Garmin Gear settings"""
if not await self.async_login():
raise IntegrationError(
"Failed to login to Garmin Connect, unable to update"
)
setting = service_data.data["setting"]
activity_type_id = next(
filter(
lambda a: a[GEAR.TYPE_KEY] == service_data.data["activity_type"],
self.data["activity_types"],
)
)[GEAR.TYPE_ID]
if setting != SERVICE_SETTING.ONLY_THIS_AS_DEFAULT:
await self.hass.async_add_executor_job(
self._api.set_gear_default,
activity_type_id,
entity.uuid,
setting == SERVICE_SETTING.DEFAULT,
)
else:
old_default_state = await self.hass.async_add_executor_job(
self._api.get_gear_defaults, self.data[GEAR.USERPROFILE_ID]
)
to_deactivate = list(
filter(
lambda o: o[GEAR.ACTIVITY_TYPE_PK] == activity_type_id
and o[GEAR.UUID] != entity.uuid,
old_default_state,
)
)
for active_gear in to_deactivate:
await self.hass.async_add_executor_job(
self._api.set_gear_default,
activity_type_id,
active_gear[GEAR.UUID],
False,
)
await self.hass.async_add_executor_job(
self._api.set_gear_default, activity_type_id, entity.uuid, True
)
async def add_body_composition(self, entity, service_data):
"""Record a weigh in/body composition"""
if not await self.async_login():
raise IntegrationError(
"Failed to login to Garmin Connect, unable to update"
)
await self.hass.async_add_executor_job(
self._api.add_body_composition,
service_data.data.get("timestamp", None),
service_data.data.get("weight"),
service_data.data.get("percent_fat", None),
service_data.data.get("percent_hydration", None),
service_data.data.get("visceral_fat_mass", None),
service_data.data.get("bone_mass", None),
service_data.data.get("muscle_mass", None),
service_data.data.get("basal_met", None),
service_data.data.get("active_met", None),
service_data.data.get("physique_rating", None),
service_data.data.get("metabolic_age", None),
service_data.data.get("visceral_fat_rating", None),
service_data.data.get("bmi", None)
)
async def add_blood_pressure(self, entity, service_data):
"""Record a blood pressure measurement"""
if not await self.async_login():
raise IntegrationError(
"Failed to login to Garmin Connect, unable to update"
)
await self.hass.async_add_executor_job(
self._api.set_blood_pressure,
service_data.data.get('systolic'),
service_data.data.get('diastolic'),
service_data.data.get('pulse'),
service_data.data.get('note', None)
)

View File

@@ -19,8 +19,7 @@ DAY_TO_NUMBER = {
def calculate_next_active_alarms(alarms):
"""
Calculate garmin next active alarms from settings.
"""Calculate garmin next active alarms from settings.
Alarms are sorted by time
"""

View File

@@ -1,12 +1,12 @@
"""Constants for the Garmin Connect integration."""
from datetime import timedelta
from typing import NamedTuple
from homeassistant.const import (
LENGTH_METERS,
MASS_KILOGRAMS,
UnitOfMass,
UnitOfTime,
UnitOfLength,
PERCENTAGE,
TIME_MINUTES,
TIME_YEARS,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -44,7 +44,7 @@ GARMIN_ENTITY_LIST = {
"netCalorieGoal": ["Net Calorie Goal", "kcal", "mdi:food", None, SensorStateClass.TOTAL, False],
"totalDistanceMeters": [
"Total Distance Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:walk",
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
@@ -69,7 +69,7 @@ GARMIN_ENTITY_LIST = {
"wellnessDescription": ["Wellness Description", "", "mdi:clock", None, SensorStateClass.TOTAL, False],
"wellnessDistanceMeters": [
"Wellness Distance Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:walk",
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
@@ -86,18 +86,18 @@ GARMIN_ENTITY_LIST = {
"wellnessKilocalories": ["Wellness KiloCalories", "kcal", "mdi:food", None, SensorStateClass.TOTAL, False],
"highlyActiveSeconds": [
"Highly Active Time",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:fire",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
False,
],
"activeSeconds": ["Active Time", TIME_MINUTES, "mdi:fire", None, SensorStateClass.TOTAL, True],
"sedentarySeconds": ["Sedentary Time", TIME_MINUTES, "mdi:seat", None, SensorStateClass.TOTAL, True],
"sleepingSeconds": ["Sleeping Time", TIME_MINUTES, "mdi:sleep", None, SensorStateClass.TOTAL, True],
"activeSeconds": ["Active Time", UnitOfTime.MINUTES, "mdi:fire", None, SensorStateClass.TOTAL, True],
"sedentarySeconds": ["Sedentary Time", UnitOfTime.MINUTES, "mdi:seat", None, SensorStateClass.TOTAL, True],
"sleepingSeconds": ["Sleeping Time", UnitOfTime.MINUTES, "mdi:sleep", None, SensorStateClass.TOTAL, True],
"measurableAwakeDuration": [
"Awake Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:sleep",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -105,7 +105,7 @@ GARMIN_ENTITY_LIST = {
],
"measurableAsleepDuration": [
"Sleep Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:sleep",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -113,7 +113,7 @@ GARMIN_ENTITY_LIST = {
],
"floorsAscendedInMeters": [
"Floors Ascended Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:stairs",
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
@@ -121,7 +121,7 @@ GARMIN_ENTITY_LIST = {
],
"floorsDescendedInMeters": [
"Floors Descended Mtr",
LENGTH_METERS,
UnitOfLength.METERS,
"mdi:stairs",
SensorDeviceClass.DISTANCE,
SensorStateClass.TOTAL,
@@ -137,11 +137,11 @@ GARMIN_ENTITY_LIST = {
SensorStateClass.TOTAL,
True,
],
"minHeartRate": ["Min Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
"maxHeartRate": ["Max Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
"restingHeartRate": ["Resting Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, True],
"minAvgHeartRate": ["Min Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, False],
"maxAvgHeartRate": ["Max Avg Heart Rate", "bpm", "mdi:heart-pulse", None, SensorStateClass.TOTAL, 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,
@@ -155,16 +155,16 @@ GARMIN_ENTITY_LIST = {
"bpm",
"mdi:heart-pulse",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"averageStressLevel": ["Avg Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.TOTAL, True],
"maxStressLevel": ["Max Stress Level", "lvl", "mdi:flash-alert", None, SensorStateClass.TOTAL, True],
"stressQualifier": ["Stress Qualifier", None, "mdi:flash-alert", None, SensorStateClass.TOTAL, False],
"stressDuration": ["Stress Duration", TIME_MINUTES, "mdi:flash-alert", None, SensorStateClass.TOTAL, False],
"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", None, SensorStateClass.TOTAL, False],
"restStressDuration": [
"Rest Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -172,7 +172,7 @@ GARMIN_ENTITY_LIST = {
],
"activityStressDuration": [
"Activity Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -180,7 +180,7 @@ GARMIN_ENTITY_LIST = {
],
"uncategorizedStressDuration": [
"Uncat. Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -188,7 +188,7 @@ GARMIN_ENTITY_LIST = {
],
"totalStressDuration": [
"Total Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -196,7 +196,7 @@ GARMIN_ENTITY_LIST = {
],
"lowStressDuration": [
"Low Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -204,7 +204,7 @@ GARMIN_ENTITY_LIST = {
],
"mediumStressDuration": [
"Medium Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -212,7 +212,7 @@ GARMIN_ENTITY_LIST = {
],
"highStressDuration": [
"High Stress Duration",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
@@ -223,7 +223,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"restStressPercentage": [
@@ -231,7 +231,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"activityStressPercentage": [
@@ -239,7 +239,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"uncategorizedStressPercentage": [
@@ -247,7 +247,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"lowStressPercentage": [
@@ -255,7 +255,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"mediumStressPercentage": [
@@ -263,7 +263,7 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"highStressPercentage": [
@@ -271,12 +271,12 @@ GARMIN_ENTITY_LIST = {
PERCENTAGE,
"mdi:flash-alert",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"moderateIntensityMinutes": [
"Moderate Intensity",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:flash-alert",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -284,7 +284,7 @@ GARMIN_ENTITY_LIST = {
],
"vigorousIntensityMinutes": [
"Vigorous Intensity",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:run-fast",
SensorDeviceClass.DURATION,
SensorStateClass.TOTAL,
@@ -292,7 +292,7 @@ GARMIN_ENTITY_LIST = {
],
"intensityMinutesGoal": [
"Intensity Goal",
TIME_MINUTES,
UnitOfTime.MINUTES,
"mdi:run-fast",
None,
SensorStateClass.TOTAL,
@@ -338,9 +338,9 @@ GARMIN_ENTITY_LIST = {
SensorStateClass.TOTAL,
True,
],
"averageSpo2": ["Average SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.TOTAL, True],
"lowestSpo2": ["Lowest SPO2", PERCENTAGE, "mdi:diabetes", None, SensorStateClass.TOTAL, True],
"latestSpo2": ["Latest SPO2", PERCENTAGE, "mdi:diabetes", 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],
"latestSpo2ReadingTimeLocal": [
"Latest SPO2 Time",
None,
@@ -362,7 +362,7 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"lowestRespirationValue": [
@@ -370,7 +370,7 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"latestRespirationValue": [
@@ -378,7 +378,7 @@ GARMIN_ENTITY_LIST = {
"brpm",
"mdi:progress-clock",
None,
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
False,
],
"latestRespirationTimeGMT": [
@@ -389,14 +389,55 @@ GARMIN_ENTITY_LIST = {
None,
False,
],
"weight": ["Weight", MASS_KILOGRAMS, "mdi:weight-kilogram", SensorDeviceClass.WEIGHT, SensorStateClass.TOTAL, False],
"bmi": ["BMI", "bmi", "mdi:food", None, SensorStateClass.TOTAL, False],
"bodyFat": ["Body Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.TOTAL, False],
"bodyWater": ["Body Water", PERCENTAGE, "mdi:water-percent", None, SensorStateClass.TOTAL, False],
"boneMass": ["Bone Mass", MASS_KILOGRAMS, "mdi:bone", SensorDeviceClass.WEIGHT, SensorStateClass.TOTAL, False],
"muscleMass": ["Muscle Mass", MASS_KILOGRAMS, "mdi:dumbbell", SensorDeviceClass.WEIGHT, SensorStateClass.TOTAL, False],
"physiqueRating": ["Physique Rating", None, "mdi:numeric", None, SensorStateClass.TOTAL, False],
"visceralFat": ["Visceral Fat", PERCENTAGE, "mdi:food", None, SensorStateClass.TOTAL, False],
"metabolicAge": ["Metabolic Age", TIME_YEARS, "mdi:calendar-heart", None, SensorStateClass.TOTAL, False],
"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", SensorStateClass.TOTAL, None, False],
"badges": ["Badges", None, "mdi:numeric", SensorStateClass.TOTAL, None, False],
"sleepScore": [
"Sleep Score",
None,
"mdi:sleep",
SensorStateClass.TOTAL,
SensorStateClass.MEASUREMENT,
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 SERVICE_SETTING(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):
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==0.1.50"],
"codeowners": ["@cyberjunky"],
"config_flow": true,
"dependencies": [],
"documentation": "https://github.com/cyberjunky/home-assistant-garmin_connect",
"iot_class": "cloud_polling",
"version": "0.2.11"
"issue_tracker": "https://github.com/cyberjunky/home-assistant-garmin_connect/issues",
"requirements": ["garminconnect>=0.2.24", "tzlocal"],
"version": "0.2.23"
}

View File

@@ -2,28 +2,40 @@
from __future__ import annotations
import logging
import voluptuous as vol
from numbers import Number
import datetime
import pytz
from tzlocal import get_localzone
from homeassistant.components.sensor import (
SensorEntity,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
UnitOfLength,
ATTR_ENTITY_ID,
CONF_ID,
)
from homeassistant.const import ATTR_ATTRIBUTION, CONF_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from .alarm_util import calculate_next_active_alarms
from .const import (
DATA_COORDINATOR,
DOMAIN as GARMIN_DOMAIN,
GARMIN_ENTITY_LIST,
GEAR,
GEAR_ICONS,
)
_LOGGER = logging.getLogger(__name__)
@@ -67,9 +79,72 @@ async def async_setup_entry(
enabled_by_default,
)
)
if "gear" in coordinator.data:
for gear_item in coordinator.data["gear"]:
entities.append(
GarminConnectGearSensor(
coordinator,
unique_id,
gear_item[GEAR.UUID],
gear_item["gearTypeName"],
gear_item["displayName"],
None,
True,
)
)
async_add_entities(entities)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
"set_active_gear", ENTITY_SERVICE_SCHEMA, coordinator.set_active_gear
)
platform.async_register_entity_service(
"add_body_composition", BODY_COMPOSITION_SERVICE_SCHEMA, coordinator.add_body_composition
)
platform.async_register_entity_service(
"add_blood_pressure", BLOOD_PRESSURE_SERVICE_SCHEMA, coordinator.add_blood_pressure
)
ENTITY_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Required("activity_type"): str,
vol.Required("setting"): str,
}
)
BODY_COMPOSITION_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Optional("timestamp"): str,
vol.Required("weight"): float,
vol.Optional("percent_fat"): float,
vol.Optional("percent_hydration"): float,
vol.Optional("visceral_fat_mass"): float,
vol.Optional("bone_mass"): float,
vol.Optional("muscle_mass"): float,
vol.Optional("basal_met"): float,
vol.Optional("active_met"): float,
vol.Optional("physique_rating"): float,
vol.Optional("metabolic_age"): float,
vol.Optional("visceral_fat_rating"): float,
vol.Optional("bmi"): float
}
)
BLOOD_PRESSURE_SERVICE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): str,
vol.Required("systolic"): int,
vol.Required("diastolic"): int,
vol.Required("pulse"): int,
vol.Optional("note"): str
}
)
class GarminConnectSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Garmin Connect Sensor."""
@@ -105,6 +180,16 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
@property
def native_value(self):
"""Return the state of the sensor."""
if self._type == "lastActivities":
return len(self.coordinator.data[self._type])
if self._type == "badges":
return len(self.coordinator.data[self._type])
if self._type == "hrvStatus":
return self.coordinator.data[self._type]["status"]
if not self.coordinator.data or not self.coordinator.data[self._type]:
return None
@@ -120,8 +205,7 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
if active_alarms:
date_time_obj = datetime.datetime.strptime(active_alarms[0], "%Y-%m-%dT%H:%M:%S")
tz = get_localzone()
timezone = pytz.timezone(tz.zone)
timezone_date_time_obj = timezone.localize(date_time_obj)
timezone_date_time_obj = date_time_obj.replace(tzinfo=tz)
return timezone_date_time_obj
else:
return None
@@ -131,11 +215,10 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
if self._device_class == SensorDeviceClass.TIMESTAMP:
date_time_obj = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
tz = get_localzone()
timezone = pytz.timezone(tz.zone)
timezone_date_time_obj = timezone.localize(date_time_obj)
timezone_date_time_obj = date_time_obj.replace(tzinfo=tz)
return timezone_date_time_obj
return round(value, 2)
return round(value, 2) if isinstance(value, Number) else value
@property
def extra_state_attributes(self):
@@ -146,11 +229,22 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
attributes = {
"last_synced": self.coordinator.data["lastSyncTimestampGMT"],
}
if self._type == "lastActivities":
attributes["last_Activities"] = self.coordinator.data[self._type]
if self._type == "badges":
attributes["badges"] = self.coordinator.data[self._type]
if self._type == "nextAlarm":
attributes["next_alarms"] = calculate_next_active_alarms(
self.coordinator.data[self._type]
)
if self._type == "hrvStatus":
attributes = {**attributes, **self.coordinator.data[self._type]}
del attributes["status"]
return attributes
@property
@@ -175,3 +269,134 @@ class GarminConnectSensor(CoordinatorEntity, SensorEntity):
and self.coordinator.data
and self._type in self.coordinator.data
)
class GarminConnectGearSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Garmin Connect Sensor."""
def __init__(
self,
coordinator,
unique_id,
uuid,
sensor_type,
name,
device_class: None,
enabled_default: bool = True,
):
"""Initialize a Garmin Connect sensor."""
super().__init__(coordinator)
self._unique_id = unique_id
self._type = sensor_type
self._uuid = uuid
self._device_class = device_class
self._enabled_default = enabled_default
self._attr_name = name
self._attr_device_class = self._device_class
self._attr_icon = GEAR_ICONS[sensor_type]
self._attr_native_unit_of_measurement = UnitOfLength.KILOMETERS
self._attr_unique_id = f"{self._unique_id}_{self._uuid}"
self._attr_state_class = SensorStateClass.TOTAL
self._attr_device_class = "garmin_gear"
@property
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["activity_types"]
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):
return list(
map(
lambda b: b["typeKey"],
filter(
lambda a: a["typeId"]
in map(lambda d: d["activityTypePk"], gear_defaults),
activity_types,
),
)
)
@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["gear_stats"]:
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["gear_defaults"],
)
)

View File

@@ -0,0 +1,149 @@
set_active_gear:
name: Set active gear for activity
description: Set active gear for activity.
fields:
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
entity_id:
name: entity
description: entity
required: true
selector:
entity:
integration: garmin_connect
device_class: garmin_gear
add_body_composition:
name: Adds updated body composition metrics
description: Adds updated body composition metrics.
fields:
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
entity_id:
name: entity
description: entity
required: true
selector:
entity:
integration: garmin_connect
device_class: weight
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: weight
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
notes:
required: false
name: Notes
description: Add a note to the measurement
example: 'Measured with Beurer BC54'

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

@@ -15,7 +15,7 @@
"password": "Heslo",
"username": "Užívateľské meno"
},
"description": "Zadajte svoje poverenia.",
"description": "Zadajte svoje poverovacie údaje.",
"title": "Garmin Connect"
}
}

View File

@@ -1,5 +1,6 @@
{
"name": "Garmin Connect",
"render_readme": true,
"domains": ["sensor"]
"homeassistant": "2024.11.0",
"hacs": "1.34.0"
}

19
mypy.ini Normal file
View File

@@ -0,0 +1,19 @@
[mypy]
python_version = 3.11
show_error_codes = true
follow_imports = silent
ignore_missing_imports = true
strict_equality = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
no_implicit_optional = true
warn_return_any = true
warn_unreachable = true

81
pylintrc Normal file
View File

@@ -0,0 +1,81 @@
[MASTER]
ignore=tests
# Use a conservative default here; 2 should speed up most setups and not hurt
# any too bad. Override on command line as appropriate.
jobs=2
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
useless-suppression,
# Specify a score threshold to be exceeded before program exits with error.
fail-under=10.0
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
# load-plugins=
# Pickle collected data for later comparisons.
persistent=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=ciso8601,
cv2
[BASIC]
good-names=i,j,k,ex,_,T,x,y,id
[MESSAGES CONTROL]
# Reasons disabled:
# format - handled by black
# duplicate-code - unavoidable
# cyclic-import - doesn't test if both import on load
# too-many-* - are not enforced for the sake of readability
# abstract-method - with intro of async there are always methods missing
# inconsistent-return-statements - doesn't handle raise
# wrong-import-order - isort guards this
disable=
format,
abstract-class-little-used,
abstract-method,
cyclic-import,
duplicate-code,
inconsistent-return-statements,
too-many-instance-attributes,
wrong-import-order,
too-few-public-methods
# enable useless-suppression temporarily every now and then to clean them up
enable=
useless-suppression,
use-symbolic-message-instead,
[REPORTS]
score=no
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
[FORMAT]
expected-line-ending-format=LF
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception

8
requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
colorlog==6.8.2
homeassistant==2024.1.0
pip>=24.1.1,<24.2
ruff==0.8.3
mypy==1.13.0
pre-commit==4.0.1
pylint==3.3.2
types-cachetools

3
requirements_lint.txt Normal file
View File

@@ -0,0 +1,3 @@
-r requirements.txt
pre-commit==4.0.1
vulture==2.14

20
scripts/develop Normal file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
# Create config dir if not present
if [[ ! -d "${PWD}/config" ]]; then
mkdir -p "${PWD}/config"
hass --config "${PWD}/config" --script ensure_config
fi
# Set the path to custom_components
## This let's us have the structure we want <root>/custom_components/integration_blueprint
## while at the same time have Home Assistant configuration inside <root>/config
## without resulting to symlinks.
export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components"
# Start Home Assistant
hass --config "${PWD}/config" --debug

View File

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

13
scripts/lint Normal file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
python3 -m pip install --requirement requirements_lint.txt
ruff check . --fix;
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 55 --ignore-names policy

7
scripts/setup Normal file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
python3 -m pip install --requirement requirements.txt

21
setup.cfg Normal file
View File

@@ -0,0 +1,21 @@
[flake8]
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
doctests = True
# To work with Black
max-line-length = 88
# E501: line too long
# W503: Line break occurred before a binary operator
# E203: Whitespace before ':'
# D202 No blank lines allowed after function docstring
# D107 Missing docstring in __init__
ignore =
E501,
W503,
E203,
D202,
D107
[isort]
# https://github.com/timothycrosley/isort
# https://github.com/timothycrosley/isort/wiki/isort-Settings
profile = black