diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..4e52157 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,129 @@ +name: Publish + +on: + workflow_dispatch: + release: + types: + - published + +jobs: + version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set_output.outputs.version }} + upload_url: ${{ steps.set_output.outputs.upload_url }} + env: + VERSION: "" + UPLOAD_URL: "" + steps: + - name: Get version and upload url from release + if: github.event_name == 'release' + run: | + echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_ENV + echo "UPLOAD_URL=${{ github.event.release.upload_url }}" >> $GITHUB_ENV + + - name: Get release from API + if: github.event_name == 'workflow_dispatch' + id: release_api + uses: octokit/request-action@v2.x + with: + route: GET /repos/${{ github.repository }}/releases/latest + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + + - name: Parse API response + if: github.event_name == 'workflow_dispatch' + run: | + echo "VERSION=${{ fromJson(steps.release_api.outputs.data).tag_name }}" >> $GITHUB_ENV + echo "UPLOAD_URL=${{ fromJson(steps.release_api.outputs.data).upload_url }}" >> $GITHUB_ENV + + - name: Log version and upload URL + run: | + echo "Version: $VERSION" + echo "Upload URL: $UPLOAD_URL" + + - name: Fail if no version or no upload URL + run: | + if [[ -z "$VERSION" || -z "$UPLOAD_URL" ]]; then + echo "Missing version or upload URL" + exit 1 + fi + + - name: Set outputs + id: set_output + run: | + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT + + package: + needs: version + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Load cached Poetry installation + id: cached-poetry + uses: actions/cache@v3 + with: + path: ~/.local + key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install Poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Load cached Poetry venv + id: cached-poetry-venv + uses: actions/cache@v3 + with: + path: .venv + key: poetry-venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-venv.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + + - name: Install package + run: poetry install --no-interaction + + - name: Set package version + run: | + poetry version ${{ needs.version.outputs.version }} + + - name: Build package + run: | + poetry build + + - name: Upload package to pyPI + run: | + poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} + poetry publish + + - name: Upload package to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + with: + upload_url: ${{ needs.version.outputs.upload_url }} + asset_path: ./dist/eq3btsmart-${{ needs.version.outputs.version }}.tar.gz + asset_name: eq3btsmart-${{ needs.version.outputs.version }}.tar.gz + asset_content_type: application/gzip + + - name: Upload wheel to release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + with: + upload_url: ${{ needs.version.outputs.upload_url }} + asset_path: ./dist/eq3btsmart-${{ needs.version.outputs.version }}-py3-none-any.whl + asset_name: eq3btsmart-${{ needs.version.outputs.version }}-py3-none-any.whl + asset_content_type: application/zip diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..75d2e42 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,113 @@ +name: Code Quality + +on: + workflow_dispatch: + pull_request: + types: + - opened + - synchronize + - reopened + +jobs: + cache: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Load cached Poetry installation + id: cached-poetry + uses: actions/cache@v3 + with: + path: ~/.local + key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install Poetry + if: steps.cached-poetry.outputs.cache-hit != 'true' + uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + + - name: Load cached Poetry venv + id: cached-poetry-venv + uses: actions/cache@v3 + with: + path: .venv + key: poetry-venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install dependencies + if: steps.cached-poetry-venv.outputs.cache-hit != 'true' + run: poetry install --no-interaction --no-root + + - name: Install package + run: poetry install --no-interaction + + ruff: + needs: cache + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Load cached Poetry installation + uses: actions/cache@v3 + with: + path: ~/.local + key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Load cached Poetry venv + uses: actions/cache@v3 + with: + path: ~/.venv + key: poetry-venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install package + run: | + poetry install --no-interaction + + - name: Run ruff + run: | + poetry run ruff eq3btsmart custom_components/eq3btsmart + + mypy: + needs: cache + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Load cached Poetry installation + uses: actions/cache@v3 + with: + path: ~/.local + key: poetry-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Load cached Poetry venv + uses: actions/cache@v3 + with: + path: ~/.venv + key: poetry-venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }} + + - name: Install package + run: | + poetry install --no-interaction + + - name: Run mypy + run: | + poetry run mypy eq3btsmart custom_components/eq3btsmart diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cadae0c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + release: + runs-on: ubuntu-latest + concurrency: release + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v4 + env: + GITHUB_TOKEN: ${{ secrets.PAT }} diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..4738bca --- /dev/null +++ b/.releaserc @@ -0,0 +1,12 @@ +{ + "repository_url": "https://github.com/dbuezas/eq3btsmart.git", + "branches": [ + "master" + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/github" + ], + "tagFormat": "${version}" +} diff --git a/eq3btsmart/bleakconnection.py b/eq3btsmart/bleakconnection.py index e1aca0d..06b9e2a 100644 --- a/eq3btsmart/bleakconnection.py +++ b/eq3btsmart/bleakconnection.py @@ -128,7 +128,7 @@ async def async_get_connection(self) -> BleakClient: self._on_connection_event() - if self._conn.is_connected: + if self._conn is not None and self._conn.is_connected: _LOGGER.debug("[%s] Connected", self._name) else: raise BackendException("Can't connect") diff --git a/poetry.lock b/poetry.lock index 78e00a5..4b5ab08 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "aiofiles" +version = "23.2.1" +description = "File support for asyncio." +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, + {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, +] + [[package]] name = "aiohttp" version = "3.9.1" @@ -286,6 +297,22 @@ typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""} "winrt-Windows.Foundation.Collections" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} "winrt-Windows.Storage.Streams" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} +[[package]] +name = "bleak-retry-connector" +version = "3.4.0" +description = "A connector for Bleak Clients that handles transient connection failures" +optional = false +python-versions = ">=3.10,<3.13" +files = [ + {file = "bleak_retry_connector-3.4.0-py3-none-any.whl", hash = "sha256:5f6c26efa03d3ae94c55045b99079fc27f1a9480cf4c8f6d6f027a36bf15cf66"}, + {file = "bleak_retry_connector-3.4.0.tar.gz", hash = "sha256:71f30928180b74f0381e0752f681d18d8de888faa9c81c78cd17123718909ea0"}, +] + +[package.dependencies] +bleak = ">=0.21.0" +bluetooth-adapters = {version = ">=0.15.2", markers = "platform_system == \"Linux\""} +dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""} + [[package]] name = "bleak-winrt" version = "1.2.0" @@ -306,6 +333,27 @@ files = [ {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, ] +[[package]] +name = "bluetooth-adapters" +version = "0.17.0" +description = "Tools to enumerate and find Bluetooth Adapters" +optional = false +python-versions = ">=3.9,<3.13" +files = [ + {file = "bluetooth_adapters-0.17.0-py3-none-any.whl", hash = "sha256:24a8d485e77289f037494fe52877b5c20051feb4747f0173b768a66d8e2b13c4"}, + {file = "bluetooth_adapters-0.17.0.tar.gz", hash = "sha256:6a82ec713a4a5eccb870d7e9ff98e4002bbae885e1ab0f98f5056fc68db22325"}, +] + +[package.dependencies] +aiohttp = ">=3.8.1" +bleak = ">=0.21.1" +dbus-fast = ">=1.21.0" +mac-vendor-lookup = ">=0.1.12" +usb-devices = ">=0.4.5" + +[package.extras] +docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<2.1)", "sphinx-rtd-theme (>=1,<3)"] + [[package]] name = "certifi" version = "2023.11.17" @@ -1044,6 +1092,23 @@ files = [ [package.extras] test = ["pytest"] +[[package]] +name = "mac-vendor-lookup" +version = "0.1.12" +description = "Find the vendor for a given MAC address" +optional = false +python-versions = "<4, >=3.5" +files = [ + {file = "mac_vendor_lookup-0.1.12-py3-none-any.whl", hash = "sha256:aeec6eac01b07e6558d889b51f475a1e1e938e09cab409a069ab6a43b13cba58"}, +] + +[package.dependencies] +aiofiles = "*" +aiohttp = "*" + +[package.extras] +test = ["coverage", "pytest"] + [[package]] name = "markupsafe" version = "2.1.3" @@ -1715,6 +1780,17 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "usb-devices" +version = "0.4.5" +description = "Tools for mapping, describing, and resetting USB devices" +optional = false +python-versions = ">=3.9,<4.0" +files = [ + {file = "usb_devices-0.4.5-py3-none-any.whl", hash = "sha256:8a415219ef1395e25aa0bddcad484c88edf9673acdeae8a07223ca7222a01dcf"}, + {file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"}, +] + [[package]] name = "virtualenv" version = "20.25.0" @@ -2135,4 +2211,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.13" -content-hash = "4f1c7cd12b29dfc7a6e32a43ee39b0f1c0f0ffeb0c872e13538c20bf5ce8ba75" +content-hash = "9f47cf0a3a730ce4834c67c0963a06b48eed3ac0ec1bbb9b95f945bb4e6cf09c" diff --git a/pyproject.toml b/pyproject.toml index 914971e..3169f1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ bleak = "^0.21.1" construct = "^2.10.68" construct-typing = "^0.6.2" homeassistant = "^2023.1.1" +bleak-retry-connector = "^3.4.0" [tool.poetry.group.dev.dependencies] ruff = "^0.1.13" @@ -26,3 +27,6 @@ voluptuous-stubs = "^0.1.1" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +[tool.mypy] +explicit_package_bases = true