pax_global_header00006660000000000000000000000064146511666040014523gustar00rootroot0000000000000052 comment=a01fd5170afc7b5a4ea408b3c79042637db52b12 mopeka-iot-ble-0.8.0/000077500000000000000000000000001465116660400143355ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/.all-contributorsrc000066400000000000000000000004551465116660400201720ustar00rootroot00000000000000{ "projectName": "mopeka-iot-ble", "projectOwner": "bluetooth-devices", "repoType": "github", "repoHost": "https://github.com", "files": ["README.md"], "imageSize": 80, "commit": true, "commitConvention": "angular", "contributors": [], "contributorsPerLine": 7, "skipCi": true } mopeka-iot-ble-0.8.0/.copier-answers.yml000066400000000000000000000010101465116660400200670ustar00rootroot00000000000000# Changes here will be overwritten by Copier _commit: 9312d8b _src_path: gh:browniebroke/pypackage-template add_me_as_contributor: false copyright_year: '2022' documentation: false email: bluetooth@koston.org full_name: J. Nick Koston github_username: bluetooth-devices initial_commit: true open_source_license: MIT package_name: mopeka_iot_ble project_name: Mopeka IOT BLE project_short_description: Mopeka IOT BLE Parser project_slug: mopeka-iot-ble run_poetry_install: true setup_github: true setup_pre_commit: true mopeka-iot-ble-0.8.0/.editorconfig000066400000000000000000000004441465116660400170140ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.bat] indent_style = tab end_of_line = crlf [LICENSE] insert_final_newline = false [Makefile] indent_style = tab mopeka-iot-ble-0.8.0/.flake8000066400000000000000000000000561465116660400155110ustar00rootroot00000000000000[flake8] exclude = docs max-line-length = 188 mopeka-iot-ble-0.8.0/.github/000077500000000000000000000000001465116660400156755ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/.github/FUNDING.yml000066400000000000000000000000361465116660400175110ustar00rootroot00000000000000github: ["bluetooth-devices"] mopeka-iot-ble-0.8.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001465116660400200605ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/.github/ISSUE_TEMPLATE/1-bug_report.md000066400000000000000000000004221465116660400227060ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve labels: bug --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Additional context** Add any other context about the problem here. mopeka-iot-ble-0.8.0/.github/ISSUE_TEMPLATE/2-feature-request.md000066400000000000000000000006721465116660400236670ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project labels: enhancement --- **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. **Additional context** Add any other context or screenshots about the feature request here. mopeka-iot-ble-0.8.0/.github/dependabot.yml000066400000000000000000000010151465116660400205220ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" mopeka-iot-ble-0.8.0/.github/labels.toml000066400000000000000000000035151465116660400200400ustar00rootroot00000000000000[breaking] color = "ffcc00" name = "breaking" description = "Breaking change." [bug] color = "d73a4a" name = "bug" description = "Something isn't working" [dependencies] color = "0366d6" name = "dependencies" description = "Pull requests that update a dependency file" [github_actions] color = "000000" name = "github_actions" description = "Update of github actions" [documentation] color = "1bc4a5" name = "documentation" description = "Improvements or additions to documentation" [duplicate] color = "cfd3d7" name = "duplicate" description = "This issue or pull request already exists" [enhancement] color = "a2eeef" name = "enhancement" description = "New feature or request" ["good first issue"] color = "7057ff" name = "good first issue" description = "Good for newcomers" ["help wanted"] color = "008672" name = "help wanted" description = "Extra attention is needed" [invalid] color = "e4e669" name = "invalid" description = "This doesn't seem right" [nochangelog] color = "555555" name = "nochangelog" description = "Exclude pull requests from changelog" [question] color = "d876e3" name = "question" description = "Further information is requested" [removed] color = "e99695" name = "removed" description = "Removed piece of functionalities." [tests] color = "bfd4f2" name = "tests" description = "CI, CD and testing related changes" [wontfix] color = "ffffff" name = "wontfix" description = "This will not be worked on" [discussion] color = "c2e0c6" name = "discussion" description = "Some discussion around the project" [hacktoberfest] color = "ffa663" name = "hacktoberfest" description = "Good issues for Hacktoberfest" [answered] color = "0ee2b6" name = "answered" description = "Automatically closes as answered after a delay" [waiting] color = "5f7972" name = "waiting" description = "Automatically closes if no answer after a delay" mopeka-iot-ble-0.8.0/.github/workflows/000077500000000000000000000000001465116660400177325ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/.github/workflows/ci.yml000066400000000000000000000040701465116660400210510ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: "3.9" - uses: pre-commit/action@v3.0.0 # Make sure commit messages follow the conventional commits convention: # https://www.conventionalcommits.org commitlint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v5.3.0 test: strategy: fail-fast: false matrix: python-version: - "3.9" - "3.10" - "3.11" - "3.12" os: - ubuntu-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - uses: snok/install-poetry@v1.3.3 - name: Install Dependencies run: poetry install shell: bash - name: Test with Pytest run: poetry run pytest --cov-report=xml shell: bash - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} release: runs-on: ubuntu-latest environment: release if: github.ref == 'refs/heads/main' needs: - test - lint - commitlint steps: - uses: actions/checkout@v3 with: fetch-depth: 0 # Run semantic release: # - Update CHANGELOG.md # - Update version in code # - Create git tag # - Create GitHub release # - Publish to PyPI - name: Python Semantic Release uses: python-semantic-release/python-semantic-release@v7.34.6 with: github_token: ${{ secrets.GITHUB_TOKEN }} pypi_token: ${{ secrets.PYPI_TOKEN }} mopeka-iot-ble-0.8.0/.github/workflows/hacktoberfest.yml000066400000000000000000000005341465116660400233030ustar00rootroot00000000000000name: Hacktoberfest on: schedule: # Run every day in October - cron: "0 0 * 10 *" # Run on the 1st of November to revert - cron: "0 13 1 11 *" jobs: hacktoberfest: runs-on: ubuntu-latest steps: - uses: browniebroke/hacktoberfest-labeler-action@v2.3.0 with: github_token: ${{ secrets.GH_PAT }} mopeka-iot-ble-0.8.0/.github/workflows/issue-manager.yml000066400000000000000000000013401465116660400232130ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.4.0 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { "message": "Automatically closing. To re-open, please provide the additional information requested." } } mopeka-iot-ble-0.8.0/.github/workflows/labels.yml000066400000000000000000000007741465116660400217270ustar00rootroot00000000000000name: Sync Github labels on: push: branches: - main paths: - ".github/**" jobs: labels: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install labels run: pip install labels - name: Sync config with Github run: labels -u ${{ github.repository_owner }} -t ${{ secrets.GITHUB_TOKEN }} sync -f .github/labels.toml mopeka-iot-ble-0.8.0/.gitignore000066400000000000000000000041101465116660400163210ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder {{package_name}} settings .spyderproject .spyproject # Rope {{package_name}} settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ mopeka-iot-ble-0.8.0/.gitpod.yml000066400000000000000000000003061465116660400164230ustar00rootroot00000000000000tasks: - command: | pip install poetry PIP_USER=false poetry install - command: | pip install pre-commit pre-commit install PIP_USER=false pre-commit install-hooks mopeka-iot-ble-0.8.0/.idea/000077500000000000000000000000001465116660400153155ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/.idea/mopeka-iot-ble.iml000066400000000000000000000005151465116660400206260ustar00rootroot00000000000000 mopeka-iot-ble-0.8.0/.idea/watcherTasks.xml000066400000000000000000000052531465116660400205070ustar00rootroot00000000000000 mopeka-iot-ble-0.8.0/.idea/workspace.xml000066400000000000000000000027361465116660400200450ustar00rootroot00000000000000 mopeka-iot-ble-0.8.0/.pre-commit-config.yaml000066400000000000000000000034521465116660400206220ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "CHANGELOG.md|.copier-answers.yml" default_stages: [commit] ci: autofix_commit_msg: "chore(pre-commit.ci): auto fixes" autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" repos: - repo: https://github.com/commitizen-tools/commitizen rev: v3.28.0 hooks: - id: commitizen stages: [commit-msg] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: debug-statements - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-toml - id: check-xml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-poetry/poetry rev: 1.8.0 hooks: - id: poetry-check - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 hooks: - id: prettier args: ["--tab-width", "2"] - repo: https://github.com/asottile/pyupgrade rev: v3.16.0 hooks: - id: pyupgrade args: [--py39-plus] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.5.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell - repo: https://github.com/PyCQA/flake8 rev: 7.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.11.0 hooks: - id: mypy additional_dependencies: [] - repo: https://github.com/PyCQA/bandit rev: 1.7.9 hooks: - id: bandit args: [-x, tests] mopeka-iot-ble-0.8.0/CHANGELOG.md000066400000000000000000000060261465116660400161520ustar00rootroot00000000000000# Changelog ## v0.8.0 (2024-07-27) ### Feature * Add support for other fluids and air ([#21](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/21)) ([`5c67178`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/5c671785665de3446622dc513932f271b6a176f0)) ## v0.7.0 (2024-02-03) ### Feature * Add TD40/TD200 models ([#15](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/15)) ([`80615f6`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/80615f65ac780c64ff2a02716e7e61b4b8213f8b)) ## v0.6.0 (2024-02-03) ### Feature * Add support for Lippert BottleCheck ([#14](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/14)) ([`95b10c2`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/95b10c299b082cc1b6047165e2ebaaab1647f071)) ## v0.5.0 (2023-11-16) ### Feature * Adding the new 2023 "Pro Check Universal" device (M1017020A) ([#9](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/9)) ([`0b81c71`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/0b81c71c1b44382ec163d5db49902aa56c06bf7e)) ## v0.4.1 (2023-02-23) ### Fix * Show readings of quality 1 ([#5](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/5)) ([`1e034e6`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/1e034e6c1884c7c8e1eefbe64022aaa11df68648)) ## v0.4.0 (2023-01-24) ### Feature * Express reading quality as a percentage ([#4](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/4)) ([`1f26c88`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/1f26c88181963710aa7d427333536fb79236cfe6)) ## v0.3.0 (2023-01-24) ### Feature * Add support for the check device ([#3](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/3)) ([`7637c02`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/7637c0237002e6325c6cc9c5d1ba45776b6760cc)) ## v0.2.1 (2023-01-24) ### Fix * Skip bad reads of the tank level ([#2](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/2)) ([`a6cb356`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/a6cb356a8aec85d178e1092dae3e274394d6b0d0)) ## v0.2.0 (2023-01-24) ### Feature * Add more exports ([#1](https://github.com/Bluetooth-Devices/mopeka-iot-ble/issues/1)) ([`fe006f9`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/fe006f910ffe366c3f04cad09b83525d0c6399e4)) ## v0.1.0 (2023-01-24) ### Feature * Add cover ([`2032ee0`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/2032ee0993990d789b7efb831592b6c3b9ad8fc3)) * Init repo with sample data ([`124fa30`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/124fa30e0077ad1de26a8b128712345e17640028)) * Init repo with sample data ([`6395c16`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/6395c1621ac0bee757d3ffe4c28c71d9bbb347dd)) * Init repo with sample data ([`9b8e45c`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/9b8e45cff936b5e0fc4bc054511aede521a75985)) * Init repo with sample data ([`731d5ad`](https://github.com/Bluetooth-Devices/mopeka-iot-ble/commit/731d5ad7ffb715f9f63804c8be760836230d795c)) mopeka-iot-ble-0.8.0/CONTRIBUTING.md000066400000000000000000000074461465116660400166010ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. You can contribute in many ways: ## Types of Contributions ### Report Bugs Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: - Your operating system name and version. - Any details about your local setup that might be helpful in troubleshooting. - Detailed steps to reproduce the bug. ### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. ### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. ### Write Documentation Mopeka IOT BLE could always use more documentation, whether as part of the official Mopeka IOT BLE docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: - Explain in detail how it would work. - Keep the scope as narrow as possible, to make it easier to implement. - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 ## Get Started! Ready to contribute? Here's how to set yourself up for local development. 1. Fork the repo on GitHub. 2. Clone your fork locally: ```shell $ git clone git@github.com:your_name_here/mopeka-iot-ble.git ``` 3. Install the project dependencies with [Poetry](https://python-poetry.org): ```shell $ poetry install ``` 4. Create a branch for local development: ```shell $ git checkout -b name-of-your-bugfix-or-feature ``` Now you can make your changes locally. 5. When you're done making changes, check that your changes pass our tests: ```shell $ poetry run pytest ``` 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: ```shell $ pre-commit run -a ``` Or better, install the hooks once and have them run automatically each time you commit: ```shell $ pre-commit install ``` 7. Commit your changes and push your branch to GitHub: ```shell $ git add . $ git commit -m "feat(something): your detailed description of your changes" $ git push origin name-of-your-bugfix-or-feature ``` Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): ```shell $ gh pr create --fill ``` ## Pull Request Guidelines We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 1. Include tests for feature or bug fixes. 2. Update the documentation for significant features. 3. Ensure tests are passing on CI. ## Tips To run a subset of tests: ```shell $ pytest tests ``` ## Making a new release The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. [gh-issues]: https://github.com/bluetooth-devices/mopeka-iot-ble/issues mopeka-iot-ble-0.8.0/LICENSE000066400000000000000000000020601465116660400153400ustar00rootroot00000000000000 MIT License Copyright (c) 2022 J. Nick Koston Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mopeka-iot-ble-0.8.0/README.md000066400000000000000000000064061465116660400156220ustar00rootroot00000000000000# Mopeka IOT BLE

