diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml index fc55f93..e3d59c2 100644 --- a/.github/workflows/create-release.yaml +++ b/.github/workflows/create-release.yaml @@ -8,6 +8,11 @@ on: required: false default: "" type: string + is_final: + description: "It is a final release (not a pre-release)" + required: false + default: true + type: boolean permissions: contents: write # Allow to commit and push. @@ -85,8 +90,8 @@ jobs: run: sudo apt-get update && sudo apt-get install -y gettext-base #---------------------------------------------- - # Replace version in pyproject.toml, config.yaml and build.yaml - - name: Replace version + # Bump version in pyproject.toml, config.yaml and build.yaml + - name: Bump version run: | envsubst < pyproject.template.toml > pyproject.toml envsubst < addons/gazpar2haws/config.yaml.template > addons/gazpar2haws/config.yaml @@ -100,7 +105,7 @@ jobs: git config --global user.name github-actions git config --global user.email github-actions@github.com git add pyproject.toml addons/gazpar2haws/config.yaml addons/gazpar2haws/build.yaml - git commit --allow-empty -m "Upgrade version to ${PACKAGE_VERSION}" + git commit --allow-empty -m "Bump version to ${PACKAGE_VERSION}" git push #---------------------------------------------- @@ -210,5 +215,6 @@ jobs: with: image: ssenart/gazpar2haws version: ${{ needs.prepare.outputs.package-version }} + is_latest : ${{ inputs.is_final }} username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} diff --git a/.github/workflows/publish-to-dockerhub.yaml b/.github/workflows/publish-to-dockerhub.yaml index e20d65e..0332574 100644 --- a/.github/workflows/publish-to-dockerhub.yaml +++ b/.github/workflows/publish-to-dockerhub.yaml @@ -8,6 +8,11 @@ on: required: false default: "" type: string + is_latest: + description: "Update the 'latest' tag" + required: false + default: true + type: boolean permissions: contents: read # Readonly permissions @@ -17,9 +22,9 @@ env: jobs: #---------------------------------------------- - # Collect information - information: - name: Collect information + # Prepare + prepare: + name: Prepare outputs: package-version: ${{ steps.select-package-version.outputs.package-version }} default_python_version: ${{ env.DEFAULT_PYTHON_VERSION }} @@ -66,7 +71,7 @@ jobs: # Publish Docker image to DockerHub publish-to-dockerhub: name: Publish to DockerHub - needs: information + needs: prepare runs-on: ubuntu-latest permissions: packages: write @@ -85,6 +90,7 @@ jobs: uses: ./.github/workflows/publish-to-dockerhub with: image: ssenart/gazpar2haws - version: ${{ needs.information.outputs.package-version }} + version: ${{ needs.prepare.outputs.package-version }} + is_latest: ${{ inputs.is_latest }} username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} diff --git a/.github/workflows/publish-to-dockerhub/action.yaml b/.github/workflows/publish-to-dockerhub/action.yaml index c6c345a..bc74835 100644 --- a/.github/workflows/publish-to-dockerhub/action.yaml +++ b/.github/workflows/publish-to-dockerhub/action.yaml @@ -10,6 +10,10 @@ inputs: version: description: "Version of the image" required: true + is_latest: + description: "Whether the version is a final release" + required: false + default: false username: description: "DockerHub username" required: true @@ -39,7 +43,7 @@ runs: images: ${{ inputs.image }} tags: | # Set latest tag for the default branch - type=raw,value=latest,enable={{is_default_branch}} + type=raw,value=latest,enable=${{ inputs.is_latest }} # Set the version tag for all branches type=raw,value=${{ inputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4909971..4d9b1fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.12] - 2025-01-15 + +### Fixed + +[#37](https://github.com/ssenart/gazpar2haws/issues/37): Error GrDF send missing data with type="Absence de Données". + +[#38](https://github.com/ssenart/gazpar2haws/issues/38): Using the HA addon, the PCE identifier is transformed into another number. + +[#36](https://github.com/ssenart/gazpar2haws/issues/36): Error if HA endpoint configuration is missing in configuration.yaml. + +### Added + +[#33](https://github.com/ssenart/gazpar2haws/issues/33): Dockerhub 'latest' tag is currently published only if the release is created in the main branch. + ## [0.1.11] - 2025-01-12 ### Fixed diff --git a/addons/gazpar2haws/rootfs/app/config/configuration.template.yaml b/addons/gazpar2haws/rootfs/app/config/configuration.template.yaml index 7b2069d..c22f182 100644 --- a/addons/gazpar2haws/rootfs/app/config/configuration.template.yaml +++ b/addons/gazpar2haws/rootfs/app/config/configuration.template.yaml @@ -1,22 +1,22 @@ logging: file: log/gazpar2haws.log - console: true + console: true level: debug - format: '%(asctime)s %(levelname)s [%(name)s] %(message)s' + format: "%(asctime)s %(levelname)s [%(name)s] %(message)s" grdf: scan_interval: ${GRDF_SCAN_INTERVAL} # Number of minutes between each data retrieval (0 means no scan: a single data retrieval at startup, then stops). devices: - - name: gazpar2haws # Name of the device in home assistant. It will be used as the entity_id: sensor.${name}. - username: "!secret grdf.username" - password: "!secret grdf.password" - pce_identifier: "!secret grdf.pce_identifier" - timezone: ${GRDF_TIMEZONE} # Timezone of the data. It should be the same as the Home Assistant timezone. - last_days: ${GRDF_LAST_DAYS} # Number of days of data to retrieve - reset: false # If true, the data will be reset before the first data retrieval. If false, the data will be kept and new data will be added. + - name: gazpar2haws # Name of the device in home assistant. It will be used as the entity_id: sensor.${name}. + username: "!secret grdf.username" + password: "!secret grdf.password" + pce_identifier: "!secret grdf.pce_identifier" + timezone: "${GRDF_TIMEZONE}" # Timezone of the data. It should be the same as the Home Assistant timezone. + last_days: ${GRDF_LAST_DAYS} # Number of days of data to retrieve + reset: false # If true, the data will be reset before the first data retrieval. If false, the data will be kept and new data will be added. homeassistant: host: "!secret homeassistant.host" port: "!secret homeassistant.port" - endpoint: ${HOMEASSISTANT_ENDPOINT} + endpoint: "${HOMEASSISTANT_ENDPOINT}" # The websocket endpoint to use. It should be /api/websocket by default. token: "!secret homeassistant.token" diff --git a/addons/gazpar2haws/rootfs/app/config/secrets.template.yaml b/addons/gazpar2haws/rootfs/app/config/secrets.template.yaml index c4f3926..d916d4d 100644 --- a/addons/gazpar2haws/rootfs/app/config/secrets.template.yaml +++ b/addons/gazpar2haws/rootfs/app/config/secrets.template.yaml @@ -1,7 +1,7 @@ -grdf.username: ${GRDF_USERNAME} -grdf.password: ${GRDF_PASSWORD} -grdf.pce_identifier: ${GRDF_PCE_IDENTIFIER} +grdf.username: "${GRDF_USERNAME}" +grdf.password: "${GRDF_PASSWORD}" +grdf.pce_identifier: "${GRDF_PCE_IDENTIFIER}" -homeassistant.host: ${HOMEASSISTANT_HOST} -homeassistant.port: ${HOMEASSISTANT_PORT} -homeassistant.token: ${HOMEASSISTANT_TOKEN} +homeassistant.host: "${HOMEASSISTANT_HOST}" +homeassistant.port: "${HOMEASSISTANT_PORT}" +homeassistant.token: "${HOMEASSISTANT_TOKEN}" diff --git a/config/secrets.template.yaml b/config/secrets.template.yaml index c4f3926..477159e 100644 --- a/config/secrets.template.yaml +++ b/config/secrets.template.yaml @@ -1,7 +1,7 @@ -grdf.username: ${GRDF_USERNAME} -grdf.password: ${GRDF_PASSWORD} -grdf.pce_identifier: ${GRDF_PCE_IDENTIFIER} - -homeassistant.host: ${HOMEASSISTANT_HOST} -homeassistant.port: ${HOMEASSISTANT_PORT} -homeassistant.token: ${HOMEASSISTANT_TOKEN} +grdf.username: "${GRDF_USERNAME}" +grdf.password: "${GRDF_PASSWORD}" +grdf.pce_identifier: "${GRDF_PCE_IDENTIFIER}" + +homeassistant.host: "${HOMEASSISTANT_HOST}" +homeassistant.port: "${HOMEASSISTANT_PORT}" +homeassistant.token: "${HOMEASSISTANT_TOKEN}" diff --git a/gazpar2haws/bridge.py b/gazpar2haws/bridge.py index cfe4d92..455f8b3 100644 --- a/gazpar2haws/bridge.py +++ b/gazpar2haws/bridge.py @@ -16,12 +16,30 @@ class Bridge: def __init__(self, config: config_utils.ConfigLoader): # GrDF scan interval (in seconds) + if config.get("grdf.scan_interval") is None: + raise ValueError("Configuration parameter 'grdf.scan_interval' is missing") self._grdf_scan_interval = int(config.get("grdf.scan_interval")) - # Home Assistant configuration + # Home Assistant configuration: host + if config.get("homeassistant.host") is None: + raise ValueError("Configuration parameter 'homeassistant.host' is missing") ha_host = config.get("homeassistant.host") + + # Home Assistant configuration: port + if config.get("homeassistant.port") is None: + raise ValueError("Configuration parameter 'homeassistant.port' is missing") ha_port = config.get("homeassistant.port") - ha_endpoint = config.get("homeassistant.endpoint") + + # Home Assistant configuration: endpoint + ha_endpoint = ( + config.get("homeassistant.endpoint") + if config.get("homeassistant.endpoint") + else "/api/websocket" + ) + + # Home Assistant configuration: token + if config.get("homeassistant.token") is None: + raise ValueError("Configuration parameter 'homeassistant.token' is missing") ha_token = config.get("homeassistant.token") # Initialize Home Assistant @@ -29,6 +47,9 @@ def __init__(self, config: config_utils.ConfigLoader): # Initialize Gazpar self._gazpar = [] + + if config.get("grdf.devices") is None: + raise ValueError("Configuration parameter 'grdf.devices' is missing") for grdf_device_config in config.get("grdf.devices"): self._gazpar.append(Gazpar(grdf_device_config, self._homeassistant)) diff --git a/gazpar2haws/gazpar.py b/gazpar2haws/gazpar.py index 3ab253e..3cebaa5 100644 --- a/gazpar2haws/gazpar.py +++ b/gazpar2haws/gazpar.py @@ -19,15 +19,59 @@ def __init__(self, config: dict[str, Any], homeassistant: HomeAssistantWS): self._homeassistant = homeassistant - # GrDF configuration + # GrDF configuration: name + if config.get("name") is None: + raise ValueError("Configuration parameter 'grdf.devices[].name' is missing") self._name = config.get("name") - self._data_source = config.get("data_source") + + # GrDF configuration: data source + self._data_source = ( + config.get("data_source") if config.get("data_source") else "json" + ) + + # GrDF configuration: username + if self._data_source != "test" and config.get("username") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].username' is missing" + ) self._username = config.get("username") + + # GrDF configuration: password + if self._data_source != "test" and config.get("password") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].password' is missing" + ) self._password = config.get("password") + + # GrDF configuration: pce_identifier + if self._data_source != "test" and config.get("pce_identifier") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].pce_identifier' is missing" + ) self._pce_identifier = str(config.get("pce_identifier")) - self._tmp_dir = config.get("tmp_dir") + + # GrDF configuration: tmp_dir + self._tmp_dir = config.get("tmp_dir") if config.get("tmp_dir") else "/tmp" + + # GrDF configuration: last_days + if config.get("last_days") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].last_days' is missing" + ) self._last_days = int(str(config.get("last_days"))) + + # GrDF configuration: timezone + if config.get("timezone") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].timezone' is missing" + ) self._timezone = str(config.get("timezone")) + + # GrDF configuration: reset + if config.get("reset") is None: + raise ValueError( + "Configuration parameter 'grdf.devices[].reset' is missing" + ) self._reset = bool(config.get("reset")) # As of date: YYYY-MM-DD @@ -129,7 +173,13 @@ async def _publish_entity( continue # Compute the total volume and energy - total += reading[property_name] + if reading[property_name] is not None: + total += reading[property_name] + else: + Logger.warning( + f"Missing property {property_name} for date {date}. Skipping..." + ) + continue statistics.append({"start": date.isoformat(), "state": total, "sum": total}) diff --git a/tests/config/configuration.yaml b/tests/config/configuration.yaml index d52f2cf..f6d86a6 100644 --- a/tests/config/configuration.yaml +++ b/tests/config/configuration.yaml @@ -17,5 +17,4 @@ grdf: homeassistant: host: "!secret homeassistant.host" port: "!secret homeassistant.port" - endpoint: "/api/websocket" token: "!secret homeassistant.token" diff --git a/tests/test_gazpar.py b/tests/test_gazpar.py index a62b406..1f357c0 100644 --- a/tests/test_gazpar.py +++ b/tests/test_gazpar.py @@ -24,7 +24,11 @@ def setup_method(self): # pylint: disable=R0801 ha_host = self._config.get("homeassistant.host") ha_port = self._config.get("homeassistant.port") - ha_endpoint = self._config.get("homeassistant.endpoint") + ha_endpoint = ( + self._config.get("homeassistant.endpoint") + if self._config.get("homeassistant.endpoint") + else "/api/websocket" + ) ha_token = self._config.get("homeassistant.token") self._haws = HomeAssistantWS( # pylint: disable=W0201 diff --git a/tests/test_haws.py b/tests/test_haws.py index 217c478..9d71b9a 100644 --- a/tests/test_haws.py +++ b/tests/test_haws.py @@ -27,7 +27,11 @@ def setup_method(self): ha_host = self._config.get("homeassistant.host") ha_port = self._config.get("homeassistant.port") - ha_endpoint = self._config.get("homeassistant.endpoint") + ha_endpoint = ( + self._config.get("homeassistant.endpoint") + if self._config.get("homeassistant.endpoint") + else "/api/websocket" + ) ha_token = self._config.get("homeassistant.token") self._haws = HomeAssistantWS( # pylint: disable=W0201