mirror of
https://github.com/home-assistant/core.git
synced 2026-02-05 14:55:35 +01:00
Compare commits
4 Commits
python-3.1
...
edenhaus-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2867d9e0f | ||
|
|
368eae89b1 | ||
|
|
1f4656fa3e | ||
|
|
19d8dab6fd |
118
.github/actions/builder/generic/action.yml
vendored
Normal file
118
.github/actions/builder/generic/action.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
name: "Image builder"
|
||||
description: "Build a Docker image"
|
||||
inputs:
|
||||
base-image:
|
||||
description: "Base image to use for the build"
|
||||
required: true
|
||||
# example: 'ghcr.io/home-assistant/amd64-homeassistant-base:2024.6.0'
|
||||
tags:
|
||||
description: "Tag(s) for the built image (can be multiline for multiple tags)"
|
||||
required: true
|
||||
# example: 'ghcr.io/home-assistant/amd64-homeassistant:2026.2.0' or multiline for multiple tags
|
||||
arch:
|
||||
description: "Architecture for the build (used for default labels)"
|
||||
required: true
|
||||
# example: 'amd64'
|
||||
version:
|
||||
description: "Version for the build (used for default labels)"
|
||||
required: true
|
||||
# example: '2026.2.0'
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile to build"
|
||||
required: true
|
||||
# example: './Dockerfile'
|
||||
cosign-base-identity:
|
||||
description: "Certificate identity regexp for base image verification"
|
||||
required: true
|
||||
# example: 'https://github.com/home-assistant/docker/.*'
|
||||
additional-labels:
|
||||
description: "Additional labels to add to the built image (merged with default labels)"
|
||||
required: false
|
||||
default: ""
|
||||
# example: 'custom.label=value'
|
||||
push:
|
||||
description: "Whether to push the image to the registry"
|
||||
required: false
|
||||
default: "true"
|
||||
# example: 'true' or 'false'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
with:
|
||||
cosign-release: "v2.5.3"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Verify base image signature
|
||||
shell: bash
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
||||
--certificate-identity-regexp "${{ inputs.cosign-base-identity }}" \
|
||||
"${{ inputs.base-image }}"
|
||||
|
||||
- name: Verify cache image signature
|
||||
id: cache
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
||||
--certificate-identity-regexp "https://github.com/home-assistant/core/.*" \
|
||||
"ghcr.io/home-assistant/${{ inputs.arch }}-homeassistant:latest"
|
||||
|
||||
- name: Prepare labels
|
||||
id: labels
|
||||
shell: bash
|
||||
run: |
|
||||
# Generate creation timestamp
|
||||
CREATED=$(date --rfc-3339=seconds --utc)
|
||||
|
||||
# Build default labels array
|
||||
LABELS=(
|
||||
"io.hass.arch=${{ inputs.arch }}"
|
||||
"io.hass.version=${{ inputs.version }}"
|
||||
"org.opencontainers.image.created=${CREATED}"
|
||||
"org.opencontainers.image.version=${{ inputs.version }}"
|
||||
)
|
||||
|
||||
# Append additional labels if provided
|
||||
if [ -n "${{ inputs.additional-labels }}" ]; then
|
||||
while IFS= read -r label; do
|
||||
[ -n "$label" ] && LABELS+=("$label")
|
||||
done <<< "${{ inputs.additional-labels }}"
|
||||
fi
|
||||
|
||||
# Output the combined labels using EOF delimiter for multiline
|
||||
{
|
||||
echo 'result<<EOF'
|
||||
printf '%s\n' "${LABELS[@]}"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build base image
|
||||
id: build
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: ${{ steps.vars.outputs.platform }}
|
||||
push: ${{ inputs.push }}
|
||||
cache-from: ${{ steps.cache.outcome == 'success' && format('ghcr.io/home-assistant/{0}-homeassistant:latest', inputs.arch) || '' }}
|
||||
build-args: |
|
||||
BUILD_FROM=${{ inputs.base-image }}
|
||||
tags: ${{ inputs.tags }}
|
||||
outputs: type=image,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
|
||||
labels: ${{ steps.labels.outputs.result }}
|
||||
|
||||
- name: Sign image
|
||||
if: ${{ inputs.push == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
# Sign each tag
|
||||
while IFS= read -r tag; do
|
||||
[ -n "$tag" ] && cosign sign --yes "${tag}@${{ steps.build.outputs.digest }}"
|
||||
done <<< "${{ inputs.tags }}"
|
||||
68
.github/actions/builder/machine/action.yml
vendored
Normal file
68
.github/actions/builder/machine/action.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: "Machine image builder"
|
||||
description: "Build or copy a machine-specific Docker image"
|
||||
inputs:
|
||||
machine:
|
||||
description: "Machine name"
|
||||
required: true
|
||||
# example: 'raspberrypi4-64'
|
||||
version:
|
||||
description: "Version for the build"
|
||||
required: true
|
||||
# example: '2026.2.0'
|
||||
arch:
|
||||
description: "Architecture for the build"
|
||||
required: true
|
||||
# example: 'aarch64'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare build variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "base_image=ghcr.io/home-assistant/${{ inputs.arch }}-homeassistant:${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Build tags array with version-specific tag
|
||||
TAGS=(
|
||||
"ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:${{ inputs.version }}"
|
||||
)
|
||||
|
||||
# Add general tag based on version
|
||||
if [[ "${{ inputs.version }}" =~ d ]]; then
|
||||
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:dev")
|
||||
elif [[ "${{ inputs.version }}" =~ b ]]; then
|
||||
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:beta")
|
||||
else
|
||||
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:stable")
|
||||
fi
|
||||
|
||||
# Output tags using EOF delimiter for multiline
|
||||
{
|
||||
echo 'tags<<EOF'
|
||||
printf '%s\n' "${TAGS[@]}"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
LABELS=(
|
||||
"io.hass.type=core"
|
||||
"io.hass.machine=${{ inputs.machine }}"
|
||||
"org.opencontainers.image.source=https://github.com/home-assistant/core"
|
||||
)
|
||||
|
||||
# Output the labels using EOF delimiter for multiline
|
||||
{
|
||||
echo 'labels<<EOF'
|
||||
printf '%s\n' "${LABELS[@]}"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build machine image
|
||||
uses: ./.github/actions/builder/generic
|
||||
with:
|
||||
base-image: ${{ steps.vars.outputs.base_image }}
|
||||
tags: ${{ steps.vars.outputs.tags }}
|
||||
arch: ${{ inputs.arch }}
|
||||
version: ${{ inputs.version }}
|
||||
dockerfile: machine/${{ inputs.machine }}
|
||||
cosign-base-identity: "https://github.com/home-assistant/core/.*"
|
||||
additional-labels: ${{ steps.vars.outputs.labels }}
|
||||
118
.github/workflows/builder.yml
vendored
118
.github/workflows/builder.yml
vendored
@@ -10,12 +10,12 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: "3.14.3"
|
||||
DEFAULT_PYTHON: "3.14.2"
|
||||
PIP_TIMEOUT: 60
|
||||
UV_HTTP_TIMEOUT: 60
|
||||
UV_SYSTEM_PYTHON: "true"
|
||||
# Base image version from https://github.com/home-assistant/docker
|
||||
BASE_IMAGE_VERSION: "2026.02.0"
|
||||
BASE_IMAGE_VERSION: "2026.01.0"
|
||||
ARCHITECTURES: '["amd64", "aarch64"]'
|
||||
|
||||
jobs:
|
||||
@@ -190,103 +190,53 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- &install_cosign
|
||||
name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
with:
|
||||
cosign-release: "v2.5.3"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Build variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
echo "base_image=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ env.BASE_IMAGE_VERSION }}" >> "$GITHUB_OUTPUT"
|
||||
echo "cache_image=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:latest" >> "$GITHUB_OUTPUT"
|
||||
echo "created=$(date --rfc-3339=seconds --utc)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify base image signature
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
||||
--certificate-identity-regexp "https://github.com/home-assistant/docker/.*" \
|
||||
"${{ steps.vars.outputs.base_image }}"
|
||||
|
||||
- name: Verify cache image signature
|
||||
id: cache
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
||||
--certificate-identity-regexp "https://github.com/home-assistant/core/.*" \
|
||||
"${{ steps.vars.outputs.cache_image }}"
|
||||
|
||||
- name: Build base image
|
||||
id: build
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
uses: ./.github/actions/builder/generic
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: ${{ steps.vars.outputs.platform }}
|
||||
push: true
|
||||
cache-from: ${{ steps.cache.outcome == 'success' && steps.vars.outputs.cache_image || '' }}
|
||||
build-args: |
|
||||
BUILD_FROM=${{ steps.vars.outputs.base_image }}
|
||||
base-image: ${{ steps.vars.outputs.base_image }}
|
||||
tags: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
|
||||
outputs: type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
|
||||
labels: |
|
||||
io.hass.arch=${{ matrix.arch }}
|
||||
io.hass.version=${{ needs.init.outputs.version }}
|
||||
org.opencontainers.image.created=${{ steps.vars.outputs.created }}
|
||||
org.opencontainers.image.version=${{ needs.init.outputs.version }}
|
||||
|
||||
- name: Sign image
|
||||
run: |
|
||||
cosign sign --yes "ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}@${{ steps.build.outputs.digest }}"
|
||||
arch: ${{ matrix.arch }}
|
||||
version: ${{ needs.init.outputs.version }}
|
||||
dockerfile: ./Dockerfile
|
||||
cosign-base-identity: "https://github.com/home-assistant/docker/.*"
|
||||
|
||||
build_machine:
|
||||
name: Build ${{ matrix.machine }} machine core image
|
||||
name: Build ${{ matrix.machine.name }} machine core image
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.machine.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
machine:
|
||||
- generic-x86-64
|
||||
- intel-nuc
|
||||
- khadas-vim3
|
||||
- odroid-c2
|
||||
- odroid-c4
|
||||
- odroid-m1
|
||||
- odroid-n2
|
||||
- qemuarm-64
|
||||
- qemux86-64
|
||||
- raspberrypi3-64
|
||||
- raspberrypi4-64
|
||||
- raspberrypi5-64
|
||||
- yellow
|
||||
- green
|
||||
- { name: generic-x86-64, arch: amd64 }
|
||||
- { name: intel-nuc, arch: amd64 }
|
||||
- { name: qemux86-64, arch: amd64 }
|
||||
- { name: khadas-vim3, arch: aarch64 }
|
||||
- { name: odroid-c2, arch: aarch64 }
|
||||
- { name: odroid-c4, arch: aarch64 }
|
||||
- { name: odroid-m1, arch: aarch64 }
|
||||
- { name: odroid-n2, arch: aarch64 }
|
||||
- { name: qemuarm-64, arch: aarch64 }
|
||||
- { name: raspberrypi3-64, arch: aarch64 }
|
||||
- { name: raspberrypi4-64, arch: aarch64 }
|
||||
- { name: raspberrypi5-64, arch: aarch64 }
|
||||
- { name: yellow, arch: aarch64 }
|
||||
- { name: green, arch: aarch64 }
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set build additional args
|
||||
run: |
|
||||
# Create general tags
|
||||
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
|
||||
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV
|
||||
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
|
||||
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV
|
||||
else
|
||||
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
@@ -294,15 +244,12 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# home-assistant/builder doesn't support sha pinning
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2025.11.0
|
||||
- name: Build machine image
|
||||
uses: ./.github/actions/builder/machine
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
--target /data/machine \
|
||||
--cosign \
|
||||
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
|
||||
machine: ${{ matrix.machine.name }}
|
||||
version: ${{ needs.init.outputs.version }}
|
||||
arch: ${{ matrix.machine.arch }}
|
||||
|
||||
publish_ha:
|
||||
name: Publish version files
|
||||
@@ -355,7 +302,10 @@ jobs:
|
||||
matrix:
|
||||
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
||||
steps:
|
||||
- *install_cosign
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
with:
|
||||
cosign-release: "v2.5.3"
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: matrix.registry == 'docker.io/homeassistant'
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -41,8 +41,8 @@ env:
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.3"
|
||||
DEFAULT_PYTHON: "3.14.3"
|
||||
ALL_PYTHON_VERSIONS: "['3.14.3']"
|
||||
DEFAULT_PYTHON: "3.14.2"
|
||||
ALL_PYTHON_VERSIONS: "['3.14.2']"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
# 10.6 is the current long-term-support
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.14.3"
|
||||
DEFAULT_PYTHON: "3.14.2"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
|
||||
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@@ -17,7 +17,7 @@ on:
|
||||
- "script/gen_requirements_all.py"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.14.3"
|
||||
DEFAULT_PYTHON: "3.14.2"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name}}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["google_air_quality_api"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["google_air_quality_api==3.0.1"]
|
||||
"requirements": ["google_air_quality_api==3.0.0"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyliebherrhomeapi"],
|
||||
"quality_scale": "silver",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyliebherrhomeapi==0.2.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ rules:
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""The syncthing integration."""
|
||||
|
||||
import asyncio
|
||||
from asyncio import Task
|
||||
import logging
|
||||
|
||||
import aiosyncthing
|
||||
@@ -14,7 +13,7 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
@@ -58,8 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async def cancel_listen_task(event: Event) -> None:
|
||||
"""Cancel the listen task on Home Assistant stop."""
|
||||
async def cancel_listen_task(_):
|
||||
await syncthing.unsubscribe()
|
||||
|
||||
entry.async_on_unload(
|
||||
@@ -82,46 +80,44 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
class SyncthingClient:
|
||||
"""A Syncthing client."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, client: aiosyncthing.Syncthing, server_id: str
|
||||
) -> None:
|
||||
def __init__(self, hass, client, server_id):
|
||||
"""Initialize the client."""
|
||||
self._hass = hass
|
||||
self._client = client
|
||||
self._server_id = server_id
|
||||
self._listen_task: Task[None] | None = None
|
||||
self._listen_task = None
|
||||
|
||||
@property
|
||||
def server_id(self) -> str:
|
||||
def server_id(self):
|
||||
"""Get server id."""
|
||||
return self._server_id
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
def url(self):
|
||||
"""Get server URL."""
|
||||
return self._client.url
|
||||
|
||||
@property
|
||||
def database(self) -> aiosyncthing.Database:
|
||||
def database(self):
|
||||
"""Get database namespace client."""
|
||||
return self._client.database
|
||||
|
||||
@property
|
||||
def system(self) -> aiosyncthing.System:
|
||||
def system(self):
|
||||
"""Get system namespace client."""
|
||||
return self._client.system
|
||||
|
||||
def subscribe(self) -> None:
|
||||
def subscribe(self):
|
||||
"""Start event listener coroutine."""
|
||||
self._listen_task = asyncio.create_task(self._listen())
|
||||
|
||||
async def unsubscribe(self) -> None:
|
||||
async def unsubscribe(self):
|
||||
"""Stop event listener coroutine."""
|
||||
if self._listen_task:
|
||||
self._listen_task.cancel()
|
||||
await self._client.close()
|
||||
|
||||
async def _listen(self) -> None:
|
||||
async def _listen(self):
|
||||
"""Listen to Syncthing events."""
|
||||
events = self._client.events
|
||||
server_was_unavailable = False
|
||||
@@ -146,7 +142,11 @@ class SyncthingClient:
|
||||
continue
|
||||
|
||||
signal_name = EVENTS[event["type"]]
|
||||
folder = event["data"].get("folder") or event["data"]["id"]
|
||||
folder = None
|
||||
if "folder" in event["data"]:
|
||||
folder = event["data"]["folder"]
|
||||
else: # A workaround, some events store folder id under `id` key
|
||||
folder = event["data"]["id"]
|
||||
async_dispatcher_send(
|
||||
self._hass,
|
||||
f"{signal_name}-{self._server_id}-{folder}",
|
||||
@@ -168,8 +168,7 @@ class SyncthingClient:
|
||||
server_was_unavailable = True
|
||||
continue
|
||||
|
||||
async def _server_available(self) -> bool:
|
||||
"""Check if the Syncthing server is available."""
|
||||
async def _server_available(self):
|
||||
try:
|
||||
await self._client.system.ping()
|
||||
except aiosyncthing.exceptions.SyncthingError:
|
||||
|
||||
@@ -21,7 +21,7 @@ DATA_SCHEMA = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
|
||||
async def validate_input(hass: HomeAssistant, data):
|
||||
"""Validate the user input allows us to connect."""
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
"""Support for Syncthing sensors."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
"""Support for monitoring the Syncthing instance."""
|
||||
|
||||
import aiosyncthing
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from . import SyncthingClient
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
FOLDER_PAUSED_RECEIVED,
|
||||
@@ -90,21 +86,14 @@ class FolderSensor(SensorEntity):
|
||||
"stateChanged": "state_changed",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
syncthing: SyncthingClient,
|
||||
server_id: str,
|
||||
folder_id: str,
|
||||
folder_label: str,
|
||||
version: str,
|
||||
) -> None:
|
||||
def __init__(self, syncthing, server_id, folder_id, folder_label, version):
|
||||
"""Initialize the sensor."""
|
||||
self._syncthing = syncthing
|
||||
self._server_id = server_id
|
||||
self._folder_id = folder_id
|
||||
self._folder_label = folder_label
|
||||
self._state: dict[str, Any] | None = None
|
||||
self._unsub_timer: CALLBACK_TYPE | None = None
|
||||
self._state = None
|
||||
self._unsub_timer = None
|
||||
|
||||
self._short_server_id = server_id.split("-")[0]
|
||||
self._attr_name = f"{self._short_server_id} {folder_id} {folder_label}"
|
||||
@@ -118,9 +107,9 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state["state"] if self._state else None
|
||||
return self._state["state"]
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
@@ -128,11 +117,11 @@ class FolderSensor(SensorEntity):
|
||||
return self._state is not None
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return self._state
|
||||
|
||||
async def async_update_status(self) -> None:
|
||||
async def async_update_status(self):
|
||||
"""Request folder status and update state."""
|
||||
try:
|
||||
state = await self._syncthing.database.status(self._folder_id)
|
||||
@@ -142,11 +131,11 @@ class FolderSensor(SensorEntity):
|
||||
self._state = self._filter_state(state)
|
||||
self.async_write_ha_state()
|
||||
|
||||
def subscribe(self) -> None:
|
||||
def subscribe(self):
|
||||
"""Start polling syncthing folder status."""
|
||||
if self._unsub_timer is None:
|
||||
|
||||
async def refresh(event_time) -> None:
|
||||
async def refresh(event_time):
|
||||
"""Get the latest data from Syncthing."""
|
||||
await self.async_update_status()
|
||||
|
||||
@@ -155,7 +144,7 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
@callback
|
||||
def unsubscribe(self) -> None:
|
||||
def unsubscribe(self):
|
||||
"""Stop polling syncthing folder status."""
|
||||
if self._unsub_timer is not None:
|
||||
self._unsub_timer()
|
||||
@@ -165,9 +154,8 @@ class FolderSensor(SensorEntity):
|
||||
"""Handle entity which will be added."""
|
||||
|
||||
@callback
|
||||
def handle_folder_summary(event: dict[str, Any]) -> None:
|
||||
"""Handle folder summary event."""
|
||||
if self._state:
|
||||
def handle_folder_summary(event):
|
||||
if self._state is not None:
|
||||
self._state = self._filter_state(event["data"]["summary"])
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -180,9 +168,8 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
@callback
|
||||
def handle_state_changed(event: dict[str, Any]) -> None:
|
||||
"""Handle folder state changed event."""
|
||||
if self._state:
|
||||
def handle_state_changed(event):
|
||||
if self._state is not None:
|
||||
self._state["state"] = event["data"]["to"]
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -195,9 +182,8 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
@callback
|
||||
def handle_folder_paused(event: dict[str, Any]) -> None:
|
||||
"""Handle folder paused event."""
|
||||
if self._state:
|
||||
def handle_folder_paused(event):
|
||||
if self._state is not None:
|
||||
self._state["state"] = "paused"
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -210,8 +196,7 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
|
||||
@callback
|
||||
def handle_server_unavailable() -> None:
|
||||
"""Handle server becoming unavailable."""
|
||||
def handle_server_unavailable():
|
||||
self._state = None
|
||||
self.unsubscribe()
|
||||
self.async_write_ha_state()
|
||||
@@ -224,8 +209,7 @@ class FolderSensor(SensorEntity):
|
||||
)
|
||||
)
|
||||
|
||||
async def handle_server_available() -> None:
|
||||
"""Handle server becoming available."""
|
||||
async def handle_server_available():
|
||||
self.subscribe()
|
||||
await self.async_update_status()
|
||||
|
||||
@@ -242,20 +226,20 @@ class FolderSensor(SensorEntity):
|
||||
|
||||
await self.async_update_status()
|
||||
|
||||
def _filter_state(self, state: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Filter and map state attributes."""
|
||||
filtered_state: dict[str, Any] = {
|
||||
def _filter_state(self, state):
|
||||
# Select only needed state attributes and map their names
|
||||
state = {
|
||||
self.STATE_ATTRIBUTES[key]: value
|
||||
for key, value in state.items()
|
||||
if key in self.STATE_ATTRIBUTES
|
||||
}
|
||||
|
||||
# A workaround, for some reason, state of paused folders is an empty string
|
||||
if filtered_state["state"] == "":
|
||||
filtered_state["state"] = "paused"
|
||||
if state["state"] == "":
|
||||
state["state"] = "paused"
|
||||
|
||||
# Add some useful attributes
|
||||
filtered_state["id"] = self._folder_id
|
||||
filtered_state["label"] = self._folder_label
|
||||
state["id"] = self._folder_id
|
||||
state["label"] = self._folder_label
|
||||
|
||||
return filtered_state
|
||||
return state
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"actual_compressor_speed": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"airflow_current_speed": {
|
||||
"default": "mdi:fan"
|
||||
},
|
||||
"mode": {
|
||||
"default": "mdi:gauge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,93 +18,89 @@ from homeassistant.util import slugify
|
||||
from . import UPDATE_TOPIC, WaterFurnaceConfigEntry, WaterFurnaceData
|
||||
|
||||
SENSORS = [
|
||||
SensorEntityDescription(name="Furnace Mode", key="mode", icon="mdi:gauge"),
|
||||
SensorEntityDescription(
|
||||
key="mode",
|
||||
translation_key="mode",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Total Power",
|
||||
key="totalunitpower",
|
||||
translation_key="total_unit_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Active Setpoint",
|
||||
key="tstatactivesetpoint",
|
||||
translation_key="tstat_active_setpoint",
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Leaving Air",
|
||||
key="leavingairtemp",
|
||||
translation_key="leaving_air_temp",
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Room Temp",
|
||||
key="tstatroomtemp",
|
||||
translation_key="room_temp",
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Loop Temp",
|
||||
key="enteringwatertemp",
|
||||
translation_key="entering_water_temp",
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Humidity Set Point",
|
||||
key="tstathumidsetpoint",
|
||||
translation_key="tstat_humid_setpoint",
|
||||
icon="mdi:water-percent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Humidity",
|
||||
key="tstatrelativehumidity",
|
||||
icon="mdi:water-percent",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Compressor Power",
|
||||
key="compressorpower",
|
||||
translation_key="compressor_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Fan Power",
|
||||
key="fanpower",
|
||||
translation_key="fan_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Aux Power",
|
||||
key="auxpower",
|
||||
translation_key="aux_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
name="Loop Pump Power",
|
||||
key="looppumppower",
|
||||
translation_key="loop_pump_power",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="actualcompressorspeed",
|
||||
translation_key="actual_compressor_speed",
|
||||
name="Compressor Speed", key="actualcompressorspeed", icon="mdi:speedometer"
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="airflowcurrentspeed",
|
||||
translation_key="airflow_current_speed",
|
||||
name="Fan Speed", key="airflowcurrentspeed", icon="mdi:fan"
|
||||
),
|
||||
]
|
||||
|
||||
@@ -128,7 +124,6 @@ class WaterFurnaceSensor(SensorEntity):
|
||||
"""Implementing the Waterfurnace sensor."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, client: WaterFurnaceData, description: SensorEntityDescription
|
||||
|
||||
@@ -26,49 +26,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"actual_compressor_speed": {
|
||||
"name": "Compressor speed"
|
||||
},
|
||||
"airflow_current_speed": {
|
||||
"name": "Fan speed"
|
||||
},
|
||||
"aux_power": {
|
||||
"name": "Aux power"
|
||||
},
|
||||
"compressor_power": {
|
||||
"name": "Compressor power"
|
||||
},
|
||||
"entering_water_temp": {
|
||||
"name": "Loop temperature"
|
||||
},
|
||||
"fan_power": {
|
||||
"name": "Fan power"
|
||||
},
|
||||
"leaving_air_temp": {
|
||||
"name": "Leaving air temperature"
|
||||
},
|
||||
"loop_pump_power": {
|
||||
"name": "Loop pump power"
|
||||
},
|
||||
"mode": {
|
||||
"name": "Furnace mode"
|
||||
},
|
||||
"room_temp": {
|
||||
"name": "Room temperature"
|
||||
},
|
||||
"total_unit_power": {
|
||||
"name": "Total power"
|
||||
},
|
||||
"tstat_active_setpoint": {
|
||||
"name": "Active setpoint"
|
||||
},
|
||||
"tstat_humid_setpoint": {
|
||||
"name": "Humidity setpoint"
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_cannot_connect": {
|
||||
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, we could not connect to {integration_title}. Please check your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
|
||||
|
||||
@@ -2865,53 +2865,3 @@ class ServiceRegistry:
|
||||
if TYPE_CHECKING:
|
||||
target = cast(Callable[..., ServiceResponse], target)
|
||||
return await self._hass.async_add_executor_job(target, service_call)
|
||||
|
||||
|
||||
# mypy: disable-error-code="attr-defined,no-untyped-def,unused-ignore,no-untyped-call"
|
||||
# fmt: off
|
||||
def _old_chain_future(source, destination):
|
||||
"""Revert of https://github.com/python/cpython/pull/142358."""
|
||||
from asyncio import futures # noqa: PLC0415
|
||||
|
||||
if not futures.isfuture(source) and not isinstance(
|
||||
source, concurrent.futures.Future
|
||||
):
|
||||
raise TypeError("A future is required for source argument")
|
||||
if not futures.isfuture(destination) and not isinstance(
|
||||
destination, concurrent.futures.Future
|
||||
):
|
||||
raise TypeError("A future is required for destination argument")
|
||||
source_loop = futures._get_loop(source) if futures.isfuture(source) else None # noqa: SLF001
|
||||
dest_loop = (
|
||||
futures._get_loop(destination) if futures.isfuture(destination) else None # noqa: SLF001
|
||||
)
|
||||
|
||||
def _set_state(future, other):
|
||||
if futures.isfuture(future):
|
||||
futures._copy_future_state(other, future) # noqa: SLF001
|
||||
else:
|
||||
futures._set_concurrent_future_state(future, other) # noqa: SLF001
|
||||
|
||||
def _call_check_cancel(destination):
|
||||
if destination.cancelled():
|
||||
if source_loop is None or source_loop is dest_loop:
|
||||
source.cancel()
|
||||
else:
|
||||
source_loop.call_soon_threadsafe(source.cancel)
|
||||
|
||||
def _call_set_state(source):
|
||||
if destination.cancelled() and dest_loop is not None and dest_loop.is_closed():
|
||||
return
|
||||
if dest_loop is None or dest_loop is source_loop:
|
||||
_set_state(destination, source)
|
||||
else:
|
||||
if dest_loop.is_closed():
|
||||
return
|
||||
dest_loop.call_soon_threadsafe(_set_state, destination, source)
|
||||
|
||||
destination.add_done_callback(_call_check_cancel)
|
||||
source.add_done_callback(_call_set_state)
|
||||
|
||||
|
||||
# monkey-patch asyncio to revert to 3.14.2 behavior
|
||||
asyncio.futures._chain_future = _old_chain_future # noqa: SLF001
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
image: ghcr.io/home-assistant/{machine}-homeassistant
|
||||
build_from:
|
||||
aarch64: "ghcr.io/home-assistant/aarch64-homeassistant:"
|
||||
amd64: "ghcr.io/home-assistant/amd64-homeassistant:"
|
||||
cosign:
|
||||
base_identity: https://github.com/home-assistant/core/.*
|
||||
identity: https://github.com/home-assistant/core/.*
|
||||
labels:
|
||||
io.hass.type: core
|
||||
org.opencontainers.image.source: https://github.com/home-assistant/core
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -1105,7 +1105,7 @@ google-nest-sdm==9.1.2
|
||||
google-photos-library-api==0.12.1
|
||||
|
||||
# homeassistant.components.google_air_quality
|
||||
google_air_quality_api==3.0.1
|
||||
google_air_quality_api==3.0.0
|
||||
|
||||
# homeassistant.components.slide
|
||||
# homeassistant.components.slide_local
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -981,7 +981,7 @@ google-nest-sdm==9.1.2
|
||||
google-photos-library-api==0.12.1
|
||||
|
||||
# homeassistant.components.google_air_quality
|
||||
google_air_quality_api==3.0.1
|
||||
google_air_quality_api==3.0.0
|
||||
|
||||
# homeassistant.components.slide
|
||||
# homeassistant.components.slide_local
|
||||
|
||||
Reference in New Issue
Block a user