CI Status Test coverage percentage

Poetry black pre-commit

PyPI Version Supported Python versions License

Mopeka IOT BLE Parser ## Installation Install this via pip (or your favourite package manager): `pip install mopeka-iot-ble` ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ## Credits This package was created with [Copier](https://copier.readthedocs.io/) and the [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) project template. mopeka-iot-ble-0.8.0/commitlint.config.js000066400000000000000000000003641465116660400203210ustar00rootroot00000000000000module.exports = { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [0, "always", Infinity], "body-max-line-length": [0, "always", Infinity], "footer-max-line-length": [0, "always", Infinity], }, }; mopeka-iot-ble-0.8.0/poetry.lock000066400000000000000000000355571465116660400165500ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "bluetooth-data-tools" version = "0.3.1" description = "Tools for converting bluetooth data and packets" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "bluetooth_data_tools-0.3.1-py3-none-any.whl", hash = "sha256:b7ebacecec2d30d69dd83077f7dce5da2eb713f040ddb7d5573a2c60078c313b"}, {file = "bluetooth_data_tools-0.3.1.tar.gz", hash = "sha256:247e896ec963bf2204c0e77bc5ec09dafaa4e065afffe38553b9d4d526b71617"}, ] [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "bluetooth-sensor-state-data" version = "1.7.1" description = "Models for storing and converting Bluetooth Sensor State Data" optional = false python-versions = "<4.0,>=3.9" files = [ {file = "bluetooth_sensor_state_data-1.7.1-py3-none-any.whl", hash = "sha256:65df4b1cd1777a5d2aed9e3e16cf00488f62838d745ae095ebc5ce7f7d7966cd"}, {file = "bluetooth_sensor_state_data-1.7.1.tar.gz", hash = "sha256:6e89909e72b596917b859673cf37eb37500e3cfdca94f08a121fca86bb2e79a4"}, ] [package.dependencies] home-assistant-bluetooth = ">=1.3.0" sensor-state-data = ">=2.0" [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.6.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "home-assistant-bluetooth" version = "1.10.4" description = "Home Assistant Bluetooth Models and Helpers" optional = false python-versions = ">=3.9,<4.0" files = [ {file = "home_assistant_bluetooth-1.10.4-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:7c3434bdec5dcfe733d3e7c56d4a24418fcd03718dc2e7707c9133d1e48145a8"}, {file = "home_assistant_bluetooth-1.10.4.tar.gz", hash = "sha256:21216b6be9d028bc232b9188ac4dce773798c6b4e47482cc3524bfc5f82515e3"}, ] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "packaging" version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" version = "8.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" version = "5.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.8" files = [ {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, ] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "sensor-state-data" version = "2.18.1" description = "Models for storing and converting Sensor Data state" optional = false python-versions = "<4.0,>=3.9" files = [ {file = "sensor_state_data-2.18.1-py3-none-any.whl", hash = "sha256:45a223acf5d4aefde45c028fa810c7925db6448984097aa1b500fe4f206d113f"}, {file = "sensor_state_data-2.18.1.tar.gz", hash = "sha256:25f17ed98748ae006ddab82d5013cf30301daaf23526d1992f99c4dc0beb49c3"}, ] [package.extras] docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [metadata] lock-version = "2.0" python-versions = "^3.9" content-hash = "46eab597e4eb4a3946ce919dc6f0f82b408b6e587ce1bf4777d0cb738aa9ec7a" mopeka-iot-ble-0.8.0/pyproject.toml000066400000000000000000000040251465116660400172520ustar00rootroot00000000000000[tool.poetry] name = "mopeka-iot-ble" version = "0.8.0" description = "Mopeka IOT BLE Parser" authors = ["J. Nick Koston "] license = "MIT" readme = "README.md" repository = "https://github.com/bluetooth-devices/mopeka-iot-ble" classifiers = [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries", ] packages = [ { include = "mopeka_iot_ble", from = "src" }, ] [tool.poetry.urls] "Bug Tracker" = "https://github.com/bluetooth-devices/mopeka-iot-ble/issues" "Changelog" = "https://github.com/bluetooth-devices/mopeka-iot-ble/blob/main/CHANGELOG.md" [tool.poetry.dependencies] python = "^3.9" home-assistant-bluetooth = ">=1.3.0" sensor-state-data = ">=2.2.0" bluetooth-sensor-state-data = ">=1.5.0" bluetooth-data-tools = ">=0.1.2" [tool.poetry.group.dev.dependencies] pytest = ">=7,<9" pytest-cov = ">=3,<6" [tool.semantic_release] branch = "main" version_toml = "pyproject.toml:tool.poetry.version" version_variable = "src/mopeka_iot_ble/__init__.py:__version__" build_command = "pip install poetry && poetry build" [tool.pytest.ini_options] addopts = "-v -Wdefault --cov=mopeka_iot_ble --cov-report=term-missing:skip-covered" pythonpath = ["src"] [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", "if TYPE_CHECKING", "raise NotImplementedError", 'if __name__ == "__main__":', ] [tool.isort] profile = "black" known_first_party = ["mopeka_iot_ble", "tests"] [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true exclude = [ 'setup.py', ] [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" mopeka-iot-ble-0.8.0/renovate.json000066400000000000000000000001011465116660400170430ustar00rootroot00000000000000{ "extends": ["github>browniebroke/renovate-configs:python"] } mopeka-iot-ble-0.8.0/setup.py000066400000000000000000000003641465116660400160520ustar00rootroot00000000000000#!/usr/bin/env python # This is a shim to allow GitHub to detect the package, build is done with poetry # Taken from https://github.com/Textualize/rich import setuptools if __name__ == "__main__": setuptools.setup(name="mopeka-iot-ble") mopeka-iot-ble-0.8.0/src/000077500000000000000000000000001465116660400151245ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/src/mopeka_iot_ble/000077500000000000000000000000001465116660400200755ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/src/mopeka_iot_ble/__init__.py000066400000000000000000000016151465116660400222110ustar00rootroot00000000000000"""Parser for Mopeka IOT BLE advertisements. Thanks to https://github.com/spbrogan/mopeka_pro_check for help decoding the advertisements. MIT License applies. """ from __future__ import annotations from sensor_state_data import ( BinarySensorDescription, BinarySensorDeviceClass, BinarySensorValue, DeviceClass, DeviceKey, SensorDescription, SensorDeviceClass, SensorDeviceInfo, SensorUpdate, SensorValue, Units, ) from .parser import MopekaIOTBluetoothDeviceData from .models import MediumType __version__ = "0.8.0" __all__ = [ "MopekaIOTBluetoothDeviceData", "BinarySensorDeviceClass", "BinarySensorDescription", "BinarySensorValue", "MediumType", "SensorDescription", "SensorDeviceInfo", "DeviceClass", "DeviceKey", "SensorDeviceClass", "SensorUpdate", "SensorDeviceInfo", "SensorValue", "Units", ] mopeka-iot-ble-0.8.0/src/mopeka_iot_ble/models.py000066400000000000000000000011161465116660400217310ustar00rootroot00000000000000"""Types for Mopeka IOT BLE advertisements. Thanks to https://github.com/spbrogan/mopeka_pro_check for help decoding the advertisements. MIT License applies. """ from enum import Enum class MediumType(Enum): """Enumeration of medium types for tank level measurements.""" PROPANE = "propane" AIR = "air" FRESH_WATER = "fresh_water" WASTE_WATER = "waste_water" LIVE_WELL = "live_well" BLACK_WATER = "black_water" RAW_WATER = "raw_water" GASOLINE = "gasoline" DIESEL = "diesel" LNG = "lng" OIL = "oil" HYDRAULIC_OIL = "hydraulic_oil" mopeka-iot-ble-0.8.0/src/mopeka_iot_ble/parser.py000066400000000000000000000152001465116660400217410ustar00rootroot00000000000000"""Parser for Gmopeka_iot BLE advertisements. Thanks to https://github.com/spbrogan/mopeka_pro_check for help decoding the advertisements. MIT License applies. """ from __future__ import annotations import logging from dataclasses import dataclass from bluetooth_data_tools import short_address from bluetooth_sensor_state_data import BluetoothData from home_assistant_bluetooth import BluetoothServiceInfo from sensor_state_data import ( BinarySensorDeviceClass, SensorDeviceClass, SensorLibrary, Units, ) from .models import MediumType _LOGGER = logging.getLogger(__name__) # converting sensor value to height MOPEKA_TANK_LEVEL_COEFFICIENTS = { MediumType.PROPANE: (0.573045, -0.002822, -0.00000535), MediumType.AIR: (0.153096, 0.000327, -0.000000294), MediumType.FRESH_WATER: (0.600592, 0.003124, -0.00001368), MediumType.WASTE_WATER: (0.600592, 0.003124, -0.00001368), MediumType.LIVE_WELL: (0.600592, 0.003124, -0.00001368), MediumType.BLACK_WATER: (0.600592, 0.003124, -0.00001368), MediumType.RAW_WATER: (0.600592, 0.003124, -0.00001368), MediumType.GASOLINE: (0.7373417462, -0.001978229885, 0.00000202162), MediumType.DIESEL: (0.7373417462, -0.001978229885, 0.00000202162), MediumType.LNG: (0.7373417462, -0.001978229885, 0.00000202162), MediumType.OIL: (0.7373417462, -0.001978229885, 0.00000202162), MediumType.HYDRAULIC_OIL: (0.7373417462, -0.001978229885, 0.00000202162), } MOPEKA_MANUFACTURER = 89 MOKPEKA_PRO_SERVICE_UUID = "0000fee5-0000-1000-8000-00805f9b34fb" @dataclass class MopekaDevice: model: str name: str adv_length: int DEVICE_TYPES = { 0x3: MopekaDevice("M1017", "Pro Check", 10), 0x4: MopekaDevice("Pro-200", "Pro-200", 10), 0x5: MopekaDevice("Pro H20", "Pro Check H2O", 10), 0x6: MopekaDevice("M1017", "Lippert BottleCheck", 10), 0x8: MopekaDevice("M1015", "Pro Plus", 10), 0x9: MopekaDevice("M1015", "Pro Plus with Cellular", 10), 0xA: MopekaDevice("TD40/TD200", "TD40/TD200", 10), 0xB: MopekaDevice("TD40/TD200", "TD40/TD200 with Cellular", 10), 0xC: MopekaDevice("M1017", "Pro Check Universal", 10), } def hex(data: bytes) -> str: """Return a string object containing two hexadecimal digits for each byte in the instance.""" return "b'{}'".format("".join(f"\\x{b:02x}" for b in data)) # noqa: E231 def battery_to_voltage(battery: int) -> float: """Convert battery value to voltage""" return battery / 32.0 def battery_to_percentage(battery: int) -> float: """Convert battery value to percentage.""" return round(max(0, min(100, (((battery / 32.0) - 2.2) / 0.65) * 100)), 1) def temp_to_celsius(temp: int) -> int: """Convert temperature value to celsius.""" return temp - 40 def tank_level_to_mm(tank_level: int) -> int: """Convert tank level value to mm.""" return tank_level * 10 def tank_level_and_temp_to_mm( tank_level: int, temp: int, medium: MediumType = MediumType.PROPANE ) -> int: """Get the tank level in mm for a given fluid type.""" coefs = MOPEKA_TANK_LEVEL_COEFFICIENTS[medium] return int(tank_level * (coefs[0] + (coefs[1] * temp) + (coefs[2] * (temp**2)))) class MopekaIOTBluetoothDeviceData(BluetoothData): """Data for Mopeka IOT BLE sensors.""" def __init__(self, medium_type: MediumType = MediumType.PROPANE) -> None: super().__init__() self._medium_type = medium_type def _start_update(self, service_info: BluetoothServiceInfo) -> None: """Update from BLE advertisement data.""" _LOGGER.debug( "Parsing Mopeka IOT BLE advertisement data: %s, MediumType is: %s", service_info, self._medium_type, ) manufacturer_data = service_info.manufacturer_data service_uuids = service_info.service_uuids address = service_info.address if ( MOPEKA_MANUFACTURER not in manufacturer_data or MOKPEKA_PRO_SERVICE_UUID not in service_uuids ): _LOGGER.debug("Not a Mopeka IOT BLE advertisement: %s", service_info) return data = manufacturer_data[MOPEKA_MANUFACTURER] model_num = data[0] if not (device_type := DEVICE_TYPES.get(model_num)): _LOGGER.debug("Unsupported Mopeka IOT BLE advertisement: %s", service_info) return adv_length = device_type.adv_length if len(data) != adv_length: return self.set_device_manufacturer("Mopeka IOT") self.set_device_type(device_type.model) self.set_device_name(f"{device_type.name} {short_address(address)}") battery = data[1] battery_voltage = battery_to_voltage(battery) battery_percentage = battery_to_percentage(battery) button_pressed = bool(data[2] & 0x80 > 0) temp = data[2] & 0x7F temp_celsius = temp_to_celsius(temp) tank_level = ((int(data[4]) << 8) + data[3]) & 0x3FFF tank_level_mm = tank_level_and_temp_to_mm(tank_level, temp, self._medium_type) reading_quality = data[4] >> 6 accelerometer_x = data[8] accelerometer_y = data[9] self.update_predefined_sensor(SensorLibrary.TEMPERATURE__CELSIUS, temp_celsius) self.update_predefined_sensor( SensorLibrary.BATTERY__PERCENTAGE, battery_percentage ) self.update_predefined_sensor( SensorLibrary.VOLTAGE__ELECTRIC_POTENTIAL_VOLT, battery_voltage, name="Battery Voltage", key="battery_voltage", ) self.update_predefined_binary_sensor( BinarySensorDeviceClass.OCCUPANCY, button_pressed, key="button_pressed", name="Button pressed", ) self.update_sensor( "tank_level", Units.LENGTH_MILLIMETERS, tank_level_mm if reading_quality >= 1 else None, SensorDeviceClass.DISTANCE, "Tank Level", ) self.update_sensor( "accelerometer_x", None, accelerometer_x, None, "Position X", ) self.update_sensor( "accelerometer_y", None, accelerometer_y, None, "Position Y", ) self.update_sensor( "reading_quality_raw", None, reading_quality, None, "Reading quality raw", ) self.update_sensor( "reading_quality", Units.PERCENTAGE, round(reading_quality / 3 * 100), None, "Reading quality", ) # Reading stars = (3-reading_quality) * "★" + (reading_quality * "⭐") mopeka-iot-ble-0.8.0/src/mopeka_iot_ble/py.typed000066400000000000000000000000001465116660400215620ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/tests/000077500000000000000000000000001465116660400154775ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/tests/__init__.py000066400000000000000000000000001465116660400175760ustar00rootroot00000000000000mopeka-iot-ble-0.8.0/tests/test_parser.py000066400000000000000000001760471465116660400204230ustar00rootroot00000000000000from bluetooth_sensor_state_data import BluetoothServiceInfo, SensorUpdate from sensor_state_data import ( BinarySensorDescription, BinarySensorDeviceClass, BinarySensorValue, DeviceKey, SensorDescription, SensorDeviceClass, SensorDeviceInfo, SensorValue, Units, ) import pytest from mopeka_iot_ble import MediumType # Consider renaming the hex method to avoid the override complaint from mopeka_iot_ble.parser import ( MopekaIOTBluetoothDeviceData, battery_to_percentage, battery_to_voltage, hex, tank_level_and_temp_to_mm, tank_level_to_mm, temp_to_celsius, ) PRO_SERVICE_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x08rF\x000\xe0\xf5\t\xf0\xd8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) PRO_SERVICE_LOW_QUALITY_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x08rF\x00@\xe0\xf5\t\xf0\xd8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) TDR40_AIR_LOW_QUALITY_INFO_2 = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", rssi=-49, manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) PRO_SERVICE_GOOD_QUALITY_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x08rF\x00\xc0\xe0\xf5\t\xf0\xd8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) PRO_INSTALLED_SERVICE_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x08pC\xb6\xc3\xe0\xf5\t\xfa\xe3"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) LIPPERT_SERVICE_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x06pC\xb6\xc3\xe0\xf5\t\xfa\xe3"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) CHECK_INSTALLED_SERVICE_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x03pC\xb6\xc3\xe0\xf5\t\xfa\xe3"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) CHECK_UNIVERSAL_INSTALLED_SERVICE_INFO = BluetoothServiceInfo( name="", address="C9:F3:32:E0:F5:09", rssi=-63, manufacturer_data={89: b"\x0cpC\xb6\xc3\xe0\xf5\t\xfa\xe3"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) TDR40_AIR_BAD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", rssi=-60, manufacturer_data={89: b"\ns@NMju\x10\x7f\x80"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) TDR40_AIR_LOW_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", rssi=-44, manufacturer_data={89: b"\x0c`8<\x83*\xea\x8c1\xf8"}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) TDR40_AIR_GOOD_QUALITY_INFO = BluetoothServiceInfo( name="", address="DA:D8:AC:6A:75:10", rssi=-50, manufacturer_data={89: b"\nq@}\xd0ju\x10\x80 "}, service_uuids=["0000fee5-0000-1000-8000-00805f9b34fb"], service_data={}, source="local", ) def test_hex(): assert ( hex(b"\x08rF\x000\xe0\xf5\t\xf0\xd8") == "b'\\x08\\x72\\x46\\x00\\x30\\xe0\\xf5\\x09\\xf0\\xd8'" ) def test_can_create(): MopekaIOTBluetoothDeviceData() def test_pro_bad_quality(): parser = MopekaIOTBluetoothDeviceData() service_info = PRO_SERVICE_BAD_QUALITY_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Plus F509", model="M1015", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5625, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=None, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=30, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=0, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=0, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=216, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=240, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_pro_low_quality(): parser = MopekaIOTBluetoothDeviceData() service_info = PRO_SERVICE_LOW_QUALITY_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Plus F509", model="M1015", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5625, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=0, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=30, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=33, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=1, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=216, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=240, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_pro_good_quality(): parser = MopekaIOTBluetoothDeviceData() service_info = PRO_SERVICE_GOOD_QUALITY_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Plus F509", model="M1015", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5625, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=0, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=30, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=216, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=240, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_pro_installed(): parser = MopekaIOTBluetoothDeviceData() service_info = PRO_INSTALLED_SERVICE_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Plus F509", model="M1015", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=341, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=27, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=227, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=250, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_check_universal_installed(): parser = MopekaIOTBluetoothDeviceData() service_info = CHECK_UNIVERSAL_INSTALLED_SERVICE_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Check Universal F509", model="M1017", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=341, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=27, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=227, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=250, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_check_installed(): parser = MopekaIOTBluetoothDeviceData() service_info = CHECK_INSTALLED_SERVICE_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Check F509", model="M1017", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=341, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=27, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=227, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=250, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_lippert(): parser = MopekaIOTBluetoothDeviceData() service_info = LIPPERT_SERVICE_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Lippert BottleCheck F509", model="M1017", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery " "Voltage", native_value=3.5, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank " "Level", native_value=341, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=27, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal " "Strength", native_value=-63, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading " "quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading " "quality " "raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position " "Y", native_value=227, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position " "X", native_value=250, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) @pytest.mark.parametrize( "battery_raw, expected_voltage", [ (89, 2.78125), (90, 2.8125), (91, 2.84375), (92, 2.875), (93, 2.90625), (94, 2.9375), (95, 2.96875), (96, 3.0), (97, 3.03125), (98, 3.0625), (99, 3.09375), (100, 3.125), ], ) def test_battery_to_voltage(battery_raw: int, expected_voltage: float) -> None: voltage = battery_to_voltage(battery_raw) assert voltage == expected_voltage def test_battery_to_percentage(): battery_raw = 89 # example battery raw value percentage = battery_to_percentage(battery_raw) assert percentage == 89.4 def test_temp_to_celsius(): temperature_raw = 77 # example temperature raw value celsius = temp_to_celsius(temperature_raw) assert celsius == 37 def test_tank_level_to_mm(): tank_level_raw = 3145 # example tank level raw value mm = tank_level_to_mm(tank_level_raw) assert mm == 31450 # tank_level_raw * 10 def test_tank_level_and_temp_to_mm(): temperature_raw = 77 # example temperature raw value tank_level_raw = 3145 # example tank level raw value tank_level_mm = tank_level_and_temp_to_mm( tank_level_raw, temperature_raw, MediumType.AIR ) expected_mm = int( tank_level_raw * ( 0.153096 # coefs[0] for MediumType.AIR + (0.000327 * temperature_raw) # coefs[1] * temp + (-0.000000294 * (temperature_raw**2)) # coefs[2] * (temp ** 2) ) ) assert tank_level_mm == expected_mm def test_parser_with_sample_data(): medium_type = MediumType.AIR battery_raw = 89 # example battery raw value tank_level_raw = 3145 # example tank level raw value temperature_raw = 77 # example temperature raw value assert MopekaIOTBluetoothDeviceData(medium_type) assert battery_to_voltage(battery_raw) == 2.78125 assert battery_to_percentage(battery_raw) == 89.4 assert temp_to_celsius(temperature_raw) == 37 assert tank_level_and_temp_to_mm( tank_level_raw, temperature_raw, medium_type ) == int( tank_level_raw * ( 0.153096 # coefs[0] for MediumType.AIR + (0.000327 * temperature_raw) # coefs[1] * temp + (-0.000000294 * (temperature_raw**2)) # coefs[2] * (temp ** 2) ) ) # Test entire parser chain def test_tdr40_air_bad_quality(): parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) service_info = TDR40_AIR_BAD_QUALITY_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="TD40/TD200 7510", # Corrected name model="TD40/TD200", # Corrected model manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery Voltage", native_value=3.59375, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank Level", native_value=588, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=24, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal Strength", native_value=-60, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading quality", native_value=33, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading quality raw", native_value=1, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position Y", native_value=128, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position X", native_value=127, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_tdr40_air_low_quality(): parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) result = parser.update(TDR40_AIR_LOW_QUALITY_INFO_2) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="Pro Check Universal 7510", # Updated name model="M1017", # Updated model manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery Voltage", native_value=3.0, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank Level", native_value=141, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=16, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal Strength", native_value=-49, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading quality", native_value=67, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading quality raw", native_value=2, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position Y", native_value=248, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position X", native_value=49, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, ) def test_tdr40_air_good_quality(): parser = MopekaIOTBluetoothDeviceData(MediumType.AIR) service_info = TDR40_AIR_GOOD_QUALITY_INFO result = parser.update(service_info) assert result == SensorUpdate( title=None, devices={ None: SensorDeviceInfo( name="TD40/TD200 7510", model="TD40/TD200", manufacturer="Mopeka IOT", sw_version=None, hw_version=None, ) }, entity_descriptions={ DeviceKey(key="battery_voltage", device_id=None): SensorDescription( device_key=DeviceKey(key="battery_voltage", device_id=None), device_class=SensorDeviceClass.VOLTAGE, native_unit_of_measurement=Units.ELECTRIC_POTENTIAL_VOLT, ), DeviceKey(key="tank_level", device_id=None): SensorDescription( device_key=DeviceKey(key="tank_level", device_id=None), device_class=SensorDeviceClass.DISTANCE, native_unit_of_measurement=Units.LENGTH_MILLIMETERS, ), DeviceKey(key="temperature", device_id=None): SensorDescription( device_key=DeviceKey(key="temperature", device_id=None), device_class=SensorDeviceClass.TEMPERATURE, native_unit_of_measurement=Units.TEMP_CELSIUS, ), DeviceKey(key="battery", device_id=None): SensorDescription( device_key=DeviceKey(key="battery", device_id=None), device_class=SensorDeviceClass.BATTERY, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="signal_strength", device_id=None): SensorDescription( device_key=DeviceKey(key="signal_strength", device_id=None), device_class=SensorDeviceClass.SIGNAL_STRENGTH, native_unit_of_measurement=Units.SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ), DeviceKey(key="reading_quality", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality", device_id=None), device_class=None, native_unit_of_measurement=Units.PERCENTAGE, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorDescription( device_key=DeviceKey(key="reading_quality_raw", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_y", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_y", device_id=None), device_class=None, native_unit_of_measurement=None, ), DeviceKey(key="accelerometer_x", device_id=None): SensorDescription( device_key=DeviceKey(key="accelerometer_x", device_id=None), device_class=None, native_unit_of_measurement=None, ), }, entity_values={ DeviceKey(key="battery_voltage", device_id=None): SensorValue( device_key=DeviceKey(key="battery_voltage", device_id=None), name="Battery Voltage", native_value=3.53125, ), DeviceKey(key="tank_level", device_id=None): SensorValue( device_key=DeviceKey(key="tank_level", device_id=None), name="Tank Level", native_value=729, ), DeviceKey(key="temperature", device_id=None): SensorValue( device_key=DeviceKey(key="temperature", device_id=None), name="Temperature", native_value=24, ), DeviceKey(key="battery", device_id=None): SensorValue( device_key=DeviceKey(key="battery", device_id=None), name="Battery", native_value=100, ), DeviceKey(key="signal_strength", device_id=None): SensorValue( device_key=DeviceKey(key="signal_strength", device_id=None), name="Signal Strength", native_value=-50, ), DeviceKey(key="reading_quality", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality", device_id=None), name="Reading quality", native_value=100, ), DeviceKey(key="reading_quality_raw", device_id=None): SensorValue( device_key=DeviceKey(key="reading_quality_raw", device_id=None), name="Reading quality raw", native_value=3, ), DeviceKey(key="accelerometer_y", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_y", device_id=None), name="Position Y", native_value=32, ), DeviceKey(key="accelerometer_x", device_id=None): SensorValue( device_key=DeviceKey(key="accelerometer_x", device_id=None), name="Position X", native_value=128, ), }, binary_entity_descriptions={ DeviceKey(key="button_pressed", device_id=None): BinarySensorDescription( device_key=DeviceKey(key="button_pressed", device_id=None), device_class=BinarySensorDeviceClass.OCCUPANCY, ) }, binary_entity_values={ DeviceKey(key="button_pressed", device_id=None): BinarySensorValue( device_key=DeviceKey(key="button_pressed", device_id=None), name="Button pressed", native_value=False, ) }, events={}, )